Ich sehe ständig Antworten, die zitierendieser Linkdefinitiv erklären"Nicht analysieren ls
!"Das stört mich aus mehreren Gründen:
Es scheint, dass die Informationen in diesem Link ohne viel Hinterfragen akzeptiert wurden, obwohl mir beim flüchtigen Lesen zumindest ein paar Fehler auffallen.
Zudem scheint es, als hätten die in diesem Link genannten Probleme keinerlei Wunsch geweckt, eine Lösung zu finden.
Aus dem ersten Absatz:
...wenn Sie
[ls]
eine Liste von Dateien anfordern, gibt es ein großes Problem: Unix erlaubt fast jedes Zeichen in einem Dateinamen, einschließlich Leerzeichen, Zeilenumbrüche, Kommas, Pipe-Symbole und so ziemlich alles andere, was Sie jemals als Trennzeichen außer NUL verwenden würden. ...ls
trennt Dateinamen durch Zeilenumbrüche. Das ist in Ordnung, bis Sie eine Datei mit einem Zeilenumbruch im Namen haben. Und da ich keine Implementierung kenne,ls
die es Ihnen erlaubt, Dateinamen mit NUL-Zeichen statt mit Zeilenumbrüchen zu beenden, können wir keine Liste von Dateinamen sicher mit abrufenls
.
Schade, oder? Wieimmerkönnen wir einen durch eine neue Zeile abgeschlossenen Datensatz für Daten verarbeiten, die möglicherweise neue Zeilen enthalten? Nun, wenn die Leute, die auf dieser Website Fragen beantworten, so etwas nicht täglich tun würden, würden wir meiner Meinung nach in Schwierigkeiten stecken.
Die Wahrheit ist jedoch, dass die meisten ls
Implementierungen tatsächlich eine sehr einfache API zum Parsen ihrer Ausgabe bereitstellen und wir alle haben dies die ganze Zeit getan, ohne es überhaupt zu merken. Sie können einen Dateinamen nicht nur mit null beenden, sondern auch mit null oder mit jeder beliebigen anderen Zeichenfolge beginnen. Darüber hinaus können Sie diese beliebigen Zeichenfolgen zuweisenpro Dateityp. 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^@^@^@$
^@
SehenDasfür mehr.
Nun ist es jedoch der nächste Teil des Artikels, der mich wirklich beschäftigt:
$ 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 besteht darin, dass weder Sie noch der Computer anhand der Ausgabe
ls
von erkennen können, welche Teile einen Dateinamen bilden. Ist es jedes Wort? Nein. Ist es jede Zeile? Nein. Auf diese Frage gibt es keine richtige Antwort außer: Sie können es nicht erkennen.Beachten Sie auch, wie
ls
manchmal Ihre Dateinamendaten verstümmelt werden (in unserem Fall wurde das\n
Zeichen zwischen den Wörtern"A"Und "Neue Zeile"in ein?Fragezeichen......
Wenn Sie einfach über alle Dateien im aktuellen Verzeichnis iterieren möchten, verwenden Sie eine
for
Schleife und einen Glob:
for f in *; do
[[ -e $f ]] || continue
...
done
Der Autor nennt esDateinamen verstümmelnwenn ls
eine Liste von Dateinamen zurückgegeben wird, die Shell-Globs enthaltenund dannempfiehlt die Verwendung eines Shell-Globs zum Abrufen einer Dateiliste!
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 definiertdie -1
und -q
ls
Operanden also:
-q
- Erzwingt, dass jedes Vorkommen nicht druckbarer Dateinamenzeichen und<tab>
s als Fragezeichen ('?'
) geschrieben wird. Implementierungen können diese Option standardmäßig bereitstellen, wenn die Ausgabe an ein Terminalgerät erfolgt.
-1
-(Die numerische Ziffer Eins.)Erzwingt die Ausgabe auf einen Eintrag pro Zeile.
Globbing ist nicht ohne Probleme - die ?
SpielebeliebigZeichen, sodass mehrere Übereinstimmungsergebnisse ?
in einer Liste mehrfach mit derselben Datei übereinstimmen. Das lässt sich leicht handhaben.
Obwohl es nicht darum geht, wie man das macht - es ist schließlich nicht viel nötig und wird unten demonstriert - war ich interessiert anWarum nicht. Meiner Meinung nach ist die beste Antwort auf diese Frage akzeptiert worden. Ich würde vorschlagen, dass Sie versuchen, sich öfter darauf zu konzentrieren, den Leuten zu sagen, was siedürfentun, als auf das, was siekippen.Zumindest ist es meiner Meinung nach viel unwahrscheinlicher, dass sich herausstellt, dass Sie Unrecht haben.
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 wohl, dass die ls
Ergebnisse so regelmäßig und vorhersehbar sind, wie man es sich nur wünschen kann, solange man weiß, wonach man suchen muss. Falsche Informationen stören mich mehr als die meisten anderen Dinge.
Die Wahrheit ist jedoch, mit der bemerkenswerten Ausnahme der Antworten von Patrick und Wumpus Q. Wumbley(trotz des tollen Griffs des Letzteren), ich halte die meisten Informationen in den Antworten hier für größtenteils richtig - ein Shell-Glob ist sowohl einfacher zu verwenden als auch im Allgemeinen effektiver, wenn es darum geht, das aktuelle Verzeichnis zu durchsuchen, als das Parsen ls
. Sie sind jedoch, zumindest meiner Meinung nach, kein ausreichender Grund, um die Verbreitung der im obigen Artikel zitierten Fehlinformationen zu rechtfertigen, noch sind sie eine akzeptable Rechtfertigung für "niemals analysieren ls
."
Bitte beachten Sie, dass die inkonsistenten Ergebnisse von Patricks Antwort hauptsächlich darauf zurückzuführen sind, dass er zsh
then verwendet bash
. zsh
- standardmäßig - $(
ersetzte )
Ergebnisse durch den Worttrennungsbefehl nicht auf portable Weise. Wenn er also fragtwo sind die restlichen Dateien hin?Die Antwort auf diese Frage lautetdeine Schale hat sie gefressen.SH_WORD_SPLIT
Aus diesem Grund müssen Sie die Variable festlegen, wenn zsh
Sie portablen Shell-Code verwenden und damit arbeiten. Sein Versäumnis, dies in seiner Antwort zu erwähnen, halte ich für äußerst irreführend.
Die Antwort von Wumpus ist für mich nicht nachvollziehbar - in einem Listenkontext ist der ?
CharakterIstein Shell-Glob. Ich weiß nicht, wie ich das sonst sagen soll.
Um den Fall mehrerer Ergebnisse zu verarbeiten, müssen Sie die Gier des Globs einschränken. Im Folgenden wird einfach 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 speichere ich jedes Zeichen, das kein /slash
, -dash
, :colon
oder alphanumerisches Zeichen ist, in einem Shell-Glob und dann sort -u
in der Liste für eindeutige Ergebnisse. Das ist sicher, weil ls
alle nicht druckbaren Zeichen bereits für uns gespeichert wurden. Achtung:
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 das Problem noch einmal an, verwende aber eine andere Methodik. Denken Sie daran, dass – neben \0
null – das /
ASCII-Zeichen das einzige in einem Pfadnamen verbotene Byte ist. Ich lasse hier Globs beiseite und kombiniere stattdessen die POSIX-spezifizierte -d
Option für ls
und die ebenfalls POSIX-spezifizierte -exec $cmd {} +
Konstruktion für find
. Da find
natürlich immer nur eins /
in Folge ausgegeben wird, beschafft das Folgende leicht eine rekursive und zuverlässig abgegrenzte Dateiliste, die alle Dentry-Informationen für jeden Eintrag enthält. Stellen Sie sich nur 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 – insbesondere, wenn die Eindeutigkeit der Ergebnisse fraglich ist.
ls -1iq |
sed '/ .*/s///;s/^/-inum /;$!s/$/ -o /' |
tr -d '\n' |
xargs find
Dies sind nur die portabelsten Mittel, die mir einfallen. Mit GNU ls
könnten Sie Folgendes tun:
ls --quoting-style=WORD
Und schließlich gibt es noch eine viel einfachere Methode,Parsingls
die ich recht häufig verwende, wenn ich Inode-Nummern benötige:
ls -1iq | grep -o '^ *[0-9]*'
Das gibt einfach Inode-Nummern zurück – eine weitere praktische, von POSIX angegebene Option.
Antwort1
Ich bin davon überhaupt nicht überzeugt, aber nehmen wir der Argumentation halber an, Siekönnte, wenn Sie bereit sind, genügend Aufwand zu betreiben, können Sie die Ausgabe ls
zuverlässig analysieren, selbst wenn Sie es mit einem „Gegner“ zu tun haben – jemandem, der den von Ihnen geschriebenen Code kennt und absichtlich Dateinamen wählt, die ihn beschädigen sollen.
Selbst wenn Sie das könnten,es wäre immer noch eine schlechte Idee.
Bourne Shell 1 ist eine schlechte Sprache. Sie sollte nicht für komplizierte Dinge verwendet werden, es sei denn, extreme Portabilität ist wichtiger als jeder andere Faktor (z. B. autoconf
).
Ich behaupte, wenn Sie mit einem Problem konfrontiert sind, bei dem das Parsen der Ausgabe ls
der Weg des geringsten Widerstands für ein Shell-Skript zu sein scheint, ist das ein starker Hinweis darauf, dass das, was Sie tun,zu kompliziert, um ein Shell-Skript zu seinund Sie sollten das Ganze in Perl, Python, Julia oder einem der anderenGutSkriptsprachen, die leicht verfügbar sind. Zur Demonstration hier Ihr letztes Programm in 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))
Es gibt keinerlei Probleme mit ungewöhnlichen Zeichen in Dateinamen - dieAusgabeist auf die gleiche Weise mehrdeutig wie die Ausgabe von ls
mehrdeutig ist, aber das würde in einem „echten“ Programm (im Gegensatz zu einer Demo wie dieser) keine Rolle spielen, da das Ergebnis von os.path.join(subdir, f)
direkt verwendet werden würde.
Ebenso wichtig und im krassen Gegensatz zu dem, was Sie geschrieben haben, wird es auch in sechs Monaten noch Sinn ergeben und sich leicht ändern lassen, wenn Sie es für etwas anderes benötigen. Nehmen wir zur Veranschaulichung an, Sie entdecken die Notwendigkeit, Dotfiles und Editor-Backups auszuschließen und alles in alphabetischer Reihenfolge nach Basisnamen zu verarbeiten:
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))
1 Ja, erweiterte Versionen der Bourne-Shell sind heutzutage leicht verfügbar: bash
und zsh
beide sind erheblich besser als das Original. Die GNU-Erweiterungen der wichtigsten „Shell-Dienstprogramme“ (find, grep usw.) helfen ebenfalls sehr. Aber selbst mit all diesen Erweiterungen wird die Shell-Umgebung nicht verbessert.genugum mit wirklich guten Skriptsprachen zu konkurrieren, daher bleibt mein Rat: „Verwenden Sie die Shell nicht für komplizierte Dinge“, egal von welcher Shell Sie sprechen.
"Wie würde eine gute interaktive Shell aussehen, die auch eine gute Skriptsprache ist?" ist eine aktuelle Forschungsfrage, da es eine inhärente Spannung zwischen den für eine interaktive CLI erforderlichen Annehmlichkeiten (wie z. B. die Möglichkeit, cc -c -g -O2 -o foo.o foo.c
anstelle von einzugeben subprocess.run(["cc", "-c", "-g", "-O2", "-o", "foo.o", "foo.c"])
) und den erforderlichen Einschränkungen zur Vermeidung subtiler Fehler in komplexen Skripten (wie z. B.nichtInterpretieren von Wörtern ohne Anführungszeichen an zufälligen Stellen als Zeichenfolgenliterale). Wenn ich versuchen würde, so etwas zu entwerfen, würde ich wahrscheinlich damit beginnen, IPython, PowerShell und Lua in einen Mixer zu geben, aber ich habe keine Ahnung, wie das Ergebnis aussehen würde.
Antwort2
Auf diesen Link wird häufig verwiesen, da die Informationen absolut korrekt sind und schon seit sehr langer Zeit vorhanden sind.
ls
ersetzt nicht druckbare Zeichen durch Glob-Zeichen, ja, aber diese Zeichen sind nicht im eigentlichen Dateinamen enthalten. Warum ist das wichtig? 2 Gründe:
- Wenn Sie diesen Dateinamen an ein Programm übergeben, existiert dieser Dateiname tatsächlich nicht. Es müsste den Glob erweitern, um den tatsächlichen Dateinamen zu erhalten.
- Der Datei-Glob könnte mit mehr als einer Datei übereinstimmen.
Zum Beispiel:
$ touch a$'\t'b
$ touch a$'\n'b
$ ls -1
a?b
a?b
Beachten Sie, dass wir zwei Dateien haben, die genau gleich aussehen. Wie können Sie sie unterscheiden, wenn sie beide als dargestellt werden a?b
?
Der Autor bezeichnet es als Verfälschung von Dateinamen, wenn ls eine Liste von Dateinamen zurückgibt, die Shell-Globs enthalten, und empfiehlt dann die Verwendung eines Shell-Globs zum Abrufen einer Dateiliste!
Hier gibt es einen Unterschied. Wenn Sie einen Glob zurückbekommen, wie gezeigt, kann dieser Glob mit mehr als einer Datei übereinstimmen. Wenn Sie jedoch die Ergebnisse durchlaufen, die mit einem Glob übereinstimmen, erhalten Sie die genaue Datei zurück, nicht einen Glob.
Zum Beispiel:
$ for file in *; do printf '%s' "$file" | xxd; done
0000000: 6109 62 a.b
0000000: 610a 62 a.b
Beachten Sie, dass die xxd
Ausgabe $file
die Rohzeichen \t
und und \n
nicht enthielt ?
.
Wenn Sie verwenden ls
, erhalten Sie stattdessen Folgendes:
for file in $(ls -1q); do printf '%s' "$file" | xxd; done
0000000: 613f 62 a?b
0000000: 613f 62 a?b
„Ich werde sowieso iterieren, warum nicht verwenden ls
?“
Das Beispiel, das Sie angeführt haben, funktioniert eigentlich nicht. Es sieht so aus, als würde es funktionieren, aber das tut es nicht.
Ich beziehe mich auf Folgendes:
for f in $(ls -1q | tr " " "?") ; do [ -f "$f" ] && echo "./$f" ; done
Ich habe ein Verzeichnis mit einer Reihe von Dateinamen erstellt:
$ 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
Wenn ich Ihren Code ausführe, erhalte ich Folgendes:
$ for f in $(ls -1q | tr " " "?") ; do [ -f "$f" ] && echo "./$f" ; done
./a b
./a b
Wo sind die restlichen Dateien hin?
Versuchen wir stattdessen Folgendes:
$ 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
Lassen Sie uns nun einen echten Glob verwenden:
$ for f in *; do stat --format='%n' "./$f"; done
./a b
./a b
./a b
./a b
./a b
./a
b
Mit Bash
Das obige Beispiel wurde mit meiner normalen Shell, zsh, durchgeführt. Wenn ich den Vorgang mit bash wiederhole, erhalte ich mit Ihrem Beispiel ein völlig anderes Ergebnis:
Gleicher Satz von Dateien:
$ 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
Radikal andere Ergebnisse mit Ihrem 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
Mit einem Shell-Glob funktioniert es einwandfrei:
$ for f in *; do stat --format='%n' "./$f"; done
./a b
./a b
./a b
./a b
./a b
./a
b
Der Grund, warum sich Bash so verhält, geht auf einen der Punkte zurück, die ich am Anfang der Antwort angesprochen habe: „Der Datei-Glob könnte mit mehr als einer Datei übereinstimmen.“
ls
gibt für mehrere Dateien denselben Glob ( a?b
) zurück, sodass wir jedes Mal, wenn wir diesen Glob erweitern, jede einzelne Datei erhalten, die damit übereinstimmt.
So erstellen Sie die Liste der von mir verwendeten Dateien neu:
touch 'a b' 'a b' a$'\xe2\x80\x82'b a$'\xe2\x80\x83'b a$'\t'b a$'\n'b
Die Hex-Codes sind UTF-8-NBSP-Zeichen.
Antwort3
Die Ausgabe ls -q
ist überhaupt kein Glob. Es bedeutet ?
normalerweise „Hier ist ein Zeichen, das nicht direkt angezeigt werden kann“. Globs bedeuten ?
normalerweise „Hier ist jedes Zeichen zulässig“.
Globs haben andere Sonderzeichen ( *
und []
innerhalb des Paares gibt es mindestens []
noch mehr). Keines davon wird durch maskiert ls -q
.
$ touch x '[x]'
$ ls -1q
[x]
x
Wenn Sie die Ausgabe behandeln, ls -1q
gibt es eine Reihe von Globs, und diese erweitern, erhalten Sie sie nicht nur x
zweimal, sondern Sie verfehlen sie [x]
vollständig. Als Glob stimmt es nicht mit sich selbst als Zeichenfolge überein.
ls -q
soll Ihre Augen und/oder Ihr Terminal vor verrückten Zeichen schützen und nicht etwas produzieren, das Sie an die Shell zurückmelden können.
Antwort4
Die Antwort ist einfach: Die Sonderfälle, die ls
Sie behandeln müssen, überwiegen jeden möglichen Nutzen. Diese Sonderfälle können vermieden werden, wenn Sie ls
die Ausgabe nicht analysieren.
Das Mantra hier istVertraue niemals dem Benutzerdateisystem(das Äquivalent zuVertraue niemals Benutzereingaben). Wenn es eine Methode gibt, die immer mit 100%iger Sicherheit funktioniert, sollte diese Methode Ihre Wahl sein, auch wenn ls
sie dasselbe Ergebnis mit geringerer Sicherheit liefert. Ich werde nicht auf technische Details eingehen, da diese bereits behandelt wurden interdonUndPatrickausführlich. Ich weiß, dass ich aufgrund der Risiken, die ls
mit einer wichtigen (und möglicherweise teuren) Transaktion verbunden sind, bei der mein Job/mein Ansehen auf dem Spiel steht, jede Lösung vorziehen werde, die kein gewisses Maß an Unsicherheit birgt, wenn dies vermieden werden kann.
Ich weiß, dass manche Leute es vorziehenein gewisses Risiko über die Sicherheit, AberIch habe einen Fehlerbericht eingereicht.