web-dev-qa-db-fra.com

Le caractère générique Bash star * produit-il toujours une liste triée (ascendante)?

J'ai un répertoire rempli de fichiers avec des noms comme logXX où XX est un nombre hexadécimal majuscule à deux caractères, avec un zéro, tel que:

log00
log01
log02
...
log0A
log0B
log0C
...
log4E
log4F
log50
...

Généralement, il y aura moins de 20 ou 30 fichiers au total. La date et l'heure sur mon système particulier ne sont pas fiables (un système embarqué sans source fiable NTP ou sources GPS). Cependant, les noms de fichiers incrémenteront de manière fiable comme indiqué ci-dessus.

Je souhaite grep à travers tous les fichiers pour l'entrée de journal la plus récente d'un certain type, j'espérais cat les fichiers ensemble tels que ...

cat /tmp/logs/log* | grep 'WARNING 07 -' | tail -n1

Cependant, il m'est venu à l'esprit que différentes versions de bash ou sh ou zsh etc. pourraient avoir des idées différentes sur la façon dont le * est développé.

Le man bash la page ne dit pas si l'extension de * serait une liste alphabétique clairement ascendante des noms de fichiers correspondants. Il semble augmenter chaque fois que je l'ai essayé sur tous les systèmes dont je dispose - mais est-ce un comportement DÉFINI ou juste spécifique à l'implémentation?

En d'autres termes, puis-je absolument compter sur cat /tmp/logs/log* pour concaténer tous mes fichiers journaux dans l'ordre alphabétique?

54
Wossname

Dans tous les shells, les globes sont triés par défaut. Ils étaient déjà par l'assistant /etc/glob appelé par Ken Thompson's Shell pour étendre les globes dans la première version d'Unix au début des années 70 (et qui a donné leur nom aux globs).

Pour sh, POSIX exige qu'ils soient triés au moyen de strcoll(), c'est-à-dire en utilisant l'ordre de tri dans les paramètres régionaux de l'utilisateur, comme pour ls bien que certains le fassent encore via strcmp(), basée uniquement sur les valeurs d'octets.

$ dash -c 'echo *'
Log01B log-0D log00 log01 log02 log0A log0B log0C log4E log4F log50 log① log② lóg01
$ bash -c 'echo *'
log① log② log00 log01 lóg01 Log01B log02 log0A log0B log0C log-0D log4E log4F log50
$ zsh -c 'echo *'
log① log② log00 log01 lóg01 Log01B log02 log0A log0B log0C log-0D log4E log4F log50
$ ls
log②  log①  log00  log01  lóg01  Log01B  log02  log0A  log0B  log0C  log-0D  log4E  log4F  log50
$ ls | sort
log②
log①
log00
log01
lóg01
Log01B
log02
log0A
log0B
log0C
log-0D
log4E
log4F
log50

Vous pouvez remarquer ci-dessus que pour les shells qui font un tri basé sur les paramètres régionaux, ici sur un système GNU avec un paramètre régional en_GB.UTF-8, Le - Dans les noms de fichiers est ignoré pour le tri (la plupart des caractères de ponctuation le feraient). Le ó est trié d'une manière plus attendue (au moins pour les Britanniques), et la casse est ignorée (sauf lorsqu'il s'agit de décider des liens).

Cependant, vous remarquerez des incohérences pour log① log②. C'est parce que l'ordre de tri de ① et ② n'est pas défini dans GNU locales (actuellement; avec un peu de chance, il sera corrigé un jour). Ils trient la même chose, donc vous obtenez des résultats aléatoires.

La modification des paramètres régionaux affectera l'ordre de tri. Vous pouvez définir les paramètres régionaux sur C pour obtenir un tri de type strcmp():

$ bash -c 'echo *'
log① log② log00 log01 lóg01 Log01B log02 log0.2 log0A log0B log0C log-0D log4E log4F log50
$ bash -c 'LC_ALL=C; echo *'
Log01B log-0D log0.2 log00 log01 log02 log0A log0B log0C log4E log4F log50 log① log② lóg01

