Comportement étrange de tr en utilisant des plages

10

J'ai un serveur particulier qui présente un comportement étrange lors de l'utilisation de tr. Voici un exemple d'un serveur qui fonctionne:

-bash-3.2$ echo "abcdefghijklmnopqrstuvwxyz1234567890"|tr -d [a-z]
1234567890
-bash-3.2$

Cela me semble parfaitement logique.

Cependant, cela provient du serveur «spécial»:

[root@host~]# echo "abcdefghijklmnopqrstuvwxyz1234567890"|tr -d [a-z]
abcdefghijklmnpqrstuvwxyz1234567890

Comme vous pouvez le voir, la suppression de tous les caractères minuscules échoue. MAIS, il a supprimé la lettre «o»

La partie intéressante est les deux exemples suivants, qui n'ont aucun sens pour moi:

[root@host~]# echo "abcdefghijklmnopqrstuvwxyz1234567890"|tr -d [a-n]
opqrstuvwxyz1234567890
[root@host~]# echo "abcdefghijklmnopqrstuvwxyz1234567890"|tr -d [a-o]
abcdefghijklmnpqrstuvwxyz1234567890
[root@host~]#

(encore une fois, le «o» est supprimé dans le dernier exemple)

Quelqu'un a-t-il une idée de ce qui se passe ici? Je ne peux pas reproduire sur une autre boîte Linux que j'utilise.

Chris
la source
5
Liées tangentiellement: les trplages sont écrites sans les entourer [...]. Ainsi tr -d '[a-z]'tuera a-z, et aussi des personnages [et ]. Utilisez tr -d a-zpour tuer seulement des lettres a-z.
Satō Katsura

Réponses:

24

vous avez un fichier nommé odans le répertoire courant

foo> ls
foo> echo "abcdefghijklmnopqrstuvwxyz1234567890"|tr -d [a-z]
1234567890
foo> touch o
foo> echo "abcdefghijklmnopqrstuvwxyz1234567890"|tr -d [a-z]
abcdefghijklmnpqrstuvwxyz1234567890

shell étendra la [a-z]chaîne si une correspondance est trouvée.

C'est ce qu'on appelle l'extension du nom de chemin, selon man bash

Extension du nom de chemin
Après le fractionnement des mots, à moins que l'option -f n'ait été définie, bash recherche dans chaque mot les caractères *,? Et [. ... (...)

bash effectuera l'expansion.

[...] Correspond à l'un des caractères inclus.

Archemar
la source
@Chris Vous pouvez vérifier l'expansion du shell en utilisant par exemple echo: touch o ; echo tr -d [a-z]donne ceci:tr -d o
pabouk
8

Qu'est-ce qui se passe

Le shell (bash) voit l'argument [a-z]. C'est un motif générique (un glob ), qui correspond à n'importe quelle lettre minuscule¹. Par conséquent, le shell recherche un nom de fichier correspondant à ce modèle. Il y a trois cas:

  • Aucun fichier du répertoire en cours ne porte un nom composé d'une seule lettre minuscule. Ensuite, le shell laisse le modèle générique inchangé et trvoit les arguments -det [a-z]. C'est ce qui se passe sur la plupart de vos machines.
  • Un fichier unique dans le répertoire en cours a un nom qui est une seule lettre minuscule. Ensuite, le shell étend le modèle à ce nom de fichier et trvoit les arguments -det le nom de fichier. Cela se produit sur le serveur, et le fichier correspondant est appelé ocar nous pouvons voir que trla lettre a été supprimée o.
  • Deux fichiers ou plus dans le répertoire en cours ont un nom qui est une seule lettre minuscule. Ensuite, le shell étend le modèle à la liste des noms de fichiers correspondants et trvoit au moins trois arguments: -det les noms de fichiers. Comme trattend un seul argument après -d, il va se plaindre.

Ce que tu aurais dû faire

S'il y a des caractères spéciaux dans l'argument d'une commande, vous devez les échapper. Mettez l'argument entre guillemets simples '…'(c'est le moyen le plus simple, il y en a d'autres). À l'intérieur des guillemets simples, tous les personnages se distinguent, à l'exception de la guillemet simple lui-même. S'il y a une seule citation à l'intérieur de l'argument, remplacez-la par'\'' .

tr -d '[a-z]'

Notez cependant que ce n'est probablement pas ce que vous vouliez dire! Cela indique trde supprimer les lettres minuscules et les crochets. Il est équivalent à tr -d ']a-z[', tr '[]a-z'etc. Pour supprimer des lettres minuscules, utilisez

tr -d a-z

L'argument de trest un jeu de caractères. Vous mettez des crochets autour d'un jeu de caractères dans une expression régulière ou un motif générique pour indiquer qu'il s'agit d'un jeu de caractères. Mais trfonctionne sur un seul personnage à la fois. Ses arguments de ligne de commande sont ce que vous mettriez entre crochets .

Vous avez besoin de crochets pour indiquer les classes de caractères . Dans une expression régulière, vous utilisez des crochets entre crochets pour indiquer une classe de caractères, par exemple, [[:lower:]]*correspond à n'importe quel nombre de lettres minuscules, [[:lower:]_]*correspond à n'importe quel nombre de lettres minuscules et de soulignements. Dans l'argument de tr, vous avez besoin de l'ensemble sans ses crochets, donc tr -d '[:lower:]'supprime les lettres minuscules, tr -d '[:lower:]_'supprime les lettres minuscules et les traits de soulignement, etc.

¹ Dans certains pays, il peut correspondre à d'autres caractères .

Gilles 'SO- arrête d'être méchant'
la source
1
Notez que sur Solaris 10 (et d'autres anciens Unités basées sur SysV), vous avez besoin tr -d '[a-z]'de /usr/bin/tr. Avec /usr/xpg4/bin/tr, tr -d a-zfonctionne mais tr -d '[a-z]'ne supprime [ni ].
Stéphane Chazelas
1
/usr/xpg4/bin/tr -d '[a-z]'pas de suppression [ni de ]correction apparente dans Solaris 11.
Stéphane Chazelas