Ich sehe durchweg Antworten, die diesen Link mit der definitiven Aussage "Don't parse ls
!" Zitieren. Das stört mich aus mehreren Gründen:
Es sieht so aus, als ob die Informationen in diesem Link mit wenig Bedenken allgemein akzeptiert wurden, obwohl ich zumindest ein paar Fehler beim gelegentlichen Lesen herausgreifen kann.
Es scheint auch, als hätten die in diesem Link genannten Probleme keinen Wunsch nach einer Lösung geweckt.
Ab dem ersten Absatz:
... wenn Sie
[ls]
nach einer Liste von Dateien fragen , gibt es ein großes Problem: Unix lässt fast jedes Zeichen in einem Dateinamen zu, einschließlich Leerzeichen, Zeilenumbrüchen, Kommas, Pipe-Symbolen und so ziemlich allem, was Sie jemals als verwenden würden Trennzeichen außer NUL. ...ls
trennt Dateinamen mit Zeilenumbrüchen. Dies ist in Ordnung, bis Sie eine Datei mit einer neuen Zeile im Namen haben. Und da mir keine Implementierung bekannt istls
, mit der Sie Dateinamen mit NUL-Zeichen anstelle von Zeilenumbrüchen abschließen können, ist es uns nicht möglich, eine Liste sicherer Dateinamen zu erhaltenls
.
Schade, oder? Wie immer können wir damit umgehen ein Newline gelisteten Datensatz für Daten beendet , die Zeilenumbrüche enthalten könnten? Nun, wenn die Leute, die auf dieser Website Fragen beantworteten, solche Dinge nicht täglich taten, könnte ich denken, dass wir in Schwierigkeiten steckten.
Die Wahrheit ist jedoch, dass die meisten ls
Implementierungen tatsächlich eine sehr einfache API zum Parsen ihrer Ausgabe bereitstellen, und wir haben es alle die ganze Zeit gemacht, ohne es überhaupt zu merken. Sie können einen Dateinamen nicht nur mit null beenden, sondern auch mit null oder einer beliebigen anderen Zeichenfolge beginnen. Außerdem können Sie diese beliebigen Zeichenfolgen pro Dateityp zuweisen . Beachten Sie bitte:
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^@^@^@$
^@
Sehen Sie dies für mehr.
Jetzt ist es der nächste Teil dieses Artikels, der mich wirklich begeistert:
$ 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
Das Problem ist, dass
ls
weder Sie noch der Computer anhand der Ausgabe von erkennen können, welche Teile davon einen Dateinamen darstellen. Ist es jedes Wort? Nein, ist es jede Zeile? Nein. Es gibt keine richtige Antwort auf diese Frage außer: Sie können es nicht sagen.
ls
Beachten Sie auch, wie manchmal Ihre Dateinamendaten verstümmelt werden (in unserem Fall wurde das\n
Zeichen zwischen den Wörtern "a" und "newline" in ein Fragezeichen umgewandelt ......
Wenn Sie nur alle Dateien im aktuellen Verzeichnis durchlaufen möchten, verwenden Sie eine
for
Schleife und einen Glob:
for f in *; do
[[ -e $f ]] || continue
...
done
Der Autor nennt es unleserliche Dateinamen, wenn er ls
eine Liste von Dateinamen zurückgibt, die Shell-Globs enthalten, und empfiehlt dann , ein Shell-Glob zu verwenden, um eine Dateiliste abzurufen!
Folgendes berücksichtigen:
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 definiert die Operanden -1
und folgendermaßen -q
ls
:
-q
- Erzwingen Sie, dass jede Instanz von nicht druckbaren Dateinamenzeichen und<tab>
s als Fragezeichen ('?'
) geschrieben wird. Implementierungen bieten diese Option möglicherweise standardmäßig an, wenn die Ausgabe an ein Endgerät erfolgt.
-1
- (Die numerische Ziffer Eins.) Erzwingt die Ausgabe eines Eintrags pro Zeile.
Das Globbing ist nicht ohne Probleme - es ?
stimmt mit jedem Zeichen überein , sodass mehrere Übereinstimmungsergebnisse ?
in einer Liste mehrmals mit derselben Datei übereinstimmen . Das ist leicht zu handhaben.
Obwohl es nicht darum geht, wie man das macht - es braucht doch nicht viel, und es wird unten gezeigt -, war ich daran interessiert, warum nicht . Meiner Meinung nach wurde die beste Antwort auf diese Frage angenommen. Ich würde vorschlagen, dass Sie versuchen, sich häufiger darauf zu konzentrieren, den Leuten zu sagen, was sie können , als auf das, was sie nicht können. Sie sind viel weniger wahrscheinlich, wie ich denke, zumindest als falsch erwiesen zu werden.
Aber warum sollte man es überhaupt versuchen? Zugegeben, meine Hauptmotivation war, dass andere mir immer wieder sagten, ich könne es nicht. Ich weiß sehr gut, dass die ls
Ausgabe so regelmäßig und vorhersehbar ist, wie Sie es wünschen können, solange Sie wissen, wonach Sie suchen müssen. Fehlinformationen stören mich mehr als die meisten anderen Dinge.
Die Wahrheit ist jedoch, dass ich mit Ausnahme der Antworten von Patrick und Wumpus Q. Wumbley (trotz des großartigen Griffs des letzteren) die meisten Informationen in den Antworten als größtenteils richtig betrachte - ein Shell Glob ist einfacher zu verwenden und im Allgemeinen effektiver beim Durchsuchen des aktuellen Verzeichnisses als beim Parsen ls
. Sie sind jedoch nicht zumindest in meiner Hinsicht Grund genug , um zu rechtfertigen , entweder die falschen Informationen in dem Artikel zitiert ausbreitende oben noch sind sie akzeptabel Rechtfertigung „ nie zu analysieren ls
. “
Bitte beachten Sie, dass die inkonsistenten Ergebnisse von Patricks Antwort größtenteils darauf zurückzuführen sind, dass er sie zsh
dann verwendet bash
. zsh
- Standardmäßig - Ersetzt keine wortgeteilten $(
Befehle )
auf tragbare Weise. Also, wenn er fragt, wo sind die restlichen Dateien hingegangen? Die Antwort auf diese Frage ist, dass Ihre Muschel sie gefressen hat. Aus diesem Grund müssen Sie die SH_WORD_SPLIT
Variable festlegen , wenn Sie zsh
portablen Shell-Code verwenden und damit umgehen. Ich halte es für furchtbar irreführend, dass er dies in seiner Antwort nicht zur Kenntnis genommen hat.
Die Antwort von Wumpus lässt sich für mich nicht berechnen - in einem Listenkontext ist der ?
Charakter ein Shell-Glob. Ich weiß nicht, wie ich das sonst sagen soll.
Um einen Fall mit mehreren Ergebnissen zu behandeln, müssen Sie die Gier des Globus einschränken. Im Folgenden wird nur eine Testbasis mit schrecklichen Dateinamen erstellt und für Sie angezeigt:
{ 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" $# )
}
AUSGABE
`ls` ?QUOTED `-m` COMMA,SEP
??\, ??^, ??`, ??b, [?\, [?\, ]?^, ]?^, _?`, _?`, a?b, a?b
NOW LITERAL - COMMA,SEP
?
\, ?
^, ?
`, ?
b, [ \, [
\, ] ^, ]
^, _ `, _
`, a b, a
b
FILE COUNT: 12
Jetzt werde ich sicher jedes Zeichen , das kein ist /slash
, -dash
, :colon
, oder alphanumerischen Zeichen in einer Shell - Glob dann sort -u
die Liste für eindeutige Ergebnisse. Dies ist sicher, da ls
bereits alle nicht druckbaren Zeichen für uns gespeichert wurden. Sehen:
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
AUSGABE:
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'
Im Folgenden gehe ich erneut auf das Problem ein, wende jedoch eine andere Methode an. Denken Sie daran, dass neben \0
Null das /
ASCII-Zeichen das einzige Byte ist, das in einem Pfadnamen verboten ist. Ich lege hier Globs beiseite und kombiniere stattdessen die von POSIX angegebene -d
Option für ls
und das von POSIX angegebene -exec $cmd {} +
Konstrukt für find
. Da find
immer nur eine /
Datei in natürlicher Reihenfolge ausgegeben wird, wird im Folgenden eine rekursive und zuverlässig begrenzte Dateiliste mit allen Eintragsinformationen für jeden Eintrag bereitgestellt. Stellen Sie sich vor, was Sie mit so etwas machen könnten:
#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
kann sehr nützlich sein - besonders wenn es um die Eindeutigkeit des Ergebnisses geht.
ls -1iq |
sed '/ .*/s///;s/^/-inum /;$!s/$/ -o /' |
tr -d '\n' |
xargs find
Dies sind nur die tragbarsten Mittel, die ich mir vorstellen kann. Mit GNU können ls
Sie Folgendes tun:
ls --quoting-style=WORD
Und zum Schluss noch eine viel einfachere Methode zum Parsenls
, die ich häufig verwende, wenn ich Inode-Nummern benötige:
ls -1iq | grep -o '^ *[0-9]*'
Das gibt nur Inode-Nummern zurück - eine weitere praktische POSIX-Option.
stat
in meiner Antwort überprüft, ob jede Datei vorhanden ist. Dein bisschen unten mit dem sed
Ding klappt nicht.
ls
? Was Sie beschreiben, ist sehr schwer. Ich muss es dekonstruieren, um alles zu verstehen, und ich bin ein relativ kompetenter Benutzer. Sie können unmöglich erwarten, dass Ihr durchschnittlicher Joe mit so etwas fertig wird.
ls
Ausgabe falsch ist, wurden im ursprünglichen Link (und an vielen anderen Stellen) gut behandelt. Diese Frage wäre vernünftig gewesen, wenn OP um Hilfe gebeten hätte, es zu verstehen, aber stattdessen versucht OP einfach zu beweisen, dass seine falsche Verwendung in Ordnung ist.
parsing ls is bad
. Tun for something in $(command)
und ich auf Wort-Splitting genaue Ergebnisse zu erhalten , ist schlecht für die große Mehrheit der command's
der nicht einfachen Ausgang.
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