Come posso usare i caratteri jolly inversi o negativi quando il pattern matching in una shell unix / linux?

StackOverflow https://stackoverflow.com/questions/216995

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?

È stato utile?

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
  1. Nella riga uno il pattern /some/dir/*.txt farà sì che il ciclo for esegua l'iterazione su tutti i file in / some / dir il cui nome termina con .txt.

  2. 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 ).

  3. Nella riga tre, tutti i file corrispondenti alla riga case pattern [0-9] * ) verranno ignorati (il continua passa alla successiva iterazione del ciclo for ). & # 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
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top