Comment utiliser des caractères génériques inverses ou négatifs lors de la recherche de modèle dans un shell Unix / Linux?
-
03-07-2019 - |
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?
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
-
Dans la première ligne, le motif
/ some / dir / *. txt
fera en sorte que la bouclede
itérera sur tous les fichiers de/ some / dir
dont le nom se termine par.txt
. -
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
.) -
À la ligne trois, tous les fichiers correspondant au motif
[0-9] *
) seront ignorés (lacontinuer
instruction saute à la prochaine itération de la bouclepour
). & # 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