„Ich werde sowieso iterieren, warum nicht verwenden ls?“

„Ich werde sowieso iterieren, warum nicht verwenden ls?“

Ich sehe ständig Antworten, die zitierendieser Linkdefinitiv erklären"Nicht analysieren ls!"Das stört mich aus mehreren Gründen:

  1. Es scheint, dass die Informationen in diesem Link ohne viel Hinterfragen akzeptiert wurden, obwohl mir beim flüchtigen Lesen zumindest ein paar Fehler auffallen.

  2. 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. ... lstrennt Dateinamen durch Zeilenumbrüche. Das ist in Ordnung, bis Sie eine Datei mit einem Zeilenumbruch im Namen haben. Und da ich keine Implementierung kenne, lsdie es Ihnen erlaubt, Dateinamen mit NUL-Zeichen statt mit Zeilenumbrüchen zu beenden, können wir keine Liste von Dateinamen sicher mit abrufen ls.

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 lsImplementierungen 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 lsvon 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 lsmanchmal Ihre Dateinamendaten verstümmelt werden (in unserem Fall wurde das \nZeichen 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 forSchleife und einen Glob:

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

Der Autor nennt esDateinamen verstümmelnwenn lseine 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 -1und -q lsOperanden 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 lsErgebnisse 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 zshthen 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_SPLITAus diesem Grund müssen Sie die Variable festlegen, wenn zshSie 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, :colonoder alphanumerisches Zeichen ist, in einem Shell-Glob und dann sort -uin der Liste für eindeutige Ergebnisse. Das ist sicher, weil lsalle 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 \0null – das /ASCII-Zeichen das einzige in einem Pfadnamen verbotene Byte ist. Ich lasse hier Globs beiseite und kombiniere stattdessen die POSIX-spezifizierte -dOption für lsund die ebenfalls POSIX-spezifizierte -exec $cmd {} +Konstruktion für find. Da findnatü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 -ikann 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 lskönnten Sie Folgendes tun:

ls --quoting-style=WORD

Und schließlich gibt es noch eine viel einfachere Methode,Parsinglsdie 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 lszuverlä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 lsder 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 lsmehrdeutig 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: bashund zshbeide 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.canstelle 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.


lsersetzt nicht druckbare Zeichen durch Glob-Zeichen, ja, aber diese Zeichen sind nicht im eigentlichen Dateinamen enthalten. Warum ist das wichtig? 2 Gründe:

  1. 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.
  2. 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 xxdAusgabe $filedie Rohzeichen \tund und \nnicht 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.“

lsgibt 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 -qist ü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 -1qgibt es eine Reihe von Globs, und diese erweitern, erhalten Sie sie nicht nur xzweimal, sondern Sie verfehlen sie [x]vollständig. Als Glob stimmt es nicht mit sich selbst als Zeichenfolge überein.

ls -qsoll 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 lsSie behandeln müssen, überwiegen jeden möglichen Nutzen. Diese Sonderfälle können vermieden werden, wenn Sie lsdie 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 lssie 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 lsmit 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.

verwandte Informationen