Vai al contenuto

Aiuto espressioni regolari in *nix


jeby

Messaggi Raccomandati:

Continuo a trovare un po' pericoloso basare il tutto sull'output di ls, per di più salvato in una variabile: basta un nome che, per esempio, contiene il carattere '\n' (astrattamente possibile, è un carattere legale) ed il tutto va a donnine allegre. L'unico carattere che puoi essere assolutamente certo di non trovare in un nome di file è il carattere NUL ($'0') ed è quello che normalmente si usa come separatore di lista in questi casi (cfr. -print0 nella riga col find).

Io personalmente trovo find un comando abbastanza odioso da usare per questo genere di cose.

Innanzitutto perchè cambia da sistema a sistema (ad esempio alcuni hanno -printf e altri no, per esempio il mio no), e poi perchè obbliga ad avere a che fare con il path relativo del file, cosa con cui non voglio avere a che fare.

Dulcis in fundo, il suo output può venire smanacciato in vari modi, tipo xargs, la cui maggiorparte è dipendente dagli spazi (per esempio awk non riesce a trovare le colonne per splittare l'output), e la soluzione -print0 provoca problemi alla stragrande maggioranza delle altre utility che si possono usare sul suo output perchè non supportano il null separator.

D'altro canto, usando ls ho un output più facilmente manipolabile ed il rischio effettivo di avere dei file con all'interno uno \n è effettivamente irrisorio (e se anche ci fosse il rischio, basterebbe passare per sed).

Un altro rischio legato all'ls è la possibilità che il filesystem cambi tra la memorizzazione dell'ls e l'effettiva movimentazione dei file. Ma: innanzitutto è una misura di sicurezza largamente out of scope dallo script. È evidente che jeby ha bisogno di lasciarlo girare, quindi dubito che sarà così babbo da spostargli del file sotto, e poi, comunque, il rischio si limita ad avere un errore di mv, che creerà un solo file refuso, permettendo comunque allo script di proseguire. Dato che lo script è scritto per essere eseguito più volte sulla stessa collezione di file funzionando sempre, è un problema di poco conto. (al contrario, l'uso di find con la regex è limitante ad operare lo spostamento dei file solo se questi non sono già rinominati, altrimenti non li trova, o sarebbe comunque necessaria una diversa regex.)

In sostanza, trovo che ls non sia certo il comando perfetto, ma che in questo contesto ha contrappassi nettamente inferiori a quelli del find.

Comunque, prendo in considerazione le obiezioni e pubblico una versione dello stesso script modificata per funzionare con find. Ora il problema è diventato che il nome del file non può contenere "/", che secondo me è peggio che non poter contenere \n, però fa niente.


#!/bin/bash

find . -maxdepth 1 -mindepth 1 -type f -print0 | while IFS= read -r -d $'\0' file;
do
file=$(echo $file | sed 's/.*\///')
if [[ $file =~ (_|%28|%29) ]];
then
oldFile="$file"
file=$(echo $file | sed 's/\_/\ /g')
file=$(echo $file | sed 's/%28/\(/g')
file=$(echo $file | sed 's/%29/\)/g')
echo "** Renaming file $oldFile to $file"
mv "$oldFile" "$file"
fi
if [[ $file =~ ^([[:alpha:][:blank:]\-]+)[0-9]{2,3}.*$ ]];
then
folder=`echo "${BASH_REMATCH[1]}" | awk '$1=$1'`
if [ ! -d "$folder" ];
then
mkdir "$folder"
echo "** Directory $folder created"
fi
echo "** Moving file $file to $folder"
mv "$file" "$folder"
fi
done

"Fico, io ti rispondo che al buio tutti i gatti sembrano leopardi e che non bisogna mai comprare un gatto in un sacco. C'entrano qualcosa? Probabilmente no, esattamente come la tua metafora." [Loric]

Link al commento
Condividi su altri Social

  • Risposte 37
  • Creato
  • Ultima Risposta

I più attivi nella discussione

Giorni di maggior attività

I più attivi nella discussione

alura: prima di proseguire, mi avete già aiutato tantissimo quindi, oltre ad essere in debito di birra verso di voi, la maggior parte del lavoro sulle foto si è praticamente concluso durante la notte! Sono rimasti fuori solo dei gruppetti che ho già sistemato a mano stamattina ;)

Ho usato il primo script di Artemis per spostare la maggior parte dei file, quello di Loric per spostare i file col "-" in mezzo e le serie che rimanevano fuori le ho spostate io a mano (comunque erano già ripulite nel nome).

Voi non avete idea del sollievo che mi avete dato, finalmente ho una roba organizzata e non quell'accozzaglia di file che avevo prima!! Se mai comprerò una stampante 3D, mandatemi una scansione di voi medesimi che vi faccio una statua! :lol:

