web-dev-qa-db-fra.com

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

Disons 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 accomplir cela?

312
user4812

Dans Bash, vous pouvez le faire en activant l'option extglob, comme ceci (remplacez ls par cp et ajoutez le répertoire cible, 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
356
Vinko Vrsalovic

L'option extglob Shell vous donne une correspondance de motif plus puissante dans la ligne de commande.

Vous l'allumez 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 complets disponibles de ext terminés glob sont (extrait de man bash):

Si l'option shell extglob est activée à l'aide de l'option intégrée shopt, plusieurs opérateurs de correspondance de motifs étendus sont reconnus .ne liste de motifs est une liste d'un ou de plusieurs motifs séparés par un |. Des motifs composites peuvent être formés à l'aide de un ou plusieurs des sous-modèles suivants:

  • ? (liste de modèles)
    Correspond à zéro ou une occurrence des motifs donnés
  • * (liste de motifs)
    Correspond à zéro ou plusieurs occurrences des motifs donnés.
  • + (liste de motifs)
    Correspond à une ou plusieurs occurrences des motifs donnés.
  • @ (liste de motifs)
    Correspond à l'un des modèles donnés
  • ! (liste de motifs)
    Correspond à tout sauf à l'un des motifs 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, procédez comme suit:

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

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

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

Pas à Bash (que je sache), mais:

cp `ls | grep -v Music` /target_directory

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

22
ejgottl

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

Vous pouvez aussi utiliser une boucle for assez simple:

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

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

Un exemple qui pourrait être ce que l'affiche originale voulait:

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

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

5
mivk

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 à sec 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
4
Abid H. Mujtaba

Une astuce que je n'ai pas encore vue ici et qui n'utilise pas extglob, find ou grep consiste à traiter deux listes de fichiers comme des ensembles et "diff" les utilisant comm:

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

comm est préférable à diff car il n’a pas d’effort supplémentaire.

Ceci retourne tous les éléments de l'ensemble 1, ls, qui sont non é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.

4
James M. Lay

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

Trouvez a plusieurs options, vous pouvez être assez précis sur ce que vous incluez et excluez.

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

3
Daniel Bungert

La liste suivante répertorie tous les fichiers *.txt du répertoire en cours, à l’exception de ceux commençant par un nombre.

Cela fonctionne dans bash, dash, zsh et dans 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 modèle /some/dir/*.txt force la boucle for à parcourir 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. - L'expression ${FILE##*/} supprime tout composant de nom de répertoire en tête du nom de fichier (ici, /some/dir/), afin que les modèles puissent correspondre uniquement au nom de base du fichier. (Si vous ne supprimez que les noms de fichiers basés sur des suffixes, vous pouvez le raccourcir en $FILE à la place.)

  3. Dans la troisième ligne, tous les fichiers correspondant à la ligne case modèle [0-9]*) seront ignorés (l'instruction continue passera à la prochaine itération de la boucle for.). - Si vous le souhaitez, vous pouvez faire quelque chose de plus intéressant ici, par exemple. par exemple, sautez tous les fichiers qui ne commencent pas par une lettre (a – 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, à la fois .bak, et les fichiers ne commençant pas par un numéro.

2
zrajm

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
0
gabreal