Je vois toujours des réponses citant ce lien indiquant définitivement "Ne pas analyser ls
!" Cela me dérange pour deux raisons:
Il semble que l’information contenue dans ce lien ait été acceptée en gros avec peu de questions, bien que je puisse au moins relever quelques erreurs de lecture occasionnelle.
Il semble également que les problèmes énoncés dans ce lien n’ont suscité aucun désir de trouver une solution.
Du premier paragraphe:
... lorsque vous demandez
[ls]
une liste de fichiers, le problème est énorme: Unix autorise presque tous les caractères d'un nom de fichier, y compris les espaces, les nouvelles lignes, les virgules, les symboles de conduite et à peu près tout ce que vous voudriez utiliser auparavant. délimiteur sauf NUL. ...ls
sépare les noms de fichiers avec des nouvelles lignes. C'est bien jusqu'à ce que vous ayez un fichier avec une nouvelle ligne dans son nom. Et comme je ne connais aucune implémentation dels
cela qui vous permette de terminer les noms de fichiers avec des caractères NUL au lieu de sauts de ligne, nous ne pouvons pas obtenir une liste de noms de fichiers en toute sécuritéls
.
Bummer, non? Comment jamais peut - on gérer un saut de ligne fin ensemble de données pour les données répertorié peuvent contenir des sauts de ligne? Eh bien, si les personnes qui répondent aux questions sur ce site Web ne font pas ce genre de choses tous les jours, je penserais peut-être que nous avions des problèmes.
En réalité, la plupart des ls
implémentations fournissent en réalité une API très simple pour analyser leurs résultats et nous le faisons tous depuis le début sans même nous en rendre compte. Non seulement vous pouvez terminer un nom de fichier par null, vous pouvez également commencer par un null ou par toute autre chaîne arbitraire de votre choix. 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 cela pour plus.
Maintenant, c’est la partie suivante de cet article qui m’amène vraiment:
$ 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 que, à la sortie de
ls
, ni vous ni l'ordinateur ne pouvez déterminer quelles parties de celui-ci constituent un nom de fichier. Est-ce chaque mot? Est-ce chaque ligne? Non. Il n'y a pas de réponse correcte à cette question si ce n'est: vous ne pouvez pas le dire.Vous remarquerez également que
ls
parfois, les données de votre nom de fichier sont corrompues (dans notre cas, le\n
caractère entre les mots "a" et "nouvelle ligne" est transformé en ? Point d' interrogation ......
Si vous voulez juste parcourir tous les fichiers du répertoire courant, utilisez une
for
boucle et un glob:
for f in *; do
[[ -e $f ]] || continue
...
done
L'auteur l'appelle en altérant les noms de fichiers lorsqu'il ls
renvoie une liste de noms de fichiers contenant des globs de shell , puis recommande l'utilisation d'un glob de 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 les opérandes -1
et -q
ls
donc:
-q
- Forcer chaque instance de caractères de nom de fichier non imprimables et<tab>
s à être écrite en tant que caractère de point d'interrogation ('?'
). Les implémentations peuvent fournir cette option par défaut si la sortie est destinée à un terminal.
-1
- (Le chiffre un.) Force la sortie à une entrée par ligne.
Globbing n'est pas sans problèmes: la ?
correspondance avec n'importe quel caractère permet à plusieurs ?
résultats dans une liste de faire correspondre le même fichier plusieurs fois. C'est facilement manipulé.
Bien que la manière de procéder ne soit pas l’essentiel - cela ne prend pas grand chose après tout, comme le montre l’illustration ci-dessous - je me demandais pourquoi pas . Selon moi, la meilleure réponse à cette question a été acceptée. Je vous suggérerais d'essayer de vous concentrer plus souvent sur le fait de dire aux gens ce qu'ils peuvent faire plutôt que ce qu'ils ne peuvent pas. Je pense que vous êtes beaucoup moins susceptible de vous tromper du moins.
Mais pourquoi même essayer? Certes, ma principale motivation était que les autres n'arrêtaient pas de me dire que je ne pouvais pas. Je sais très bien que la ls
sortie est aussi régulière et prévisible que vous le souhaiteriez, à condition de savoir quoi chercher. La désinformation me dérange plus que la plupart des choses.
La vérité est que, à l'exception notable des réponses de Patrick et de Wumpus Q. Wumbley (malgré le formidable traitement de ce dernier) , je considère que la plupart des informations des réponses ici sont globalement correctes: un shell glob est à la fois plus simple à utiliser. et généralement plus efficace pour la recherche dans le répertoire actuel que l'analyse syntaxique ls
. Cependant, ils ne constituent pas, du moins à mon égard, 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 analyserls
" .
Veuillez noter que les résultats contradictoires de la réponse de Patrick résultent principalement de son utilisation à ce moment- zsh
là bash
. zsh
- par défaut - la $(
commande de fractionnement de mots substituée ne produit pas les )
résultats de manière portable. Alors, quand il demande où est allé le reste des fichiers? la réponse à cette question est que votre coquille les a mangés. C'est pourquoi vous devez définir la SH_WORD_SPLIT
variable lorsque vous utilisez zsh
et manipulez du code shell portable. Je considère que son omission de noter cela dans sa réponse est terriblement trompeuse.
La réponse de Wumpus ne calcule pas pour moi - dans un contexte de liste, le ?
caractère est un glob shell. Je ne sais pas comment dire autrement.
Afin de gérer un cas de résultats multiples, vous devez limiter la gourmandise du glob. Ce qui suit va juste 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" $# )
}
SORTIE
`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 en sécurité tous les caractères qui n'est pas /slash
, -dash
, :colon
ou caractère alphanumérique dans un glob shell alors sort -u
la liste des résultats uniques. Ceci est sûr car nous ls
avons déjà sauvegardé tous les caractères non imprimables. 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
SORTIE:
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. Rappelez-vous que, outre \0
null, le /
caractère ASCII est le seul octet interdit dans un chemin d'accès. Je mets globs de côté ici et combine à la place l' -d
option spécifiée par POSIX pour ls
et la -exec $cmd {} +
construction également spécifiée par POSIX pour find
. Etant donné find
qu’il n’en émettra naturellement que /
successivement, les éléments suivants permettent d’obtenir facilement une liste de fichiers récursive et délimitée de manière fiable, y compris toutes les informations de répertoire pour chaque entrée. Imaginez ce que vous pourriez faire avec quelque chose comme ceci:
#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 - surtout lorsque l'unicité des résultats est en cause.
ls -1iq |
sed '/ .*/s///;s/^/-inum /;$!s/$/ -o /' |
tr -d '\n' |
xargs find
Ce ne sont que les moyens les plus portables que je puisse penser. Avec GNU, ls
vous pouvez faire:
ls --quoting-style=WORD
Enfin, voici une méthode d' analysels
beaucoup plus simple que j'utilise souvent lorsque j'ai besoin de numéros d'inode:
ls -1iq | grep -o '^ *[0-9]*'
Cela ne fait que renvoyer des numéros d'inode - ce qui est une autre option pratique spécifiée par POSIX.
stat
ma réponse, car elle vérifie en réalité que chaque fichier existe. Votre part au bas avec la sed
chose ne fonctionne pas.
ls
en premier lieu? Ce que vous décrivez est très difficile. Je vais devoir le déconstruire pour tout comprendre et je suis un utilisateur relativement compétent. Vous ne pouvez pas vous attendre à ce que votre Joe moyen soit capable de gérer quelque chose comme ça.
ls
sortie est erronée ont été bien couvertes dans le lien d'origine (et dans de nombreux autres endroits). Cette question aurait été raisonnable si OP demandait de l'aide pour le comprendre, mais au lieu de cela, OP essaie simplement de prouver que son utilisation incorrecte est correcte.
parsing ls is bad
. Compter for something in $(command)
sur les mots pour obtenir des résultats précis est une mauvaise command's
chose pour la grande majorité de ceux-ci qui ne disposent pas d'une sortie simple.
time bash -c 'for i in {1..1000}; do ls -R &>/dev/null; done'
= 3.18s vstime bash -c 'for i in {1..1000}; do echo **/* >/dev/null; done'
= 1.28s