Come posso usare i caratteri jolly inversi o negativi quando il pattern matching in una shell unix / linux?
-
03-07-2019 - |
Domanda
Dire che voglio copiare il contenuto di una directory escludendo file e cartelle i cui nomi contengono la parola "Musica".
cp [exclude-matches] *Music* /target_directory
Cosa dovrebbe andare al posto di [exclude-match] per raggiungere questo obiettivo?
Soluzione
In Bash puoi farlo abilitando l'opzione extglob
, in questo modo (sostituisci ls
con cp
e aggiungi la directory di destinazione, di ovviamente)
~/foobar> shopt extglob
extglob off
~/foobar> ls
abar afoo bbar bfoo
~/foobar> ls !(b*)
-bash: !: event not found
~/foobar> shopt -s extglob # Enables extglob
~/foobar> ls !(b*)
abar afoo
~/foobar> ls !(a*)
bbar bfoo
~/foobar> ls !(*foo)
abar bbar
In seguito puoi disabilitare extglob con
shopt -u extglob
Altri suggerimenti
L'opzione shell extglob
offre una corrispondenza dei pattern più potente nella riga di comando.
Lo accendi con shopt -s extglob
e lo spegni con shopt -u extglob
.
Nel tuo esempio inizialmente dovresti:
$ shopt -s extglob
$ cp !(*Music*) /target_directory
Tutti gli operatori di bing ext terminati glob sono (estratto da man bash
):
Se l'opzione shell extglob è abilitata usando lo shopt builtin, diversi sono estesi gli operatori di corrispondenza dei modelli sono riconosciuti. Nella seguente descrizione, una pacca tern-list è un elenco di uno o più pattern separati da un |. Modelli compositi può essere formato utilizzando uno o più dei seguenti sottotitoli:
- ? (Modello-list)
Corrisponde a zero o una occorrenza dei modelli specificati- * (modello-list)
Corrisponde a zero o più occorrenze dei modelli specificati- + (modello-list)
Corrisponde a una o più occorrenze dei modelli specificati- @ (modello-list)
Corrisponde a uno dei modelli indicati- ! (Modello-list)
Corrisponde a tutto tranne uno dei modelli indicati
Quindi, per esempio, se volessi elencare tutti i file nella directory corrente che non sono .c
o .h
, dovresti fare:
$ ls -d !(*@(.c|.h))
Ovviamente, il normale shell globing funziona, quindi l'ultimo esempio potrebbe anche essere scritto come:
$ ls -d !(*.[ch])
Non in bash (che io conosco), ma:
cp `ls | grep -v Music` /target_directory
So che questo non è esattamente quello che stavi cercando, ma risolverà il tuo esempio.
Se vuoi evitare il costo in mem dell'utilizzo del comando exec, credo che puoi fare di meglio con xargs. Penso che quanto segue sia un'alternativa più efficiente a
find foo -type f ! -name '*Music*' -exec cp {} bar \; # new proc for each exec
find . -maxdepth 1 -name '*Music*' -prune -o -print0 | xargs -0 -i cp {} dest/
In bash, un'alternativa a shopt -s extglob
è GLOBIGNORE
variabile . Non è molto meglio, ma trovo più facile da ricordare.
Un esempio che potrebbe essere quello che voleva il poster originale:
GLOBIGNORE="*techno*"; cp *Music* /only_good_music/
Al termine, deseleziona GLOBIGNORE
per poter rm * techno *
nella directory di origine.
Puoi anche usare un semplice per
loop:
for f in `find . -not -name "*Music*"`
do
cp $f /target/dir
done
La mia preferenza personale è usare grep e il comando while. Ciò consente di scrivere script potenti ma leggibili, garantendo di finire per fare esattamente ciò che si desidera. Inoltre, utilizzando un comando echo, è possibile eseguire una corsa a secco prima di eseguire l'operazione effettiva. Ad esempio:
ls | grep -v "Music" | while read filename
do
echo $filename
done
stamperà i file che finirai per copiare. Se l'elenco è corretto, il passo successivo è semplicemente sostituire il comando echo con il comando copia come segue:
ls | grep -v "Music" | while read filename
do
cp "$filename" /target_directory
done
Una soluzione per questo può essere trovata con find.
$ mkdir foo bar
$ touch foo/a.txt foo/Music.txt
$ find foo -type f ! -name '*Music*' -exec cp {} bar \;
$ ls bar
a.txt
Trova ha alcune opzioni, puoi essere abbastanza specifico su cosa includere ed escludere.
Modifica: Adam nei commenti ha notato che questo è ricorsivo. trova opzioni mindepth e maxdepth possono essere utili per controllare questo.
Un trucco che non ho ancora visto qui che non usa extglob
, find
o grep
è quello di trattare due file elenca come set e " diff " usando comm
:
comm -23 <(ls) <(ls *Music*)
comm
è preferibile su diff
perché non ha cruft extra.
Restituisce tutti gli elementi del set 1, ls
, che sono non anche nel set 2, ls * Music *
. Ciò richiede che entrambi i set siano in ordine per funzionare correttamente. Nessun problema per ls
e l'espansione glob, ma se stai usando qualcosa come find
, assicurati di invocare sort
.
comm -23 <(find . | sort) <(find . | grep -i '.jpg' | sort)
Potenzialmente utile.
Le seguenti opere elencano tutti i file * .txt
nella directory corrente, ad eccezione di quelli che iniziano con un numero.
Funziona in bash
, dash
, zsh
e in tutte le altre shell compatibili con POSIX.
for FILE in /some/dir/*.txt; do # for each *.txt file
case "${FILE##*/}" in # if file basename...
[0-9]*) continue ;; # starts with digit: skip
esac
## otherwise, do stuff with $FILE here
done
-
Nella riga uno il pattern
/some/dir/*.txt
farà sì che il ciclofor
esegua l'iterazione su tutti i file in/ some / dir
il cui nome termina con.txt
. -
Nella riga due viene utilizzata un'istruzione case per eliminare i file indesiderati. & # 8211; L'espressione
$ {FILE ## * /}
rimuove qualsiasi componente di nome dir principale dal nome del file (qui/ some / dir /
) in modo che gli schemi possano corrispondere solo al basename del file. (Se stai solo eliminando i nomi dei file in base ai suffissi, puoi accorciarli a$ FILE
). -
Nella riga tre, tutti i file corrispondenti alla riga
case
pattern[0-9] *
) verranno ignorati (ilcontinua
passa alla successiva iterazione del ciclofor
). & # 8211; Se vuoi, puoi fare qualcosa di più interessante qui, ad es. come saltare tutti i file che non iniziano con una lettera (a & # 8211; z) usando[! a-z] *
, oppure potresti usare più pattern per saltare diversi tipi di nomi di file, ad es.[0-9] * | * .bak
per saltare sia i file.bak
sia i file che non iniziano con un numero.
questo lo farebbe escludendo esattamente 'Music'
cp -a ^'Music' /target
questo e quello per escludere cose come la musica? * o *? la musica
cp -a ^\*?'complete' /target
cp -a ^'complete'?\* /target