web-dev-qa-db-fra.com

Pourquoi * pas * analyser `ls` (et que faire à la place)?

Je vois constamment des réponses citant ce lien indiquant définitivement "Ne pas analyser ls!" Cela me dérange pour deux raisons:

  1. Il semble que les informations contenues dans ce lien aient été acceptées en gros avec peu de questions, bien que je puisse relever au moins quelques erreurs de lecture occasionnelle.

  2. Il semble également que les problèmes évoqués dans ce lien n'aient suscité aucun désir de trouver une solution.

Du premier paragraphe:

... quand vous demandez [ls] pour une liste de fichiers, il y a un énorme problème: Unix autorise presque tous les caractères d'un nom de fichier, y compris les espaces, les sauts de ligne, les virgules, les symboles de canal et pratiquement tout ce que vous essayez d'utiliser comme délimiteur sauf NUL. ... ls sépare les noms de fichiers par des retours à la ligne. C'est très bien jusqu'à ce que vous ayez un fichier avec une nouvelle ligne dans son nom. Et comme je ne connais aucune implémentation de ls qui vous permette de terminer les noms de fichiers avec des caractères NUL au lieu de nouvelles lignes, cela ne nous permet pas d'obtenir une liste de noms de fichiers en toute sécurité avec ls.

Bummer, non? Comment jamais pouvons-nous gérer un ensemble de données répertorié terminé par une nouvelle ligne pour les données qui pourraient contenir des nouvelles lignes? Eh bien, si les gens qui répondaient aux questions sur ce site Web ne faisaient pas ce genre de choses quotidiennement, je pourrais penser que nous avons eu des ennuis.

La vérité est cependant que la plupart des implémentations ls fournissent en fait une API très simple pour analyser leur sortie et nous l'avons tous fait tout le long sans même le réaliser. Non seulement vous pouvez terminer un nom de fichier avec null, mais vous pouvez également en commencer un avec null ou avec toute autre chaîne arbitraire que vous souhaitez. De plus, vous pouvez affecter ces chaînes arbitraires par type de fichier . Veuillez considérer:

LS_COLORS='lc=\0:rc=:ec=\0\0\0:fi=:di=:' ls -l --color=always | cat -A
total 4$
drwxr-xr-x 1 mikeserv mikeserv 0 Jul 10 01:05 ^@^@^@^@dir^@^@^@/$
-rw-r--r-- 1 mikeserv mikeserv 4 Jul 10 02:18 ^@file1^@^@^@$
-rw-r--r-- 1 mikeserv mikeserv 0 Jul 10 01:08 ^@file2^@^@^@$
-rw-r--r-- 1 mikeserv mikeserv 0 Jul 10 02:27 ^@new$
line$
file^@^@^@$
^@

Voir this pour en savoir plus.

Maintenant, c'est la prochaine partie de cet article qui me fait vraiment réfléchir:

$ ls -l
total 8
-rw-r-----  1 lhunath  lhunath  19 Mar 27 10:47 a
-rw-r-----  1 lhunath  lhunath   0 Mar 27 10:47 a?newline
-rw-r-----  1 lhunath  lhunath   0 Mar 27 10:47 a space

Le problème est qu'à partir de la sortie de ls, ni vous ni l'ordinateur ne pouvez dire quelles parties de celui-ci constituent un nom de fichier. Est-ce chaque mot? Non. C'est chaque ligne? Non. Il n'y a pas de bonne réponse à cette question autre que: vous ne pouvez pas le dire.

