Comment utiliser des caractères génériques inverses ou négatifs lors de la recherche de modèle dans un shell Unix / Linux?

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

Question

Dites que je veux copier le contenu d'un répertoire, à l'exclusion des fichiers et des dossiers dont le nom contient le mot "Musique".

cp [exclude-matches] *Music* /target_directory

Que devrait-on remplacer par [exclude-matches] pour y parvenir?

Était-ce utile?

La solution

Dans Bash, vous pouvez le faire en activant l'option extglob , comme ceci (remplacez ls par cp et ajoutez le répertoire cible, de bien sûr)

~/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

Vous pouvez ultérieurement désactiver Extglob avec

shopt -u extglob

Autres conseils

L'option shell extglob vous donne une correspondance de modèle plus puissante en ligne de commande.

Vous l'activez avec shopt -s extglob et vous l'éteignez avec shopt -u extglob .

Dans votre exemple, vous feriez initialement:

$ shopt -s extglob
$ cp !(*Music*) /target_directory

Les opérateurs ext disponibles glob complets sont (extrait de man bash ):

  

Si l’option shell extglob est activée à l’aide de la commande shopt, plusieurs options étendues   les opérateurs de correspondance de modèle sont reconnus. Dans la description suivante, une pat   tern-list est une liste d'un ou plusieurs modèles séparés par un |. Motifs composites   peut être formé en utilisant un ou plusieurs des sous-motifs suivants:

     
      
  • ? (liste de modèles)
      Correspond à zéro ou à une occurrence des motifs donnés
  •   
  • * (liste de modèles)
      Correspond à zéro ou plusieurs occurrences des modèles donnés
  •   
  • + (liste de modèles)
      Correspond à une ou plusieurs occurrences des modèles donnés
  •   
  • @ (liste de modèles)
      Correspond à l'un des modèles donnés
  •   
  • ! (liste de modèles)
      Correspond à tout sauf à l'un des modèles donnés
  •   

Ainsi, par exemple, si vous souhaitez répertorier tous les fichiers du répertoire en cours qui ne sont pas des fichiers .c ou .h , vous ferez:

$ ls -d !(*@(.c|.h))

Bien sûr, le globing de shell normal fonctionne, donc le dernier exemple pourrait également être écrit:

$ ls -d !(*.[ch])

Pas dans bash (que je sache), mais:

cp `ls | grep -v Music` /target_directory

Je sais que ce n'est pas exactement ce que vous recherchiez, mais cela résoudra votre exemple.

Si vous voulez éviter le coût en mémoire lié à l’utilisation de la commande exec, je pense que vous pouvez faire mieux avec xargs. Je pense que ce qui suit est une alternative plus efficace à

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/

À bach, une alternative à shopt -s extglob est le variable GLOBIGNORE . Ce n'est pas vraiment mieux, mais je trouve plus facile de m'en souvenir.

Voici un exemple de ce que l'affiche originale voulait:

GLOBIGNORE="*techno*"; cp *Music* /only_good_music/

Une fois terminé, désélectionnez GLOBIGNORE pour pouvoir rm * techno * dans le répertoire source.

Vous pouvez également utiliser une boucle assez simple pour :

for f in `find . -not -name "*Music*"`
do
    cp $f /target/dir
done

Ma préférence personnelle est d'utiliser grep et la commande while. Cela permet d'écrire des scripts puissants mais lisibles, garantissant que vous finissez par faire exactement ce que vous voulez. De plus, en utilisant une commande d'écho, vous pouvez effectuer un essai à blanc avant d'effectuer l'opération réelle. Par exemple:

ls | grep -v "Music" | while read filename
do
echo $filename
done

imprimera les fichiers que vous finirez par copier. Si la liste est correcte, l'étape suivante consiste simplement à remplacer la commande echo par la commande copy comme suit:

ls | grep -v "Music" | while read filename
do
cp "$filename" /target_directory
done

Une solution à cela peut être trouvée avec find.

$ mkdir foo bar
$ touch foo/a.txt foo/Music.txt
$ find foo -type f ! -name '*Music*' -exec cp {} bar \;
$ ls bar
a.txt

La recherche a plusieurs options, vous pouvez être assez précis sur ce que vous incluez ou excluez.

Edit: Adam dans les commentaires a noté que cela est récursif. trouver les options mindepth et maxdepth peut être utile pour contrôler ceci.

Une astuce que je n'ai pas encore vue ici et qui n'utilise pas extglob , find ou grep consiste à traiter deux fichiers. répertorie sous forme de jeux et les "diff" en utilisant comm :

comm -23 <(ls) <(ls *Music*)

comm est préférable à diff car il n'a pas de crudité supplémentaire.

Ceci renvoie tous les éléments de l'ensemble 1, ls , qui ne sont pas également dans l'ensemble 2, ls * Music * . Cela nécessite que les deux ensembles soient triés pour fonctionner correctement. Aucun problème pour ls et l'expansion globale, mais si vous utilisez quelque chose comme find , veillez à invoquer sort .

comm -23 <(find . | sort) <(find . | grep -i '.jpg' | sort)

Potentiellement utile.

Les travaux suivants répertorient tous les fichiers *. txt du répertoire en cours, à l'exception de ceux commençant par un nombre.

Ceci fonctionne dans bash , tiret , zsh et tous les autres shells compatibles 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. Dans la première ligne, le motif / some / dir / *. txt fera en sorte que la boucle de itérera sur tous les fichiers de / some / dir dont le nom se termine par .txt .

  2. En deuxième ligne, une instruction case est utilisée pour éliminer les fichiers indésirables. & # 8211; L’expression $ {FILE ## * /} supprime tout composant de nom de répertoire principal du nom de fichier (ici, / some / dir / ), de sorte que les modèles ne puissent se comparer nom de base du fichier. (Si vous ne supprimez que les noms de fichiers en fonction de suffixes, vous pouvez le réduire à $ FILE .)

  3. À la ligne trois, tous les fichiers correspondant au motif [0-9] * ) seront ignorés (la continuer instruction saute à la prochaine itération de la boucle pour ). & # 8211; Si vous le souhaitez, vous pouvez faire quelque chose de plus intéressant ici, par exemple. comme sauter tous les fichiers qui ne commencent pas par une lettre (a & # 8211; z) en utilisant [! a-z] * , ou vous pouvez utiliser plusieurs modèles pour ignorer plusieurs types de noms de fichiers, par exemple. [0-9] * | * .bak pour ignorer les fichiers .bak et les fichiers ne commençant pas par un numéro.

cela le ferait en excluant exactement 'Musique'

cp -a ^'Music' /target

ceci et cela pour exclure des choses comme Music? * ou *? Music

cp -a ^\*?'complete' /target
cp -a ^'complete'?\* /target
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top