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?
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
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])
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.
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/
Vous pouvez aussi utiliser une boucle for
assez simple:
for f in `find . -not -name "*Music*"`
do
cp $f /target/dir
done
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.
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
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.
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.
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
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
.
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.)
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.
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