"[!]` (point d'exclamation entre crochets) caractère générique dans bash

10

J'ai rencontré des schémas de globbing et des caractères génériques et je suis particulièrement intéressé [!].

Cette construction est similaire à la [!]construction, sauf qu'au lieu de faire correspondre des caractères à l'intérieur des crochets, elle correspondra à n'importe quel caractère, tant qu'elle n'est pas répertoriée entre les [et ].

rm myfile [!192]

Je pense que ce qui précède supprimera tout / tous les fichiers, à l'exception de tous / tous les fichiers qui ont 192 dans leur nom.

Je suis cependant préoccupé par la bonne utilisation de ceci avec une extension de fichier, et en particulier de multiples conditions.

Quelle serait la syntaxe appropriée dans une telle situation?

rm myfile [!.gif .csv. mp3] 

ou

rm myfile [!.gif !.csv !.mp3]

Je crains que la période ne soit mal placée, et donc tout fichier avec un .(qui serait certainement l'un d'eux?) Serait alors manipulé, lorsque je chercherais à provoquer la manipulation de fichiers spécifiques.

Cette construction est similaire à la [ ]construction, sauf qu'au lieu de faire correspondre des caractères à l'intérieur des crochets, elle correspondra à n'importe quel caractère, tant qu'elle n'est pas répertoriée entre les [et ].