Notez également comment ls brouille parfois vos données de nom de fichier (dans notre cas, il a transformé le \n caractère entre les mots "a" et "nouvelle ligne" dans un ? point d'interrogation ...

...

Si vous voulez simplement parcourir tous les fichiers du répertoire courant, utilisez une boucle for et un glob:

for f in *; do
    [[ -e $f ]] || continue
    ...
done

L'auteur l'appelle garbling noms de fichiers lorsque ls renvoie une liste de noms de fichiers contenant des globes Shell et puis recommande d'utiliser un glob Shell pour récupérer une liste de fichiers!

Considérer ce qui suit:

printf 'touch ./"%b"\n' "file\nname" "f i l e n a m e" |
    . /dev/stdin
ls -1q

f i l e n a m e  
file?name

IFS="
" ; printf "'%s'\n" $(ls -1q)

'f i l e n a m e'
'file
name'

POSIX définit le -1 et -qls opérandes donc:

-q - Force chaque instance de caractères de nom de fichier non imprimables et <tab>s à écrire comme point d'interrogation ('?' ) personnage. Les implémentations peuvent fournir cette option par défaut si la sortie est vers un périphérique terminal.

-1 - (Le chiffre numérique un.) Force la sortie à une entrée par ligne.

Globbing n'est pas sans problèmes - ? correspond à tout caractère donc plusieurs correspondances ? les résultats dans une liste correspondront plusieurs fois au même fichier. C'est facile à gérer.

Bien que la façon de faire cette chose ne soit pas la question - cela ne prend pas grand-chose après tout et est démontré ci-dessous - j'étais intéressé par pourquoi pas . À mon avis, la meilleure réponse à cette question a été acceptée. Je vous suggère d'essayer de vous concentrer plus souvent à dire aux gens ce qu'ils peuvent faire que ce qu'ils ne peuvent pas. Vous ' il est beaucoup moins probable, comme je pense, de se tromper au moins.

Mais pourquoi même essayer? Certes, ma principale motivation était que les autres ne cessaient de me dire que je ne pouvais pas. Je sais très bien que la sortie de ls est aussi régulière et prévisible que vous pourriez le souhaiter tant que vous savez quoi chercher. La désinformation me dérange plus que la plupart des choses.

La vérité est, cependant, à l'exception notable des réponses de Patrick et de Wumpus Q. Wumbley (malgré la poignée impressionnante de ce dernier) , je considère la plupart des informations dans les réponses ici comme la plupart du temps correct - un glob Shell est à la fois plus simple à utiliser et généralement plus efficace lorsqu'il s'agit de rechercher dans le répertoire courant que l'analyse de ls. Ils ne sont cependant pas, du moins à mon avis, une raison suffisante pour justifier la propagation de la désinformation citée dans l'article ci-dessus, ni une justification acceptable pour " ne jamais analyser ls. "

Veuillez noter que les résultats incohérents de la réponse de Patrick sont principalement le fait qu'il utilise zsh puis bash. zsh - par défaut - ne divise pas Word $(commande substituée) résulte de manière portable. Donc, quand il demande où sont passés les autres fichiers? la réponse à cette question est votre Shell les a mangés. C'est pourquoi vous devez définir le SH_Word_SPLIT variable lors de l'utilisation de zsh et du traitement du code Shell portable. Je considère que son omission de le noter dans sa réponse est terriblement trompeuse.

La réponse de Wumpus ne calcule pas pour moi - dans un contexte de liste, le ? le caractère est un glob de Shell. Je ne sais pas comment dire cela autrement.

Afin de gérer un cas de résultats multiples, vous devez restreindre la gourmandise du glob. Ce qui suit va simplement créer une base de test de noms de fichiers affreux et l'afficher pour vous:

{ printf %b $(printf \\%04o `seq 0 127`) |
sed "/[^[-b]*/s///g
        s/\(.\)\(.\)/touch '?\v\2' '\1\t\2' '\1\n\2'\n/g" |
. /dev/stdin

echo '`ls` ?QUOTED `-m` COMMA,SEP'
ls -qm
echo ; echo 'NOW LITERAL - COMMA,SEP'
ls -m | cat
( set -- * ; printf "\nFILE COUNT: %s\n" $# )
}

PRODUCTION

`ls` ?QUOTED `-m` COMMA,SEP
??\, ??^, ??`, ??b, [?\, [?\, ]?^, ]?^, _?`, _?`, a?b, a?b

NOW LITERAL - COMMA,SEP
?
 \, ?
     ^, ?
         `, ?
             b, [       \, [
\, ]    ^, ]
^, _    `, _
`, a    b, a
b

FILE COUNT: 12

Maintenant, je vais protéger chaque personnage qui n'est pas un /slash, -dash, :colon, ou caractère alphanumérique dans un glob Shell puis sort -u la liste des résultats uniques. C'est sûr car ls a déjà mis de côté tous les caractères non imprimables pour nous. Regarder:

for f in $(
        ls -1q |
        sed 's|[^-:/[:alnum:]]|[!-\\:[:alnum:]]|g' |
        sort -u | {
                echo 'PRE-GLOB:' >&2
                tee /dev/fd/2
                printf '\nPOST-GLOB:\n' >&2
        }
) ; do
        printf "FILE #$((i=i+1)): '%s'\n" "$f"
done

PRODUCTION:

PRE-GLOB:
[!-\:[:alnum:]][!-\:[:alnum:]][!-\:[:alnum:]]
[!-\:[:alnum:]][!-\:[:alnum:]]b
a[!-\:[:alnum:]]b

POST-GLOB:
FILE #1: '?
           \'
FILE #2: '?
           ^'
FILE #3: '?
           `'
FILE #4: '[     \'
FILE #5: '[
\'
FILE #6: ']     ^'
FILE #7: ']
^'
FILE #8: '_     `'
FILE #9: '_
`'
FILE #10: '?
            b'
FILE #11: 'a    b'
FILE #12: 'a
b'

Ci-dessous, j'aborde à nouveau le problème mais j'utilise une méthodologie différente. N'oubliez pas qu'en plus de \0null - le / ASCII est le seul octet interdit dans un nom de chemin. Je mets des globes de côté ici et je combine plutôt le POSIX spécifié -d option pour ls et également spécifié par POSIX -exec $cmd {} + construction pour find. Parce que find n'en émettra que naturellement _ / en séquence, ce qui suit fournit facilement une liste de fichiers récursive et délimitée de manière fiable, y compris toutes les informations dentry pour chaque entrée. Imaginez ce que vous pourriez faire avec quelque chose comme ça:

#v#note: to do this fully portably substitute an actual newline \#v#
#v#for 'n' for the first sed invocation#v#
cd ..
find ././ -exec ls -1ldin {} + |
sed -e '\| *\./\./|{s||\n.///|;i///' -e \} |
sed 'N;s|\(\n\)///|///\1|;$s|$|///|;P;D'

###OUTPUT

152398 drwxr-xr-x 1 1000 1000        72 Jun 24 14:49
.///testls///

152399 -rw-r--r-- 1 1000 1000         0 Jun 24 14:49
.///testls/?
            \///

152402 -rw-r--r-- 1 1000 1000         0 Jun 24 14:49
.///testls/?
            ^///

152405 -rw-r--r-- 1 1000 1000         0 Jun 24 14:49
.///testls/?
        `///
...

ls -i peut être très utile - en particulier lorsque l'unicité des résultats est en question.

ls -1iq | 
sed '/ .*/s///;s/^/-inum /;$!s/$/ -o /' | 
tr -d '\n' | 
xargs find

Ce ne sont que les moyens les plus portables auxquels je peux penser. Avec GNU ls vous pourriez faire:

ls --quoting-style=Word

Et enfin, voici une méthode beaucoup plus simple d'analyse ls que j'utilise assez souvent lorsque j'ai besoin de numéros d'inode:

ls -1iq | grep -o '^ *[0-9]*'

Cela renvoie simplement les numéros d'inode - qui est une autre option spécifiée POSIX pratique.

221
mikeserv

Je ne suis pas du tout convaincu de cela, mais supposons à titre d'argument que vous pourriez , si vous êtes prêt à faire assez d'efforts, analyser la sortie de ls de manière fiable, même face à un "adversaire" - quelqu'un qui connaît le code que vous avez écrit et choisit délibérément des noms de fichiers conçus pour le casser.

Même si vous pouviez le faire, ce serait toujours une mauvaise idée .

Bourne Shell n'est pas un bon langage. Il ne doit pas être utilisé pour quelque chose de compliqué, sauf si la portabilité extrême est plus importante que tout autre facteur (par exemple autoconf).

Je prétends que si vous êtes confronté à un problème où l'analyse de la sortie de ls semble être le chemin de moindre résistance pour un script Shell, c'est une indication forte que quoi que vous fassiez est trop compliqué pour Shell et vous devriez réécrire le tout en Perl ou Python. Voici votre dernier programme en Python:

import os, sys
for subdir, dirs, files in os.walk("."):
    for f in dirs + files:
      ino = os.lstat(os.path.join(subdir, f)).st_ino
      sys.stdout.write("%d %s %s\n" % (ino, subdir, f))

Cela n'a aucun problème avec les caractères inhabituels dans les noms de fichiers - la sortie est ambiguë de la même manière que la sortie de ls est ambiguë, mais cela n'aurait pas d'importance dans un "vrai" programme (par opposition à une démo comme celle-ci), qui utiliserait directement le résultat de os.path.join(subdir, f).

Tout aussi important, et contrairement à ce que vous avez écrit, cela aura toujours un sens dans six mois, et il sera facile de le modifier lorsque vous en aurez besoin pour faire quelque chose de légèrement différent. À titre d'illustration, supposons que vous découvriez la nécessité d'exclure les fichiers dot et les sauvegardes de l'éditeur, et de tout traiter par ordre alphabétique par nom de base:

import os, sys
filelist = []
for subdir, dirs, files in os.walk("."):
    for f in dirs + files:
        if f[0] == '.' or f[-1] == '~': continue
        lstat = os.lstat(os.path.join(subdir, f))
        filelist.append((f, subdir, lstat.st_ino))

filelist.sort(key = lambda x: x[0])
for f, subdir, ino in filelist: 
   sys.stdout.write("%d %s %s\n" % (ino, subdir, f))
191
zwol

Ce lien est souvent référencé car les informations sont tout à fait exactes et existent depuis très longtemps.


ls remplace les caractères non imprimables par des caractères globaux oui, mais ces caractères ne sont pas dans le nom de fichier réel. Pourquoi est-ce important? 2 raisons:

  1. Si vous passez ce nom de fichier à un programme, ce nom de fichier n'existe pas réellement. Il faudrait étendre le glob pour obtenir le vrai nom de fichier.
  2. Le fichier glob peut correspondre à plusieurs fichiers.

Par exemple:

$ touch a$'\t'b
$ touch a$'\n'b
$ ls -1
a?b
a?b

Remarquez comment nous avons 2 fichiers qui se ressemblent exactement. Comment allez-vous les distinguer si les deux sont représentés comme a?b?


L'auteur appelle cela des noms de fichiers tronqués lorsque ls renvoie une liste de noms de fichiers contenant des globes Shell et recommande ensuite d'utiliser un glob Shell pour récupérer une liste de fichiers!

Il y a une différence ici. Lorsque vous récupérez un glob, comme indiqué, ce glob peut correspondre à plusieurs fichiers. Cependant, lorsque vous parcourez les résultats correspondant à un glob, vous récupérez le fichier exact, pas un glob.

Par exemple:

$ for file in *; do printf '%s' "$file" | xxd; done
0000000: 6109 62                                  a.b
0000000: 610a 62                                  a.b

Remarquez comment la sortie xxd montre que $file contenait les caractères bruts \t et \n, ne pas ?.

Si vous utilisez ls, vous obtenez ceci à la place:

for file in $(ls -1q); do printf '%s' "$file" | xxd; done
0000000: 613f 62                                  a?b
0000000: 613f 62                                  a?b

"Je vais quand même répéter, pourquoi ne pas utiliser ls?"

Votre exemple que vous avez donné ne fonctionne pas vraiment. Il semble que cela fonctionne, mais ce n'est pas le cas.

Je fais référence à ceci:

 for f in $(ls -1q | tr " " "?") ; do [ -f "$f" ] && echo "./$f" ; done

J'ai créé un répertoire avec un tas de noms de fichiers:

$ for file in *; do printf '%s' "$file" | xxd; done
0000000: 6120 62                                  a b
0000000: 6120 2062                                a  b
0000000: 61e2 8082 62                             a...b
0000000: 61e2 8083 62                             a...b
0000000: 6109 62                                  a.b
0000000: 610a 62                                  a.b

Lorsque j'exécute votre code, j'obtiens ceci:

$ for f in $(ls -1q | tr " " "?") ; do [ -f "$f" ] && echo "./$f" ; done
./a b
./a b

Où sont passés les autres fichiers?

Essayons ceci à la place:

$ for f in $(ls -1q | tr " " "?") ; do stat --format='%n' "./$f"; done
stat: cannot stat ‘./a?b’: No such file or directory
stat: cannot stat ‘./a??b’: No such file or directory
./a b
./a b
stat: cannot stat ‘./a?b’: No such file or directory
stat: cannot stat ‘./a?b’: No such file or directory

Permet maintenant d'utiliser un glob réel:

$ for f in *; do stat --format='%n' "./$f"; done
./a b
./a  b
./a b
./a b
./a b
./a
b

Avec bash

L'exemple ci-dessus était avec mon Shell normal, zsh. Lorsque je répète la procédure avec bash, j'obtiens un autre ensemble de résultats complètement différent avec votre exemple:

Même ensemble de fichiers:

$ for file in *; do printf '%s' "$file" | xxd; done
0000000: 6120 62                                  a b
0000000: 6120 2062                                a  b
0000000: 61e2 8082 62                             a...b
0000000: 61e2 8083 62                             a...b
0000000: 6109 62                                  a.b
0000000: 610a 62                                  a.b

Des résultats radicalement différents avec votre code:

for f in $(ls -1q | tr " " "?") ; do stat --format='%n' "./$f"; done
./a b
./a b
./a b
./a b
./a
b
./a  b
./a b
./a b
./a b
./a b
./a b
./a b
./a
b
./a b
./a b
./a b
./a b
./a
b

Avec un glob de Shell, cela fonctionne parfaitement bien:

$ for f in *; do stat --format='%n' "./$f"; done
./a b
./a  b
./a b
./a b
./a b
./a
b

La raison pour laquelle bash se comporte de cette manière remonte à l'un des points que j'ai fait valoir au début de la réponse: "Le fichier glob peut correspondre à plusieurs fichiers".

ls renvoie le même glob (a?b) pour plusieurs fichiers, donc chaque fois que nous développons ce glob, nous obtenons chaque fichier qui lui correspond.


Comment recréer la liste des fichiers que j'utilisais:

touch 'a b' 'a  b' a$'\xe2\x80\x82'b a$'\xe2\x80\x83'b a$'\t'b a$'\n'b

Les codes hexadécimaux sont des caractères NBT UTF-8.

184
Patrick

Essayons de simplifier un peu:

$ touch a$'\n'b a$'\t'b 'a b'
$ ls
a b  a?b  a?b
$ IFS="
"
$ set -- $(ls -1q | uniq)
$ echo "Total files in Shell array: $#"
Total files in Shell array: 4

Voir? C'est déjà mal ici. Il y a 3 fichiers mais bash en rapporte 4. Ceci est dû au fait que set reçoit les globes générés par ls qui sont développés par le Shell avant d'être passés à set. C'est pourquoi vous obtenez:

$ for x ; do
>     printf 'File #%d: %s\n' $((i=$i+1)) "$x"
> done
File #1: a b
File #2: a b
File #3: a    b
File #4: a
b

Ou, si vous préférez:

$ printf ./%s\\0 "$@" |
> od -A n -c -w1 |
> sed -n '/ \{1,3\}/s///;H
> /\\0/{g;s///;s/\n//gp;s/.*//;h}'
./a b
./a b
./a\tb
./a\nb

Ce qui précède a été exécuté sur bash 4.2.45.

54
terdon

La sortie de ls -q n'est pas du tout un glob. Il utilise ? pour signifier "Il y a ici un caractère qui ne peut pas être affiché directement". Les globes utilisent ? pour signifier "Tout caractère est autorisé ici".

Les globes ont d'autres caractères spéciaux (* et [] au moins, et à l'intérieur du [] paire il y en a plus). Aucun de ceux-ci n'est échappé par ls -q.

$ touch x '[x]'
$ ls -1q
[x]
x

Si vous traitez le ls -1q sortie il y a un ensemble de globes et développez-les, non seulement vous obtiendrez x deux fois, vous manquerez [x] complètement. En tant que glob, il ne correspond pas à lui-même en tant que chaîne.

ls -q est destiné à sauver vos yeux et/ou votre terminal des personnages fous, pas à produire quelque chose que vous pouvez renvoyer au Shell.

52
user41515

La réponse est simple: les cas particuliers de ls que vous devez gérer l'emportent sur tout avantage possible. Ces cas particuliers peuvent être évités si vous n'analysez pas la sortie ls.

Le mantra ici est ne jamais faire confiance au système de fichiers utilisateur (l'équivalent de ne jamais faire confiance à l'entrée utilisateur ). S'il existe une méthode qui fonctionnera toujours, avec 100% de certitude, ce devrait être la méthode que vous préférez même si ls fait de même mais avec moins de certitude. Je n'entrerai pas dans les détails techniques, car ceux-ci étaient couverts par terdon et Patrick de manière approfondie. Je sais qu'en raison des risques d'utiliser ls dans une transaction importante (et peut-être chère) où mon travail/prestige est en jeu, je préférerai toute solution qui n'a pas un degré d'incertitude si elle peut être évité.

Je sais que certaines personnes préfèrent n certain risque par rapport à la certitude , mais j'ai déposé un rapport de bogue .

41
Braiam

La raison pour laquelle les gens disent jamais faire quelque chose n'est pas nécessairement parce que cela ne peut absolument pas être fait correctement. Nous pouvons peut-être le faire, mais cela peut être plus compliqué, moins efficace en termes d'espace ou de temps. Par exemple, il serait parfaitement correct de dire "Ne jamais construire un grand backend de commerce électronique dans un assemblage x86".

Passons maintenant au problème en question: comme vous l'avez démontré, vous pouvez créer une solution qui analyse ls et donne le bon résultat - donc l'exactitude n'est pas un problème.

Est-ce plus compliqué? Oui, mais nous pouvons cacher cela derrière une fonction d'assistance.

Alors maintenant à l'efficacité:

Économie d'espace: votre solution s'appuie sur uniq pour filtrer les doublons, par conséquent, nous ne pouvons pas générer les résultats paresseusement. Donc, soit O(1) vs O(n) ou les deux ont O(n).

Gain de temps: dans le meilleur des cas, uniq utilise une approche hashmap, nous avons donc toujours un algorithme O(n) dans le nombre d'éléments procuré, probablement bien que ce soit O(n log n).

Maintenant, le vrai problème: bien que votre algorithme ne soit toujours pas trop mauvais, j'ai fait très attention à utiliser les éléments procurés et pas les éléments pour n. Parce que cela fait une grande différence. Disons que vous avez un fichier \n\n qui se traduira par un glob pour ?? donc faites correspondre chaque fichier de 2 caractères dans la liste. Curieusement, si vous avez un autre fichier \n\r qui se traduira également par ?? et aussi retourner les 2 fichiers de caractères .. voyez où cela va? Un comportement exponentiel au lieu d'un comportement linéaire peut certainement être qualifié de "comportement d'exécution pire". C'est la différence entre un algorithme pratique et celui sur lequel vous écrivez des articles dans des revues CS théoriques.

Tout le monde aime les exemples, non? Et c'est parti. Créez un dossier appelé "test" et utilisez ce script python dans le même répertoire où se trouve le dossier.

#!/usr/bin/env python3
import itertools
dir = "test/"
filename_length = 3
options = "\a\b\t\n\v\f\r"

for filename in itertools.product(options, repeat=filename_length):
        open(dir + ''.join(filename), "a").close()

La seule chose que cela fait est de générer tous les produits de longueur 3 pour 7 caractères. Les mathématiques au secondaire nous disent que cela devrait être 343 fichiers. Eh bien, cela devrait être très rapide à imprimer, alors voyons:

time for f in *; do stat --format='%n' "./$f" >/dev/null; done
real    0m0.508s
user    0m0.051s
sys 0m0.480s

Essayons maintenant votre première solution, car je ne peux vraiment pas obtenir cela

eval set -- $(ls -1qrR ././ | tr ' ' '?' |
sed -e '\|^\(\.\{,1\}\)/\.\(/.*\):|{' -e \
        's//\1\2/;\|/$|!s|.*|&/|;h;s/.*//;b}' -e \
        '/..*/!d;G;s/\(.*\)\n\(.*\)/\2\1/' -e \
        "s/'/'\\\''/g;s/.*/'&'/;s/?/'[\"?\$IFS\"]'/g" |
uniq)

chose ici pour travailler sur Linux mint 16 (ce qui, je pense, en dit long sur l'utilisabilité de cette méthode).

Quoi qu'il en soit, étant donné que ce qui précède ne filtre à peu près le résultat qu'après l'avoir obtenu, la solution antérieure devrait être au moins aussi rapide que la dernière (pas de trucs d'inode dans celui-ci, mais ceux-ci ne sont pas fiables, vous abandonnerez donc l'exactitude).

Alors maintenant combien de temps

time for f in $(ls -1q | tr " " "?") ; do stat --format='%n' "./$f" >/dev/null; done

prendre? Eh bien, je ne sais vraiment pas, cela prend un certain temps pour vérifier les noms de fichiers 343 ^ 343 - je vous le dirai après la mort thermique de l'univers.

32
Voo

Intention déclarée de l'OP adressée

préface et justification de la réponse originalemis à jour le 2015-05-18

mikeserv (l'OP) a déclaré dans la dernière mise à jour de sa question: "Je fais considère cela comme une honte bien que j'ai d'abord a posé cette question pour signaler une source de désinformation, et, malheureusement, la réponse la plus votée ici est en grande partie trompeuse. "

Bien, OK; Je pense que c'était plutôt dommage que j'ai passé tellement de temps à essayer de comprendre comment expliquer mon sens pour trouver ça alors que je relisais la question. Cette question a fini par "[générer] des discussions plutôt que des réponses" et a fini par peser ~ 18 Ko de texte (pour la seule question, juste pour être clair) ce qui serait long même pour un billet de blog.

Mais StackExchange n'est pas votre boîte à savon et ce n'est pas votre blog. Cependant, en fait, vous l'avez utilisé comme au moins un peu des deux. Les gens ont fini par consacrer beaucoup de temps à répondre à votre "To-Point-Out" au lieu de répondre aux vraies questions des gens. À ce stade, je signalerai la question comme ne convenant pas à notre format, étant donné que le PO a déclaré explicitement qu'il n'était même pas destiné à être une question du tout.

À ce stade, je ne sais pas si ma réponse a été ou non; probablement pas, mais il visait certaines de vos questions, et peut-être peut-être une réponse utile à quelqu'un d'autre; les débutants prennent courage, certains de ces "ne se transforment pas" en "font parfois" une fois que vous avez acquis de l'expérience. :)

En règle générale...

veuillez pardonner les bords rugueux restants; J'ai déjà passé beaucoup trop de temps là-dessus ... plutôt que de citer directement l'OP (comme prévu à l'origine), je vais essayer de résumer et de paraphraser.

[retravaillé en grande partie à partir de ma réponse originale]
après examen, je crois avoir mal lu l'accent que le PO mettait sur les questions auxquelles j'ai répondu; cependant, les points abordés étaient soulevé, et j'ai laissé les réponses en grande partie intactes car je pense qu'elles sont pertinentes et abordent des problèmes que j'ai vus soulevés dans d'autres contextes ainsi que des conseils aux débutants.

Le message d'origine demandait, de plusieurs manières, pourquoi divers articles donnaient des conseils tels que "Ne pas analyser ls sortie" ou "Vous ne devriez jamais analyser ls sortie", etc.

Ma résolution suggérée est que les instances de ce type de déclaration ne sont que des exemples d'un idiome, formulé de manières légèrement différentes, dans lesquelles un quantificateur absolu est associé à un impératif [par exemple, "ne [jamais] X" ", "[Vous devez] toujours Y", "[il ne faut] jamais Z"] pour former des déclarations destinées à être utilisées comme règles générales ou directives, en particulier lorsqu'elles sont données à ceux qui sont nouveaux sur un sujet, plutôt que d'être conçues comme des vérités absolues, la nonobstant forme de ces déclarations.

Lorsque vous commencez à apprendre de nouveaux sujets, et à moins que vous ne compreniez bien pourquoi vous pourriez avoir à faire autrement, c'est une bonne idée de simplement suivre les règles générales acceptées sans exception - sauf sous la direction de quelqu'un de plus expérimenté que vous-même. Avec l'augmentation des compétences et de l'expérience, vous devenez davantage en mesure de déterminer quand et si une règle s'applique dans une situation particulière. Une fois que vous atteignez un niveau d'expérience significatif, vous comprendrez probablement le raisonnement derrière la règle générale en premier lieu, et à ce stade, vous pouvez commencer à utiliser votre jugement pour savoir si et à quel niveau les raisons derrière la règle s'appliquent dans cette situation, ainsi que la question de savoir s'il existe peut-être des préoccupations.

Et c'est alors qu'un expert, peut-être, pourrait choisir de faire des choses en violation des "Règles". Mais cela n'en ferait pas moins "les règles".

Et, donc, au sujet en question: à mon avis, juste parce qu'un expert pourrait être en mesure de violer cette règle sans se faire complètement claquer, je ne vois aucun moyen que vous pourriez justifier de dire à un débutant que "parfois" c'est ok pour analyser la sortie ls, car: ce n'est pas. Ou, du moins, ce n'est certainement pas bon pour un débutant de le faire.

Vous placez toujours vos pions au centre; dans l'ouverture une pièce, un mouvement; château à la première occasion; chevaliers devant les évêques; un chevalier sur la jante est sombre; et assurez-vous toujours que vous pouvez voir votre calcul jusqu'à la fin! (Oups, désolé, fatigué, c'est pour les échecs StackExchange.)

Des règles, censées être brisées?

Lorsque vous lisez un article sur un sujet destiné aux débutants ou susceptible d'être lu par des débutants, vous verrez souvent des choses comme ceci:

  • "Vous ne devriez pas jamais faire X."
  • "Ne fais jamais Q!"
  • "Ne fais pas Z."
  • "On devrait toujours faire Y!"
  • "C, quoi qu'il arrive."

Bien que ces déclarations semblent certes énoncer des règles absolues et intemporelles, elles ne le sont pas; c'est plutôt une façon d'énoncer des règles générales [a.k.a. "directives", "règles de base", "les bases", etc.] qui est au moins sans doute une façon appropriée de les énoncer pour les débutants qui pourraient lire ces articles. Cependant, juste parce qu'elles sont énoncées comme absolues, les règles ne lient certainement pas les professionnels et les experts [qui étaient probablement ceux qui ont résumé ces règles en premier lieu, comme un moyen d'enregistrer et de transmettre les connaissances acquises au fur et à mesure qu'elles traitaient des problèmes récurrents. problèmes dans leur métier particulier.]

Ces règles ne vont certainement pas révéler comment un expert traiterait un problème complexe ou nuancé, dans lequel, disons, ces règles sont en conflit les unes avec les autres; ou dans lequel les préoccupations qui ont conduit à la règle en premier lieu ne s'appliquent tout simplement pas. Les experts n'ont pas peur (ou ne devraient pas avoir peur!) De simplement enfreindre les règles qu'ils savent ne pas avoir de sens dans une situation particulière. Les experts doivent constamment trouver un équilibre entre divers risques et préoccupations dans leur métier et doivent fréquemment utiliser leur jugement pour choisir de briser ce type de règles, avoir à équilibrer divers facteurs et ne pas pouvoir se fier uniquement à un tableau de règles à suivre. Prenons Goto comme exemple: il y a eu un long débat récurrent sur leur nocivité. (Ouais, ne pas jamais utiliser gotos. ;RÉ)

Une proposition modale

Une caractéristique étrange, au moins en anglais, et j'imagine dans de nombreuses autres langues, des règles générales, est qu'elles sont énoncées sous la même forme qu'une proposition modale, mais les experts dans un domaine sont prêts à donner une règle générale pour une situation, tout en sachant qu’ils enfreindront la règle le cas échéant. Il est donc clair que ces instructions ne sont pas censées être équivalentes aux mêmes instructions en logique modale.

C'est pourquoi je dis qu'ils doivent simplement être idiomatiques. Plutôt que d'être véritablement une situation "jamais" ou "toujours", ces règles servent généralement à codifier des directives générales qui tendent à être appropriées dans un large éventail de situations, et que, lorsque les débutants les suivent aveuglément, de meilleurs résultats que le débutant qui choisit de les affronter sans raison valable. Parfois, ils codifient des règles conduisant simplement à des résultats inférieurs aux normes plutôt qu'aux échecs purs et simples accompagnant des choix incorrects lorsqu'ils vont à l'encontre des règles.

Ainsi, les règles générales ne sont pas les propositions modales absolues qu'elles semblent être à la surface, mais plutôt un moyen raccourci de donner la règle avec un passe-partout standard implicite, quelque chose comme ce qui suit:

sauf si vous avez la possibilité de dire que cette directive est incorrecte dans un cas particulier et de vous prouver que vous avez raison, alors $ {RULE}

où, bien sûr, vous pouvez remplacer "ne jamais analyser ls sortie" à la place de $ {RULE}. :)

Oh oui! Quoi À propos Analyse ls Sortie?

Eh bien, étant donné tout cela ... je pense qu'il est assez clair que cette règle est bonne. Tout d'abord, la vraie règle doit être comprise comme idiomatique, comme expliqué ci-dessus ...

Mais en plus, ce n'est pas seulement que vous devez être très bon avec les scripts Shell pour savoir s'il peut être cassé, dans certains cas particuliers. C'est aussi qu'il faut autant d'habileté pour dire que vous l'avez compris mal lorsque vous essayez de le casser lors des tests! Et, je dis avec confiance qu'une très grande majorité de l'audience probable de tels articles (donnant des conseils comme "Ne pas analyser la sortie de ls!") ne peut pas faire ces choses, et ceux qui ont une telle compétence se rendront probablement compte qu'ils le découvrent d'eux-mêmes et ignorent de toute façon la règle.

Mais ... regardez simplement cette question, et comment même les gens qui ont probablement la compétence ont pensé que c'était un mauvais appel à le faire; et combien d'efforts l'auteur de la question a dépensé pour arriver au point du meilleur exemple actuel! Je vous garantis sur un problème aussi difficile que 99% des gens se tromperaient et avec potentiellement très de mauvais résultats! Même si la méthode retenue s'avère bonne; jusqu'à ce qu'elle (ou une autre) ls l'idée d'analyse soit adoptée par l'ensemble des informaticiens/développeurs, résiste à de nombreux tests (en particulier à l'épreuve du temps) et, finalement, parvient à passer à une "technique commune" statut, il est probable que beaucoup de gens pourraient l'essayer et se tromper ... avec des conséquences désastreuses.

Donc, je vais répéter une dernière fois ... que, surtout dans ce cas, ça c'est pourquoi "jamais parse ls output! "est décidément la manière à droite de le formuler.

[MISE À JOUR 2014-05-18: clarification du raisonnement pour la réponse (ci-dessus) pour répondre à un commentaire de l'OP; l'ajout suivant est en réponse aux ajouts du PO à la question d'hier]]

[MISE À JOUR 2014-11-10: en-têtes ajoutés et contenu réorganisé/refactorisé; et aussi: reformatage, reformulation, clarification et euh ... "concision-ifying" ... je voulais que ce soit simplement un nettoyage, même si cela s'est transformé en un peu de retravailler. je l'avais laissé dans un état désolé, j'ai donc principalement essayé de lui donner un peu d'ordre. j'ai senti qu'il était important de laisser en grande partie la première section intacte; donc seulement deux changements mineurs là-bas, redondants 'mais' supprimés, et 'cela' souligné.]

† À l'origine, je ne voulais cela que pour clarifier mon original; mais a décidé d'autres ajouts après réflexion

‡ voir https://unix.stackexchange.com/tour pour les directives sur les publications

26
shelleybutterfly

Est-il possible d'analyser la sortie de ls dans certains cas? Sûr. L'idée d'extraire une liste de numéros d'inode d'un répertoire est un bon exemple - si vous savez que ls de votre implémentation prend en charge -q, Et donc chaque fichier produira exactement une ligne de sortie, et tout ce dont vous avez besoin sont les numéros d'inode, les analyser à partir de la sortie de ls -Rai1q est certainement une solution possible. Bien sûr, si l'auteur n'avait jamais vu de conseils comme "Ne jamais analyser la sortie de ls" auparavant, il ne penserait probablement pas aux noms de fichiers contenant des nouvelles lignes et laisserait probablement le "q" en conséquence, et le le code serait subtilement brisé dans ce cas Edge - donc, même dans les cas où l'analyse de la sortie de ls est raisonnable, ce conseil est toujours utile.

Le point le plus large est que, lorsqu'un débutant dans les scripts Shell essaie d'avoir un script pour comprendre (par exemple) quel est le plus gros fichier dans un répertoire, ou quel est le fichier le plus récemment modifié dans un répertoire, son premier réflexe est d'analyser ls - compréhensible, car ls est l'une des premières commandes qu'un débutant apprend.

Malheureusement, cet instinct est faux et cette approche est brisée. Encore plus malheureusement, il est subtilement cassé - cela fonctionnera la plupart du temps, mais échouera dans les cas Edge qui pourraient peut-être être exploités par une personne connaissant le code.

Le débutant pourrait penser à ls -s | sort -n | tail -n 1 | awk '{print $2}' Comme un moyen d'obtenir le plus gros fichier d'un répertoire. Et cela fonctionne, jusqu'à ce que vous ayez un fichier avec un espace dans le nom.

OK, alors qu'en est-il de ls -s | sort -n | tail -n 1 | sed 's/[^ ]* *[0-9]* *//'? Fonctionne très bien jusqu'à ce que vous ayez un fichier avec une nouvelle ligne dans le nom.

L'ajout de -q Aux arguments de ls aide-t-il lorsqu'il y a une nouvelle ligne dans le nom de fichier? Cela pourrait ressembler à cela, jusqu'à ce que vous ayez 2 fichiers différents qui contiennent un caractère non imprimable au même endroit dans le nom de fichier, puis la sortie de ls ne vous permet pas de distinguer lequel était le plus grand . Pire, pour étendre le "?", Il a probablement recours à son eval de Shell - qui causera des problèmes s'il frappe un fichier nommé, par exemple,

foo`/tmp/malicious_script`bar

--quoting-style=Shell Aide-t-il (si votre ls le prend même en charge)? Non, affiche toujours? pour les caractères non imprimables, il est donc toujours ambigu de savoir laquelle des correspondances multiples était la plus importante. --quoting-style=literal? Non, pareil. --quoting-style=locale Ou --quoting-style=c Pourraient aider si vous avez juste besoin d'imprimer le nom du plus gros fichier sans ambiguïté, mais probablement pas si vous devez faire quelque chose avec le fichier par la suite - ce serait un tas de code pour annuler la citation et revenir au vrai nom de fichier afin de pouvoir le passer, disons, à gzip.

Et à la fin de tout ce travail, même si ce qu'il a est sûr et correct pour tous les noms de fichiers possibles, il est illisible et impossible à gérer, et aurait pu être fait beaucoup plus facilement, en toute sécurité et de manière lisible dans python ou Perl ou Rubis.

Ou même en utilisant d'autres outils Shell - du haut de ma tête, je pense que cela devrait faire l'affaire:

find . -type f -printf "%s %f\0" | sort -nz | awk 'BEGIN{RS="\0"} END{sub(/[0-9]* /, "", $0); print}'

Et devrait être au moins aussi portable que --quoting-style.

16
godlygeek