Altre soluzioni che ho trovato in rete per estrarre la parte prima dei numeri sono:


${filename%%[0-9]*}

questa era quella a cui avevo più o meno pensato io, ma non so perché non funziona...Ad essere sincero è quella che mi piace di più perché, come nel caso dello script di Loric, è molto semplice è usa proprietà "base" della bash, e non altri linguaggi.


printf "%s\n" *[[:space:]][0-9]* | sed 's/^\([^0-9]*\) [0-9].*$/\1/'

questa non l'ho nemmeno provata...

Dato che siete così generosi e che oggi è anche il mio compleanno, se volete proseguiamo con altre liste, oppure, che è la cosa che preferisco, vi faccio delle domande sugli script che avete già fatto così imparo e la prossima volta faccio da me!

Ad esempio:

in entrambi gli script c'è un ciclo while, ma è impostato in maniera diversa:


while read -r file


while IFS= read -r -d $'\0' kk

per cosa stanno le opzioni -r e -d?

il $'\0' a che serve?

non l'ho trovato nel man while

Loric, nel tuo script utilizzi:


SUBDIR="${NODIRNAME/ [0-9][0-9][0-9]_*}"

perché non col "." prima di "*"?


SUBDIR="${NODIRNAME/ [0-9][0-9][0-9]_.*}"

potrebbe essere così?


SUBDIR="${NODIRNAME/ [0-9]{2,3}_.*}"

E se fosse


SUBDIR="${NODIRNAME/ [0-9]*}"

leverebbe tutto quello che c'è da appena prima del primo numero? Cioè ad esempio, nel caso di

file 1 001 ciao pippo.jpg

quello che hai scritto tu restituisce file 1, mentre quello che ho scritto io restituisce file, giusto?

infine, se tutti i file nella cartella che mi interessano fossero nello stesso formato e con la stessa estensione (non era il caso delle foto, molte erano senza estensione... :roll: ), non avrei bisogno di fare la regex nel find, giusto? Gli passo solo il risultato del find sulla tipologia di file (esempio: tutti i .jpg), eseguo in maniera condizionale il riordino di parentesi e trattini bassi (ovvero, il primo IF che c'è nello script di Artemis) e poi la parte del SUBDIR="{NODIRNAME/ [0-9][0-9][0-9]_*}

giusto?

scusate se sono gnucco, ma se imparo qualcosa la prossima volta non devo disturbarvi :)

Grazieeeee!

Mazda MX-5 20th anniversary "barbone edition" - Tutto quello che scrivo è IMHO

k21x8z.png

Link al commento
Condividi su altri Social

Io personalmente trovo find un comando abbastanza odioso da usare per questo genere di cose.

Innanzitutto perchè cambia da sistema a sistema (ad esempio alcuni hanno -printf e altri no, per esempio il mio no), e poi perchè obbliga ad avere a che fare con il path relativo del file, cosa con cui non voglio avere a che fare.

-print0, è vero, non è strettamente POSIX. Il find dello scatolotto della Synology che ho io, infatti, non lo supporta nativamente, ma è possibile scaricare una versione alternativa di find che invece lo ha.

Il path relativo non è neanche esso un problema, almeno se la prima cosa che fai, appunto, è estrarre il nome del file dal suo path e poi agisci su una directory pre-impostata, cosa che puoi fare o con una mezza riga in bash, come ho fatto io, o con basename che fa parte delle coreutils Peraltro, per come find viene utilizzato in suddetto script, non funziona recursivamente e si limita a mostrare solo i file nella dir di primo livello quindi il problema è comunque escluso.

Dulcis in fundo, il suo output può venire smanacciato in vari modi, tipo xargs, la cui maggiorparte è dipendente dagli spazi (per esempio awk non riesce a trovare le colonne per splittare l'output), e la soluzione -print0 provoca problemi alla stragrande maggioranza delle altre utility che si possono usare sul suo output perchè non supportano il null separator.

Ho una certa antipatia per xargs ed infatti ho evitato di usarlo.

D'altro canto, usando ls ho un output più facilmente manipolabile ed il rischio effettivo di avere dei file con all'interno uno \n è effettivamente irrisorio (e se anche ci fosse il rischio, basterebbe passare per sed).

Un altro rischio legato all'ls è la possibilità che il filesystem cambi tra la memorizzazione dell'ls e l'effettiva movimentazione dei file. Ma: innanzitutto è una misura di sicurezza largamente out of scope dallo script. È evidente che jeby ha bisogno di lasciarlo girare, quindi dubito che sarà così babbo da spostargli del file sotto, e poi, comunque, il rischio si limita ad avere un errore di mv, che creerà un solo file refuso, permettendo comunque allo script di proseguire. Dato che lo script è scritto per essere eseguito più volte sulla stessa collezione di file funzionando sempre, è un problema di poco conto. (al contrario, l'uso di find con la regex è limitante ad operare lo spostamento dei file solo se questi non sono già rinominati, altrimenti non li trova, o sarebbe comunque necessaria una diversa regex.)