(extrait de http://www.tldp.org/LDP/GNU-Linux-Tools-Summary/html/x11655.htm )

Maintenant; pour moi, cela suggère alors qu'un singulier !est suffisant, et toutes les valeurs qui y figurent après sont contenues dans la plage.

IronUhlan
la source
5
comme astuce générale, utilisez ls my-patternou echo my-commandpour vérifier si le globbing est fait comme vous le souhaitez, avant d'exécuterrm
Wayne_Yux
N'oubliez pas non plus que de nombreux fichiers, sinon la plupart, n'ont pas d'extensions. Les extensions sur les systèmes Linux ne sont généralement pas pertinentes et ne sont pas utilisées pour définir le type de fichier par la majorité des programmes.
terdon
2
Notez que votre première citation est mal citée, maintenant elle semble dire essentiellement qu'elle [!]est similaire mais pas comme[!]
ilkkachu
2
Il convient de mentionner que cela ^a exactement la même signification que !dans ce contexte, [^a]exclut donc aexactement comme le [!a]fait: Si le premier caractère suivant le [est un! ou ^ alors tout caractère non inclus est mis en correspondance. ( man bash)
dessert
3
rm myfile [!192]supprimera myfile, ainsi que tout fichier caractère non nommé 1, 9ou 2.
Kevin

Réponses:

16

Citant man 7 glob:

An  expression  "[!...]"  matches  a single character, namely any character
that is not matched by the expression obtained by removing the first '!' from it.
(Thus, "[!]a-]" matches any single character except ']', 'a' and '-'.)

L'accent sur la partie à caractère unique ici []ne peut pas être utilisé pour des chaînes entières dans Bash Pattern Matching.


bashL' expansion d'accolade de 's peut être utilisée pour faire correspondre des chaînes, mais il n'y a aucun moyen (je le sais) de les annuler. Par exemple

1.{gif,csv,mp3}

est étendu à

1.gif 1.csv 1.mp3

peu importe si ces fichiers existent.


Comme l'a souligné wchargin , shoptpeut être utilisé pour activer les opérateurs de correspondance de modèle étendus, l'un d'eux étant !(). Après la course shopt -s extglob, par exemple

1.!(gif|csv|mp3)

est étendu à

1.jpg 1.bmp 1.png

si ces fichiers existent dans le répertoire courant. Pour plus d'informations, consultez la man bashsection EXPANSION / Expansion de noms de chemins) et Expansion de noms de chemins (globbing) .


Pour ce que vous essayez de faire, je l'utilise toujours find, ce qui offre la négation et bien plus encore, par exemple de la manière suivante:

find . -maxdepth 1 -type f ! -name "*.gif" -a ! -name "*.csv" -a ! -name "*.mp3"

Cela répertorie simplement les résultats, si vous souhaitez les supprimer, ajoutez simplement l' -deleteoption à la fin de la commande. Comme toujours pour supprimer des actions: vérifiez toujours soigneusement en premier .

findoffre une tonne d'options utiles très bien expliquées par man find.

dessert
la source
1
Notez que cette findcommande est récursive ( edit: comme il a été montré à l'origine - je pourrais garder ce commentaire pour les informations supplémentaires pertinentes mais non essentielles qu'il véhicule). Pour rechercher des fichiers résidant uniquement dans le répertoire courant mais pas également dans ses sous-répertoires - et donc supprimer uniquement ces fichiers si l' -deleteaction est ajoutée - vous pouvez les ajouter -maxdepth 1immédiatement après find .. Cela le mettrait en ligne avec l'effet du globbing, car les globs ne sont pas récursifs dans Bash, sauf si l' globstaroption shell est activée et **utilisée. Le glob représenté dans la question n'est pas récursif dans tous les obus.
Eliah Kagan
2
« il n'y a aucun moyen (je connais) pour les nier » -avec shopt -s extglob, vous pouvez utiliser echo 1.!(gif|csv|mp3), qui imprime tout 1.extextn'est pas gif, csvou mp3. (Voir la sous-section "Pattern Matching" de man bash.)
wchargin
10

Je pense que vous avez mal compris l'utilisation des crochets. [abc]correspond à l' un des caractères a, bou c. [!abc]correspond à un caractère qui ne l' est pasa , bou c. Donc le commandement

rm myfile [192]

supprimera myfileet les fichiers 1, 9et 2parce que vous avez un espace entre mon fichier et le support. En revanche, la commande

rm myfile[192]

supprimera les fichiers myfile1, myfile9et myfile2.

pLumo
la source
vrai. Mieux vaut utiliser find.
pLumo
En fait, [] est utilisé pour une plage, [!] Un seul caractère
IronUhlan
1
@IronUhlan, ils prennent tous les deux une plage ou une liste de caractères. C'est juste que l'autre est une négation.
ilkkachu
7

Les parenthèses [ ]désignent une classe de caractères. N'importe quel caractère à l'intérieur d'eux peut correspondre à la position donnée. Donc

file[192]

correspond à trois noms possibles:

file1
file2
file9

Il ne correspond pasfile192 , car il ne traite que le caractère unique après file.

Nous pouvons également utiliser des plages entre parenthèses. [0-9]correspond à n'importe quel chiffre dans la position donnée. Pour deux chiffres, nous avons besoin [0-9][0-9]. Pour un chiffre suivi d'une majuscule, nous pourrions utiliser [0-9][A-Z]...

! indique la négation.

file[!192]

correspondra aux fichiers qui ont n'importe quel caractère après filesauf 1ou 9ou 2. Par exemple, il correspondra à file7et fileset file%(et bien d'autres) mais pas file192 , car cela a deux caractères supplémentaires.

Pour votre cas d'utilisation, je suggère d'utiliser findau lieu de [!]. Il a un notopérateur.

Il est également récursif , ce qui signifie qu'il va dans tous les sous-répertoires par défaut. Vous pouvez le limiter au répertoire courant avec -maxdepth 1, si c'est ce que vous voulez. Les caractères génériques du shell (globbing) ne sont pas récursifs (bien que le globbing récursif avec **puisse être activé).

En supposant que vous ne voulez pas éviter de supprimer des liens symboliques, nous pouvons ajouter -type daux tests annulés pour éviter de supprimer des répertoires. Merci à Eliah Kagan pour cette suggestion.

find /path -maxdepth 1 -not \( -type d -or -iname "*.gif" -or -iname "*.csv" -or -iname "*.mp3" \)

Si vous voyez ce que vous voulez, vous pouvez réexécuter la commande avec -delete

find /path -maxdepth 1  -not \( -type d -or -iname "*.gif" -or -iname "*.csv" -or -iname "*.mp3" \) -delete
Zanna
la source
2
Et je pense que vous pouvez également faire plusieurs gammes à la fois. Par exemple, le chiffre hexadécimal pourrait être[0-9a-fA-F]
Wilson
@Zanna, oui! J'utilisais réellement find :) Je ne savais pas qu'il y avait un opérateur "not" :)
IronUhlan