Notez que certains paramètres régionaux peuvent provoquer des confusions même pour les chaînes tout-alnum tout ASCII. Comme ceux tchèques (sur les systèmes GNU au moins) où ch est un élément d'assemblage qui trie après h:

$ LC_ALL=cs_CZ.UTF-8 bash -c 'echo *'
log0Ah log0Bh log0Dh log0Ch

Ou, comme l'a souligné @ninjalj, même les plus étranges dans les régions hongroises:

$ LC_ALL=hu_HU.UTF-8 bash -c 'echo *'
logX LOGx LOGX logZ LOGz LOGZ logY LOGY LOGy

Dans zsh, vous pouvez choisir le tri avec qualificatifs globaux . Par exemple:

echo *(om) # to sort by modification time
echo *(oL) # to sort by size
echo *(On) # for a *reverse* sort by name
echo *(o+myfunction) # sort using a user-defined function
echo *(N)  # to NOT sort
echo *(n)  # sort by name, but numerically, and so on.

Le tri numérique de echo *(n) peut également être activé globalement avec l'option numericglobsort:

$ zsh -c 'echo *'
log① log② log00 log01 lóg01 Log01B log02 log0.2 log0A log0B log0C log-0D log4E log4F log50
$ zsh -o numericglobsort -c 'echo *'
log① log② log00 lóg01 Log01B log0.2 log0A log0B log0C log01 log02 log-0D log4E log4F log50

Si vous (comme j'étais) êtes confus par cet ordre dans cette instance particulière (ici en utilisant mes paramètres régionaux britanniques), voir ici pour plus de détails.

53

La page de manuel de bash spécifie:

Expansion du nom de chemin

Après le fractionnement de Word, sauf si le -f option a été définie, bash recherche dans chaque mot les caractères *, ?, et [. Si l'un de ces caractères apparaît, le mot est considéré comme un modèle et remplacé par une liste alphabétique de noms de fichiers correspondant au modèle […].

38
user4556274

À moins que vous ne déclenchiez des options Shell très spécifiques dans certains shells, la sortie est garantie d'être la même.

L'ordre est spécifié dans la norme POSIX :

Si le modèle correspond à des noms de fichiers ou de chemins existants, le modèle doit être remplacé par ces noms de fichiers et de chemins, triés selon la séquence de classement en vigueur dans les paramètres régionaux actuels . Si cette séquence de classement n'a pas un ordre total de tous les caractères (voir XBD LC_COLLATE), tous les noms de fichiers ou les chemins qui se classent également doivent être comparés octet par octet en utilisant la séquence de classement pour les paramètres régionaux POSIX.

Voir aussi LC_COLLATE Category in the POSIX Locale , qui en bref dit que si LC_COLLATE=C, puis les choses sont ordonnées dans l'ordre ASCII.


Le manuel bash mentionne

LC_COLLATE

Cette variable détermine l'ordre de classement utilisé lors du tri des résultats de l'extension de nom de chemin et détermine le comportement des expressions de plage, des classes d'équivalence et des séquences de classement dans l'expansion de nom de chemin et la correspondance de modèle.

ksh93 et zsh ont un libellé similaire, ce qui m'amène à croire qu'ils suivent la norme POSIX à cet égard.

D'autres shells, comme pdksh et dash ne disent rien sur le tri des noms de fichiers résultant de la globalisation des noms de fichiers. Je suis tenté de croire que cela signifie qu'ils adhèrent toujours au même standard, au moins lors de l'utilisation des paramètres régionaux POSIX. D'après mon expérience, je n'ai pas rencontré de shell qui fasse un tri ouvertement "étrange" des noms de fichiers ASCII.

30
Kusalananda

Si l'objectif principal est de trier les fichiers d'entrée en fonction de leur âge, le plus ancien en premier, vous pouvez écrire

(cd /tmp/logs; cat `ls -rt log*`) | grep whatever

Et si des journaux tournés et compressés sont également impliqués:

(cd /tmp/logs; zcat -f `ls -rt log*`) | grep whatever
1
sultansofswing