Come dici si può usare un'altra regexp. Peraltro da quello che ho capito qui si tratta di file di immagini, per cui al limite si cerca per estensione.

In sostanza, trovo che ls non sia certo il comando perfetto, ma che in questo contesto ha contrappassi nettamente inferiori a quelli del find.

Comunque, prendo in considerazione le obiezioni e pubblico una versione dello stesso script modificata per funzionare con find. Ora il problema è diventato che il nome del file non può contenere "/", che secondo me è peggio che non poter contenere \n, però fa niente.

Ecco, un file che ha "/" nel suo nome NON dovrebbe esistere. Il FS ext3, per esempio, lo esclude categoricamente (anche se lo si può fregare con alcuni trucchetti e a determinate condizioni). "/", in altre parole, è proprio un carattere illegale perché serve (o dovrebbe servire) solo a delimitare i path.

- - - - - - - - - - AGGIUNTA al messaggio già esistente - - - - - - - - - -

Altre soluzioni che ho trovato in rete per estrarre la parte prima dei numeri sono:


${filename%%[0-9]*}

questa era quella a cui avevo più o meno pensato io, ma non so perché non funziona...Ad essere sincero è quella che mi piace di più perché, come nel caso dello script di Loric, è molto semplice è usa proprietà "base" della bash, e non altri linguaggi.

La sintassi è corretta:


$ filename="Vacanze Calabria 001 (20030810) testo vario"; echo "${filename%%[0-9]*}"
Vacanze Calabria

In che contesto non funziona?

in entrambi gli script c'è un ciclo while, ma è impostato in maniera diversa:


while read -r file


while IFS= read -r -d $'\0' kk

per cosa stanno le opzioni -r e -d?

il $'\0' a che serve?

non l'ho trovato nel man while

E' un'opzione di read che, di solito, è un comando c.d. built-in in bash, cioè non provoca un fork() per lanciare un comando esterno ma viene interpretato direttamente.

Lo switch -r significa "non interpretare il backslash come carattere di escape, trattatalo come carattere normale". Senza di esso un carattere \ a fine riga verrebbe interpretato come "questa riga continua nella riga successiva, non considerare il newline (o altro delimitatore) come fine riga".

Lo switch -d, invece, dice "Non usare il carattere di newline come fine riga, ma quello che ti dico io". Ed in questo caso si tratta del carattere 0. $'\0' è la notazione che si usa in bash per descrivere un carattere (in questo caso 0) come "carattere che ha il valore ASCII pari a x" (qui x = 0).

Loric, nel tuo script utilizzi:


SUBDIR="${NODIRNAME/ [0-9][0-9][0-9]_*}"

perché non col "." prima di "*"?


SUBDIR="${NODIRNAME/ [0-9][0-9][0-9]_.*}"

potrebbe essere così?


SUBDIR="${NODIRNAME/ [0-9]{2,3}_.*}"

No. Perché a quel punto l'espressione regolare si aspetta il . dopo il _. Questo è bash, non PCRE :)

E se fosse


SUBDIR="${NODIRNAME/ [0-9]*}"

leverebbe tutto quello che c'è da appena prima del primo numero? Cioè ad esempio, nel caso di

file 1 001 ciao pippo.jpg

quello che hai scritto tu restituisce file 1, mentre quello che ho scritto io restituisce file, giusto?

Per la precisione restituisce "file "

infine, se tutti i file nella cartella che mi interessano fossero nello stesso formato e con la stessa estensione (non era il caso delle foto, molte erano senza estensione... :roll: ), non avrei bisogno di fare la regex nel find, giusto? Gli passo solo il risultato del find sulla tipologia di file (esempio: tutti i .jpg), eseguo in maniera condizionale il riordino di parentesi e trattini bassi (ovvero, il primo IF che c'è nello script di Artemis) e poi la parte del SUBDIR="{NODIRNAME/ [0-9][0-9][0-9]_*}

giusto?

scusate se sono gnucco, ma se imparo qualcosa la prossima volta non devo disturbarvi :)

Grazieeeee!

Puoi fare la sostituzione di parentesi, _ e altre sottostringhe direttamente da bash. Per cose più complesse sarebbe in effetti meglio usare sed o awk.

Aggiungo un ulteriore dettaglio: lo script di Artemis contiene alcuni costrutti molto avanzati, tipo le regex con "=~", disponibili solo nelle ultime versioni di bash (non mi ricordo se dalla 3 o dalla 4). Dacci un'occhiata sono una butenza :)

Modificato da loric

Alfiat Bravetta senza pomello con 170 cavalli asmatici che vanno a broda; pack "Terrone Protervo" (by Cosimo) contro lo sguardo da triglia. Questa è la "culona".

Link al commento
Condividi su altri Social

-print0, è vero, non è strettamente POSIX. Il find dello scatolotto della Synology che ho io, infatti, non lo supporta nativamente, ma è possibile scaricare una versione alternativa di find che invece lo ha.

Il path relativo non è neanche esso un problema, almeno se la prima cosa che fai, appunto, è estrarre il nome del file dal suo path e poi agisci su una directory pre-impostata, cosa che puoi fare o con una mezza riga in bash, come ho fatto io, o con basename che fa parte delle coreutils Peraltro, per come find viene utilizzato in suddetto script, non funziona recursivamente e si limita a mostrare solo i file nella dir di primo livello quindi il problema è comunque escluso.

basename soffre gli spazi e altri terminatori. Non mi sono mai interessato più di tanto quindi può essere che mi sono perso qualche impostazione, ma tra l'altro strippa via anche i suffissi.

Preferisco non usarlo di solito, mi ha sempre dato problemi.

Ecco, un file che ha "/" nel suo nome NON dovrebbe esistere. Il FS ext3, per esempio, lo esclude categoricamente (anche se lo si può fregare con alcuni trucchetti e a determinate condizioni). "/", in altre parole, è proprio un carattere illegale perché serve (o dovrebbe servire) solo a delimitare i path.

In ambiente unix è vero, ma in caso di file creati in ambiente Windows e Dos (o qualsiasi sistema ibm legacy basato su AS) può capitare.

Figurati che io ebbi a che fare con una intera partita di brevetti che si chiamavano "{publication}/{application}/{countryCode}.xml" (dove ovviamente c'erano i numeri tra graffe). Pensa le bestemmie nel processare sti file con gli script. :D

"Fico, io ti rispondo che al buio tutti i gatti sembrano leopardi e che non bisogna mai comprare un gatto in un sacco. C'entrano qualcosa? Probabilmente no, esattamente come la tua metafora." [Loric]

Link al commento
Condividi su altri Social

basename soffre gli spazi e altri terminatori. Non mi sono mai interessato più di tanto quindi può essere che mi sono perso qualche impostazione, ma tra l'altro strippa via anche i suffissi.

Preferisco non usarlo di solito, mi ha sempre dato problemi.

Basename strippa i suffissi solo se usato con lo switch -s. In realtà da bash io uso "${FILENAME##*/}". No fork(), nessuna necessità di lanciare comandi esterni. E' la soluzione più veloce ed efficiente IMHO.

In ambiente unix è vero, ma in caso di file creati in ambiente Windows e Dos (o qualsiasi sistema ibm legacy basato su AS) può capitare.

Figurati che io ebbi a che fare con una intera partita di brevetti che si chiamavano "{publication}/{application}/{countryCode}.xml" (dove ovviamente c'erano i numeri tra graffe). Pensa le bestemmie nel processare sti file con gli script. :D

Ah be', se abbiamo a che fare col file system del demonio allora alzo le mani :D

edit: vedo che il NTFS rifiuta nomi di file con il carattere /

Modificato da loric

Alfiat Bravetta senza pomello con 170 cavalli asmatici che vanno a broda; pack "Terrone Protervo" (by Cosimo) contro lo sguardo da triglia. Questa è la "culona".

Link al commento
Condividi su altri Social

Ho trovato questo su come "maneggiare" (correttamente e non) i nomi di file da bash:

Filenames and Pathnames in Shell (bash, dash, ash, ksh, and so on): How to do it Correctly

edit:

ho trovato anche questo: http://mywiki.wooledge.org/ParsingLs

Alfiat Bravetta senza pomello con 170 cavalli asmatici che vanno a broda; pack "Terrone Protervo" (by Cosimo) contro lo sguardo da triglia. Questa è la "culona".

Link al commento
Condividi su altri Social

Crea un account o accedi per lasciare un commento

Devi essere iscritto per commentare e visualizzare le sezioni protette!

Crea un account

Iscriviti nella nostra community. È facile!

Registra un nuovo account

Accedi

Sei già registrato? Accedi qui.

Accedi Ora

×
×
  • Crea Nuovo...

 

Stiamo sperimentando dei banner pubblicitari a minima invasività: fai una prova e poi facci sapere come va!

Per accedere al forum, disabilita l'AdBlock per questo sito e poi clicca su accetta: ci sarai di grande aiuto! Grazie!

Se non sai come si fa, puoi pensarci più avanti, cliccando su "ci penso" per continuare temporaneamente a navigare. Periodicamente ricomparità questo avviso come promemoria.