Warum * nicht * 'ls' analysieren (und was stattdessen tun?)

204

Ich sehe durchweg Antworten, die diesen Link mit der definitiven Aussage "Don't parse ls!" Zitieren. Das stört mich aus mehreren Gründen:

  1. 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.

  2. 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. ... lstrennt 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 ist ls, 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 erhalten ls.

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 lsImplementierungen 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 lsweder 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.

lsBeachten Sie auch, wie manchmal Ihre Dateinamendaten verstümmelt werden (in unserem Fall wurde das \nZeichen 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 forSchleife und einen Glob:

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

Der Autor nennt es unleserliche Dateinamen, wenn er lseine 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 -1und 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 lsAusgabe 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 zshdann 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_SPLITVariable festlegen , wenn Sie zshportablen 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 -udie Liste für eindeutige Ergebnisse. Dies ist sicher, da lsbereits 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 \0Null 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 -dOption für lsund das von POSIX angegebene -exec $cmd {} +Konstrukt für find. Da findimmer 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 lsSie 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.

mikeserv
quelle
12
@mikeserv Ok, das habe ich getan. Shell Glob ist 2,48 mal schneller. time bash -c 'for i in {1..1000}; do ls -R &>/dev/null; done'= 3,18s vs time bash -c 'for i in {1..1000}; do echo **/* >/dev/null; done'= 1,28s
Patrick
28
In Bezug auf Ihr letztes Update verlassen Sie sich nicht mehr auf die visuelle Ausgabe, um festzustellen, ob Ihr Code funktioniert. Übergeben Sie Ihre Ausgabe an ein tatsächliches Programm, und lassen Sie das Programm versuchen, eine Operation für die Datei auszuführen. Aus diesem Grund habe ich statin meiner Antwort überprüft, ob jede Datei vorhanden ist. Dein bisschen unten mit dem sedDing klappt nicht.
Patrick
57
Das kannst du nicht ernst meinen. Wie kann es einfacher oder einfacher oder in irgendeiner Weise besser sein, durch alle Rahmen zu springen, die Ihre Frage beschreibt, als einfach überhaupt nicht zu analysieren 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.
terdon
46
-1 für die Verwendung einer Frage zur Auswahl eines Arguments. Alle Gründe, warum das Parsen der lsAusgabe 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.
R ..
14
@mikeserv Es ist nicht nur das 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'sder nicht einfachen Ausgang.
BroSlow

Antworten:

184

Davon bin ich überhaupt nicht überzeugt, aber nehmen wir an, Sie könnten , wenn Sie bereit sind, sich genug Mühe zu geben, die Ergebnisse lsauch gegenüber einem "Gegner" - jemandem, der es tut - zuverlässig analysieren kennt den von Ihnen geschriebenen Code und wählt absichtlich Dateinamen aus, um ihn zu knacken.

Selbst wenn Sie das könnten, wäre es immer noch eine schlechte Idee .

Borowski-Shell ist keine gute Sprache. Es sollte nicht für komplizierte Dinge verwendet werden, es sei denn, extreme Portabilität ist wichtiger als irgendein anderer Faktor (z autoconf. B. ).

Ich behaupte, wenn Sie mit einem Problem konfrontiert sind, bei dem das Parsen der Ausgabe von lswie der Pfad des geringsten Widerstands für ein Shell-Skript erscheint, ist dies ein starkes Indiz dafür, dass alles, was Sie tun, für Shell zu kompliziert ist und Sie das Ganze in neu schreiben sollten Perl oder Python. Hier ist 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))

Dies hat keinerlei Probleme mit ungewöhnlichen Zeichen in Dateinamen - die Ausgabe ist mehrdeutig, genauso wie die Ausgabe von lsmehrdeutig ist, aber das würde in einem "echten" Programm (im Gegensatz zu einer Demo wie dieser) keine Rolle spielen benutze das Ergebnis von os.path.join(subdir, f)direkt.

Ebenso wichtig und im krassen Gegensatz zu dem, was Sie geschrieben haben, wird es in sechs Monaten immer noch Sinn machen, und es wird leicht zu modifizieren sein, wenn Sie es brauchen, um etwas etwas anderes zu tun. Nehmen wir zur Veranschaulichung an, Sie müssen Punktdateien und Editor-Backups ausschließen und alles in alphabetischer Reihenfolge nach dem Basisnamen 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))
zwol
quelle
5
Das ist gut. Spricht das for in | for invon Rekursion? Ich bin mir nicht sicher. Auch wenn es so ist, kann es doch nicht mehr als eins sein, oder? Dies ist die einzige Antwort, die mir bisher Sinn macht.
mikeserv
10
Keine Rekursion, nur verschachtelte forSchleifen. os.walkhinter den kulissen muss man sich nicht mehr darum kümmern, als sich darum zu sorgen, wie lsoder wie man findintern arbeitet.
zwol
6
Technisch gesehen wird os.walkein Generatorobjekt zurückgegeben . Generatoren sind Pythons Version von Lazy Lists. Jedes Mal, wenn die äußere for-Schleife iteriert, wird der Generator aufgerufen und liefert den Inhalt eines anderen Unterverzeichnisses. Gleichwertige Funktionalität in Perl ist File::Find, wenn das hilft.
zwol
6
Sie sollten sich darüber im Klaren sein, dass ich dem von Ihnen kritisierten Dokument und den Antworten von Patrick und Terdon zu 100% zustimme. Meine Antwort sollte einen zusätzlichen , unabhängigen Grund liefern , um das Parsen der lsAusgabe zu vermeiden .
zwol
19
Das ist sehr irreführend. Shell ist keine gute Programmiersprache, sondern nur, weil es keine Programmiersprache ist. Es ist eine Skriptsprache. Und es ist eine gute Skriptsprache.
Miles Rout
178

Auf diesen Link wird häufig verwiesen, da die Informationen absolut korrekt sind und schon sehr lange vorhanden sind.


lsErsetzt nicht druckbare Zeichen durch Glob-Zeichen. Ja, aber diese Zeichen sind nicht im tatsächlichen Dateinamen enthalten. Warum ist das wichtig? 2 Gründe:

  1. Wenn Sie diesen Dateinamen an ein Programm übergeben, ist dieser Dateiname tatsächlich nicht vorhanden. Es müsste den Glob erweitern, um den echten Dateinamen zu erhalten.
  2. Das Datei-Glob entspricht möglicherweise mehr als einer Datei.

Zum Beispiel:

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

Beachten Sie, wie wir 2 Dateien haben, die genau gleich aussehen. Wie werden Sie sie unterscheiden, wenn beide als dargestellt werden a?b?


Der Autor nennt es unleserliche Dateinamen, wenn ls eine Liste von Dateinamen zurückgibt, die Shell-Globs enthalten, und empfiehlt dann, ein Shell-Glob zu verwenden, um eine Dateiliste abzurufen!

Hier gibt es einen Unterschied. Wenn Sie einen Glob zurückerhalten (siehe Abbildung), stimmt dieser Glob möglicherweise mit mehr als einer Datei überein. Wenn Sie jedoch die Ergebnisse durchlaufen, die mit einem Glob übereinstimmen, erhalten Sie die genaue Datei und nicht einen Glob zurück.

Zum Beispiel:

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

Beachten Sie, wie die xxdAusgabe zeigt, dass $filedie unformatierten Zeichen \tund \nnicht enthalten ?.

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 benutzen ls?"

Dein Beispiel, das du gegeben hast, funktioniert nicht wirklich. Es sieht aus wie es funktioniert, aber es funktioniert nicht.

Damit meine ich:

 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
./ab
./ab

Wo sind die restlichen Dateien geblieben?

Lass es uns stattdessen versuchen:

$ 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
./ab
./ab
stat: cannot stat ‘./a?b’: No such file or directory
stat: cannot stat ‘./a?b’: No such file or directory

Verwenden wir nun ein aktuelles Glob:

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

Mit Bash

Das obige Beispiel war mit meiner normalen Shell, zsh. Wenn ich die Prozedur mit bash wiederhole, erhalte ich mit Ihrem Beispiel eine völlig andere Ergebnismenge:

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 unterschiedliche Ergebnisse mit Ihrem Code:

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

Mit einem Shell Glob funktioniert es einwandfrei:

$ for f in *; do stat --format='%n' "./$f"; done
./a b
./a  b
./ab
./ab
./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: "Die Datei glob entspricht möglicherweise mehr als einer Datei".

lsgibt den gleichen glob ( a?b) für mehrere Dateien zurück, sodass wir jedes Mal, wenn wir diesen glob erweitern, jede einzelne Datei erhalten, die mit ihm ü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-Code-Zeichen sind UTF-8-NBSP-Zeichen.

Patrick
quelle
5
@mikeserv tatsächlich gibt seine Lösung keinen Glob zurück. Ich habe gerade meine Antwort aktualisiert, um diesen Punkt zu verdeutlichen.
Patrick
18
"Nicht der Rest"? Es ist inkonsistentes Verhalten und unerwartete Ergebnisse, wie ist das kein Grund?
Patrick
11
@mikeserv Hast du meinen Kommentar zu deiner Frage nicht gesehen? Shell Globbing ist 2,5-mal schneller als ls. Ich habe Sie auch gebeten, Ihren Code zu testen, da er nicht funktioniert. Was hat zsh damit zu tun?
Patrick
27
@mikeserv Nein, es gilt immer noch alles selbst für die Bash. Obwohl ich mit dieser Frage fertig bin, da Sie nicht auf das hören, was ich sage.
Patrick
7
Weißt du was? Ich denke, ich werde diese Antwort positiv bewerten und in meiner klarstellen, dass ich mit allem, was darin steht, einverstanden bin. ;-)
zwol
54

Lassen Sie uns versuchen, ein wenig zu vereinfachen:

$ 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

Sehen? Das ist schon falsch, genau dort. Es gibt 3 Dateien, aber bash meldet 4. Dies liegt daran, setdass dem die generierten Globs gegeben werden, lsdie von der Shell erweitert werden, bevor sie an übergeben werden set. Welches ist, warum Sie bekommen:

$ 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

Oder wenn Sie es vorziehen:

$ 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

Das obige wurde weiter ausgeführt bash 4.2.45.

terdon
quelle
2
Ich habe das positiv bewertet. Es ist gut zu sehen, wie dich dein eigener Code beißt. Aber nur weil ich es falsch verstanden habe, heißt das nicht, dass es nicht richtig gemacht werden kann. Ich habe Ihnen heute Morgen eine sehr einfache Methode gezeigt, mit ls -1qRi | grep -o '^ *[0-9]*'der Sie die lsAusgabe analysieren können. Das ist die schnellste und beste Methode, um eine Liste der Inode-Nummern zu erhalten.
mikeserv
38
@mikeserv: Es könnte richtig gemacht werden, wenn du die Zeit und die Geduld hast. Tatsache ist jedoch, dass es von Natur aus fehleranfällig ist. Du selbst hast es falsch verstanden. während über seine Vorzüge streiten! Das ist ein gewaltiger Streik dagegen, wenn selbst die eine Person, die dafür kämpft, es nicht richtig macht. Und wahrscheinlich werden Sie noch mehr Zeit damit verbringen, Fehler zu machen, bevor Sie es richtig machen. Ich weiß nicht, was Sie angeht, aber die meisten Leute haben mit ihrer Zeit besser zu tun, als ewig mit derselben Codezeile herumzuspielen.
CHAO
@cHao - ich habe nicht über seine Vorzüge gestritten - ich habe gegen seine Propaganda protestiert.
mikeserv
16
@mikeserv: Die Argumente dagegen sind begründet und verdient. Sogar Sie haben gezeigt, dass sie wahr sind.
CHAO
1
@cHao - ich stimme nicht zu. Es gibt eine nicht so feine Linie zwischen einem Mantra und einer Weisheit.
mikeserv
50

Die Ausgabe von ls -qist überhaupt kein Glob. Es ?bedeutet "Es gibt hier ein Zeichen, das nicht direkt angezeigt werden kann". Globs ?bedeuten "Hier ist jedes Zeichen erlaubt".

Globs haben andere Sonderzeichen ( *und []zumindest und innerhalb des []Paares gibt es mehr). Keiner von denen wird von entkommen ls -q.

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

Wenn Sie die ls -1qAusgabe behandeln, gibt es eine Reihe von Globs und erweitern Sie sie. Sie erhalten nicht nur das xDoppelte, Sie werden es auch [x]komplett vermissen . Als Glob passt es sich nicht als String an.

ls -q soll deine Augen und / oder das Terminal vor verrückten Charakteren bewahren und nichts produzieren, was du der Shell zurückgeben kannst.


quelle
42

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 die lsAusgabe nicht analysieren .

Das Mantra hier ist , dem Dateisystem des Benutzers niemals zu vertrauen (das Äquivalent, Benutzereingaben niemals zu vertrauen ). Wenn es eine Methode gibt, die mit 100% iger Sicherheit immer funktioniert, sollte es die Methode sein, die Sie bevorzugen, auch wenn Sie lsdasselbe tut, aber mit weniger Sicherheit. Ich werde nicht auf technische Details eingehen, da diese von Terdon und Patrick ausführlich behandelt wurden. Ich weiß, dass ich aufgrund des Risikos, lsin einer wichtigen (und möglicherweise teuren) Transaktion, in der mein Job / Prestige auf dem Spiel steht, eine Lösung vorziehen werde, die keine gewisse Unsicherheit aufweist, wenn sie vermieden werden kann.

Ich weiß, einige Leute bevorzugen ein gewisses Risiko gegenüber der Gewissheit , aber ich habe einen Fehlerbericht eingereicht .

Braiam
quelle
33

Der Grund, warum Leute sagen, niemals etwas zu tun, ist nicht notwendigerweise, weil es absolut positiv nicht richtig gemacht werden kann. Wir sind möglicherweise in der Lage, dies zu tun, aber es kann sowohl räumlich als auch zeitlich komplizierter und weniger effizient sein. Zum Beispiel wäre es völlig in Ordnung zu sagen, dass Sie in x86-Assembly niemals ein großes E-Commerce-Backend erstellen sollten.

Nun zum vorliegenden Problem: Wie Sie gezeigt haben, können Sie eine Lösung erstellen, die ls analysiert und das richtige Ergebnis liefert - Richtigkeit ist also kein Problem.

Ist es komplizierter? Ja, aber das können wir hinter einer Hilfsfunktion verbergen.

Nun zur Effizienz:

Platzsparend: Ihre Lösung ist darauf angewiesen uniq, Duplikate herauszufiltern. Daher können wir die Ergebnisse nicht träge generieren. Also entweder O(1)vs. O(n)oder beides haben O(n).

Zeiteffizienz: Im besten Fall uniqwird ein Hashmap-Ansatz verwendet, sodass wir immer noch einen O(n)Algorithmus für die Anzahl der beschafften Elemente haben , obwohl dies wahrscheinlich der Fall ist O(n log n).

Jetzt das eigentliche Problem: Während Ihr Algorithmus immer noch nicht so schlecht aussieht, habe ich wirklich darauf geachtet, beschaffte Elemente und keine Elemente für n zu verwenden. Weil das einen großen Unterschied macht. Angenommen, Sie haben eine Datei \n\n, die einen Glob ergibt, ??sodass alle 2 Zeichendateien in der Auflistung übereinstimmen. Lustigerweise, wenn Sie eine andere Datei haben \n\r, die ebenfalls ??alle 2-Zeichen-Dateien ergibt und auch zurückgibt . Sehen Sie, wohin das führt? Exponentielles statt lineares Verhalten wird mit Sicherheit als "schlechteres Laufzeitverhalten" bezeichnet. Dies ist der Unterschied zwischen einem praktischen Algorithmus und einem Algorithmus, über den Sie in theoretischen CS-Fachzeitschriften schreiben.

Jeder liebt Beispiele, oder? Auf geht's. Erstellen Sie einen Ordner mit dem Namen "test" und verwenden Sie dieses Python-Skript in demselben Verzeichnis, in dem sich der Ordner befindet.

#!/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()

Das einzige, was dies bewirkt, ist, alle Produkte der Länge 3 für 7 Zeichen zu generieren. Die Mathematik der High School sagt uns, dass es 343 Dateien sein sollten. Nun, das sollte wirklich schnell zu drucken sein, also schauen wir mal:

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

Jetzt lass uns deine erste Lösung ausprobieren, weil ich das wirklich nicht verstehe

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)

was hier auf Linux Mint 16 zu arbeiten (was meiner Meinung nach Bände für die Benutzerfreundlichkeit dieser Methode spricht).

Auf jeden Fall sollte die frühere Lösung mindestens so schnell sein wie die spätere (keine Inode-Tricks in dieser - aber diese sind unzuverlässig, sodass Sie die Richtigkeit aufgeben würden), da das oben genannte Ergebnis so gut wie nur gefiltert wird.

Also, wie lange noch?

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

nehmen? Nun, ich weiß es wirklich nicht, es dauert eine Weile, bis ich die 343 ^ 343-Dateinamen überprüft habe - ich werde es Ihnen nach dem heißen Tod des Universums sagen.

Voo
quelle
6
Natürlich ist, wie in den Kommentaren unter einer anderen Antwort erwähnt , die Aussage "... Sie haben gezeigt, dass Sie eine Lösung erstellen können, die ls analysiert und das richtige Ergebnis liefert ..." nicht wahr.
Wildcard
26

Die erklärte Absicht des OP wurde angesprochen

Vorwort und Begründung der ursprünglichen Antwort aktualisiert am 18.05.2015

mikeserv (der OP) erklärte im letzten Update zu seiner Frage: "Ich finde es schade, dass ich diese Frage zum ersten Mal gestellt habe, um auf eine Quelle von Fehlinformationen hinzuweisen. "

Na gut; Ich finde es ziemlich schade, dass ich so viel Zeit damit verbracht habe, herauszufinden , wie ich meine Bedeutung erklären kann, um das herauszufinden , während ich die Frage erneut las. Diese Frage endete „[Erzeugen] Diskussion eher als Antworten“ und endete bei einem Gewicht von ~ 18K von Text (für die Frage allein, um nur klar zu sein) , die lange auch nur für eine Blog - Post wären.

Aber StackExchange ist nicht Ihre Seifenkiste und auch nicht Ihr Blog. Tatsächlich haben Sie es jedoch als mindestens einen Teil von beiden verwendet. Die Leute haben viel Zeit damit verbracht, Ihr "To-Point-Out" zu beantworten, anstatt die eigentlichen Fragen der Leute zu beantworten. An dieser Stelle werde ich die Frage als nicht passend für unser Format kennzeichnen, da das OP ausdrücklich erklärt hat, dass es überhaupt nicht beabsichtigt war, eine Frage zu sein.

An diesem Punkt bin ich nicht sicher, ob meine Antwort auf den Punkt war oder nicht; wahrscheinlich nicht, aber es war auf einige Ihrer Fragen gerichtet, und vielleicht kann es eine nützliche Antwort für jemand anderen sein; Anfänger haben Mut, einige dieser "nicht" verwandeln sich in "manchmal", wenn Sie mehr Erfahrung haben. :)

Generell...

bitte verzeihen Sie verbleibende Ecken und Kanten; Ich habe schon viel zu viel Zeit damit verbracht ... anstatt das OP direkt zu zitieren (wie ursprünglich beabsichtigt), werde ich versuchen, es zusammenzufassen und zu paraphrasieren.

[weitgehend überarbeitet von meiner ursprünglichen Antwort] Nach Prüfung glaube ich, dass ich die Betonung, die das OP auf die von mir beantworteten Fragen legte
, falsch gelesen habe; Die angesprochenen Punkte wurden jedoch angesprochen , und ich habe die Antworten größtenteils unangetastet gelassen, da ich glaube, dass sie auf den Punkt gebracht sind und Probleme ansprechen, die ich auch in anderen Zusammenhängen in Bezug auf Ratschläge für Anfänger angesprochen habe.

In dem ursprünglichen Beitrag wurde auf verschiedene Weise gefragt, warum verschiedene Artikel Ratschläge gaben, z. B. "Keine lsAusgabe analysieren " oder "Sie sollten keine lsAusgabe analysieren" .

Mein Lösungsvorschlag für dieses Problem lautet, dass Beispiele für solche Aussagen lediglich Beispiele für ein leicht anders formuliertes Idiom sind, bei dem ein absoluter Quantifikator mit einem Imperativ gepaart wird [z. B. «nicht [je] X», «[Du solltest] immer Y», «[man sollte] nie Z»], um Aussagen zu bilden, die als allgemeine Regeln oder Richtlinien verwendet werden sollen, insbesondere wenn sie denjenigen gegeben werden, die neu in einem Fach sind, anstatt als absolute Wahrheiten gedacht zu sein ungeachtet der offensichtlichen Form dieser Aussagen.

Wenn Sie anfangen, neue Themen zu erlernen, und wenn Sie nicht genau wissen, warum Sie etwas anderes tun müssen, ist es eine gute Idee, einfach die anerkannten allgemeinen Regeln ausnahmslos zu befolgen - es sei denn, Sie werden von einer erfahrenen Person angeleitet das selbst. Mit zunehmender Kompetenz und Erfahrung können Sie außerdem feststellen, wann und ob in einer bestimmten Situation eine Regel gilt. Sobald Sie ein signifikantes Erfahrungsniveau erreicht haben, werden Sie wahrscheinlich zuerst die Gründe für die allgemeine Regel verstehen, und an diesem Punkt können Sie beginnen, Ihr Urteil darüber zu fällen, ob und auf welcher Ebene die Gründe für die Regel zutreffen diese Situation, und auch, ob es vielleicht übergeordnete Bedenken gibt.

Und dann könnte sich ein Experte vielleicht dafür entscheiden, Dinge zu tun, die gegen "The Rules" verstoßen. Aber das würde sie nicht weniger zu "The Rules" machen.

Und damit zum vorliegenden Thema: Meiner Ansicht nach sehe ich, nur weil ein Experte in der Lage sein könnte, gegen diese Regel zu verstoßen, ohne völlig niedergeschlagen zu werden, keine Möglichkeit, einem Anfänger zu rechtfertigen, dass dies "manchmal" der Fall ist okay, um die lsAusgabe zu analysieren , weil: es ist nicht . Zumindest ist es für Anfänger nicht richtig, dies zu tun.

Sie legen Ihre Bauern immer in die Mitte; in der Eröffnung ein Stück, ein Zug; Schloss zum frühestmöglichen Zeitpunkt; Ritter vor Bischöfen; ein ritter am rand ist grimmig; und stellen Sie immer sicher, dass Sie Ihre Berechnung bis zum Ende durchschauen können! (Hoppla, tut mir leid, ich werde müde, das ist für den Schach-StackExchange.)

Regeln, die gebrochen werden sollen?

Wenn Sie einen Artikel zu einem Thema lesen, das sich an Anfänger richtet oder wahrscheinlich von diesen gelesen wird, werden Sie häufig Folgendes sehen:

  • "Du solltest niemals X machen."
  • "Tu niemals Q!"
  • "Tu nicht Z."
  • "Man sollte immer Y machen!"
  • "C, egal was."

Zwar scheinen diese Aussagen absolut und zeitlos zu sein, aber sie sind es nicht. Stattdessen ist dies eine Möglichkeit, allgemeine Regeln (aka "Richtlinien", "Faustregeln", "Grundlagen" usw.) aufzustellen, die zumindest für die Anfänger, die diese Artikel lesen, eine geeignete Möglichkeit darstellen. Nur weil sie als absolut angegeben sind, binden die Regeln Fachleute und Experten nicht [die wahrscheinlich diejenigen waren, die diese Regeln überhaupt erst zusammengefasst haben, um die im Laufe der Wiederholung gewonnenen Erkenntnisse aufzuzeichnen und weiterzugeben Probleme in ihrem speziellen Handwerk.]

Diese Regeln werden sicherlich nicht verraten, wie ein Experte mit einem komplexen oder nuancierten Problem umgehen würde, bei dem diese Regeln beispielsweise im Widerspruch zueinander stehen. oder bei denen die Bedenken, die überhaupt zur Regel geführt haben, einfach nicht zutreffen. Experten haben keine Angst davor (oder sollten keine Angst davor haben!), Einfach Regeln zu brechen, von denen sie zufällig wissen, dass sie in einer bestimmten Situation keinen Sinn ergeben. Experten haben ständig damit zu tun, verschiedene Risiken und Bedenken in ihrem Handwerk auszugleichen, und müssen häufig ihr Urteilsvermögen einsetzen, um diese Art von Regeln zu brechen. Sie müssen verschiedene Faktoren ausbalancieren und dürfen sich nicht nur auf eine Tabelle mit zu befolgenden Regeln verlassen. Nehmen Sie Gotoals Beispiel: Es gab eine lange, wiederkehrende Debatte darüber, ob sie schädlich sind. (Ja, nicht immer gotos verwenden;. D)

Ein modaler Vorschlag

Ein merkwürdiges Merkmal, zumindest auf Englisch, und ich stelle mir in vielen anderen Sprachen allgemeine Regeln vor, ist, dass sie in der gleichen Form wie ein Modalvorschlag angegeben sind, die Experten auf einem Gebiet jedoch bereit sind, eine allgemeine Regel für a anzugeben Situation, die ganze Zeit zu wissen, dass sie die Regel brechen, wenn dies angebracht ist. Es ist daher klar, dass diese Anweisungen nicht den gleichen Anweisungen in der modalen Logik entsprechen sollen.

Deshalb sage ich, dass sie einfach idiomatisch sein müssen. Anstatt wirklich eine "nie" - oder "immer" -Situation zu sein, dienen diese Regeln in der Regel dazu, allgemeine Richtlinien zu kodifizieren, die für eine Vielzahl von Situationen geeignet sind und die, wenn Anfänger sie blind befolgen, wahrscheinlich weitreichende Folgen haben bessere Ergebnisse als der Anfänger, der sich ohne guten Grund gegen sie entscheidet. Manchmal kodifizieren sie Regeln, was einfach zu minderwertigen Ergebnissen führt und nicht zu den völligen Fehlern, die mit falschen Entscheidungen einhergehen, wenn sie gegen die Regeln verstoßen.

Allgemeine Regeln sind also nicht die absoluten Modalsätze, die sie an der Oberfläche zu sein scheinen, sondern eine Kurzform, um die Regel mit einem implizierten Standard-Boilerplate zu beschreiben, etwa wie folgt:

es sei denn, Sie können feststellen, dass diese Richtlinie in einem bestimmten Fall falsch ist, und sich selbst beweisen, dass Sie Recht haben, dann $ {RULE}

Wobei Sie natürlich lsanstelle von $ {RULE} "Niemals die Ausgabe analysieren " ersetzen könnten . :)

Oh ja! Was über Parsing lsOutput?

Nun, angesichts all dessen ... ich denke, es ist ziemlich klar, dass diese Regel eine gute ist. Zunächst muss die reale Regel als idiomatisch verstanden werden, wie oben erläutert ...

Darüber hinaus muss man nicht nur sehr gut mit Shell-Skripten umgehen können, um zu wissen, ob sie in bestimmten Fällen fehlerhaft sein können. Es ist auch wichtig, dass man genau so gut weiß, dass man etwas falsch gemacht hat, wenn man versucht, es beim Testen zu brechen! Und ich sage zuversichtlich, dass eine sehr große Mehrheit des wahrscheinlichen Publikums solcher Artikel (mit Ratschlägen wie „Analysieren Sie nicht die Ausgabe von ls!“) Diese Dinge nicht kann , und diejenigen, die über solche Fähigkeiten verfügen, werden dies wahrscheinlich erkennen sie finden es selbst heraus und ignorieren die Regel trotzdem.

Aber ... sehen Sie sich diese Frage an und wie selbst Leute, die wahrscheinlich die Fähigkeit haben, es für einen schlechten Ruf hielten, dies zu tun; und wie viel Mühe der Verfasser der Frage aufgewendet hat, um an einen Punkt des aktuell besten Beispiels zu gelangen! Ich garantiere Ihnen, dass bei einem so schweren Problem 99% der Leute da draußen falsch liegen und möglicherweise sehr schlechte Ergebnisse erzielen! Auch wenn sich die Methode als gut herausstellt; Bis eine (oder eine andere) lsAnalyseidee von den IT- / Entwicklerleuten als Ganzes übernommen wird, viele Tests (insbesondere Zeittests) übersteht und es schließlich schafft, den Status einer "gemeinsamen Technik" zu erreichen, ist es wahrscheinlich, dass a Viele Leute könnten es versuchen und es falsch machen ... mit katastrophalen Folgen.

So werde ich ein letztes Mal wiederholen .... dass, besonders in diesem Fall , dass deshalb „ nie analysieren lsAusgang!“ ist definitiv der richtige Weg, um es auszudrücken.

[UPDATE 2014-05-18: Begründung für die Beantwortung (oben) geklärt, um auf einen Kommentar von OP zu antworten; Der folgende Zusatz ist eine Antwort auf die Ergänzungen des OP zu der Frage von gestern.]

[UPDATE 2014-11-10: Header hinzugefügt und Inhalte neu organisiert / überarbeitet; und auch: Neuformatierung, Neuformulierung, Klärung und ähm ... "prägnantes" ... ich wollte, dass dies einfach eine Bereinigung ist, obwohl es sich in eine Art Überarbeitung verwandelt hat. Ich hatte es in einem traurigen Zustand hinterlassen, also habe ich hauptsächlich versucht, ihm einen Auftrag zu erteilen. Ich fand es wichtig, den ersten Abschnitt weitgehend intakt zu lassen. also nur zwei kleinere Änderungen, überflüssig "aber" entfernt und "das" hervorgehoben.]

† Ich beabsichtigte dies ursprünglich nur als Klarstellung meines Originals; aber entschied sich für andere Ergänzungen nach Überlegung

‡ Richtlinien zu Beiträgen finden Sie unter https://unix.stackexchange.com/tour

Shelleybutterfly
quelle
2
Niemals ist nicht idiomatisch. Dies ist keine Antwort auf irgendetwas.
mikeserv
1
Hmm. Nun, ich wusste nicht, ob diese Antwort zufriedenstellend sein würde , aber ich hatte absolut nicht damit gerechnet, dass sie kontrovers sein würde . Und ich wollte nicht argumentieren, dass 'nie' per se idiomatisch ist; aber das "Mach niemals X!" ist eine idiomatische Verwendung . Ich sehe zwei allgemeine Fälle, die zeigen können, dass 'Never / don't parse ls!' Richtige Ratschläge: 1. Zeigen Sie (zu Ihrer Zufriedenheit), dass für jeden Anwendungsfall, in dem eine lsAusgabe analysiert werden kann, eine andere Lösung verfügbar ist, die in gewisser Weise überlegen ist, ohne dies zu tun. 2. zeigen Sie, dass die Aussage in den genannten Fällen keine wörtliche ist.
Shelleybutterfly
Wenn Sie Ihre Frage noch einmal betrachten, sehen Sie, dass Sie zuerst "nicht ..." und nicht "nie ..." erwähnen, was in Ihrer Analyse gut auftaucht. Deshalb werde ich dies auch klarstellen. An dieser Stelle gibt es bereits eine Lösung der ersten Art, die anscheinend zu Ihrer Zufriedenheit demonstriert / erklärt wird, so dass ich nicht weiter darauf eingehen werde. Aber ich werde versuchen, meine Antwort ein wenig zu klären: Wie ich schon sagte, ich habe nicht versucht, kontrovers (oder konfrontativ!) Zu sein, sondern darauf hinzuweisen, wie diese Aussagen im Allgemeinen beabsichtigt sind.
Shelleybutterfly
1
Ich sollte diesen Beitrag aufräumen. Noch nie ist nicht der richtige Weg , es zu Ausdruck. Es ist ein bisschen lächerlich, dass die Leute denken, sie wären dafür qualifiziert, anderen niemals etwas zu erzählen oder nicht - sagen Sie ihnen einfach, dass Sie nicht glauben, dass es funktionieren wird und warum, aber Sie wissen, was funktionieren wird und warum. lsist ein Computerprogramm - Sie können Computerausgaben
mikeserv
1
Nun, ich habe meine Gegenstimme umgekehrt, weil du zumindest recht hast, was das Markieren betrifft. Ich versuche es heute Nacht oder morgen aufzuräumen. Mein Gedanke ist, dass ich die meisten Codebeispiele auf eine Antwort verschieben werde, die ich schätze. Aber es entschuldigt, soweit es mich betrifft, immer noch nicht die Ungenauigkeiten in diesem oft zitierten Blog-Beitrag. Ich wünschte, die Leute würden ganz aufhören, das Bash-Handbuch zu zitieren - zumindest nicht, bis sie die POSIX-Spezifikationen zitiert haben ...
mikeserv
16

Ist es möglich, die Ausgabe von lsin bestimmten Fällen zu analysieren ? Sicher. Die Idee, eine Liste von Inode-Nummern aus einem Verzeichnis zu extrahieren, ist ein gutes Beispiel - wenn Sie wissen, dass die Implementierung dies lsunterstützt -qund daher jede Datei genau eine Ausgabezeile erzeugt und Sie nur die Inode-Nummern benötigen, aus denen sie analysiert werden ls -Rai1qAusgabe ist sicherlich eine mögliche Lösung. Hätte der Autor zuvor keinen Rat wie "Niemals die Ausgabe von ls analysieren" gesehen, würde er wahrscheinlich nicht über Dateinamen mit Zeilenumbrüchen nachdenken und wahrscheinlich das "q" als Ergebnis und das "ls" weglassen Code würde in diesem Edge-Fall auf subtile Weise beschädigt - selbst in Fällen, in denen lsdie Ausgabe des Parsings sinnvoll ist, ist dieser Ratschlag dennoch nützlich.

Der umfassendere Punkt ist, dass, wenn ein Neuling in der Shell-Skripterstellung versucht, ein Skript herauszufinden (zum Beispiel), was die größte Datei in einem Verzeichnis ist oder was die zuletzt geänderte Datei in einem Verzeichnis ist, sein erster Instinkt darin besteht ls, s zu analysieren output - verständlich, weil lses eines der ersten Kommandos ist, das ein Neuling lernt.

Leider ist dieser Instinkt falsch und dieser Ansatz ist gebrochen. Noch bedauerlicher ist, dass es auf subtile Weise kaputt ist - es wird die meiste Zeit funktionieren, aber in Randfällen, die möglicherweise von jemandem mit Kenntnissen des Codes ausgenutzt werden könnten, scheitern.

Der Neuling könnte sich ls -s | sort -n | tail -n 1 | awk '{print $2}'eine Möglichkeit vorstellen, die größte Datei in einem Verzeichnis abzurufen. Und es funktioniert, bis Sie eine Datei mit einem Leerzeichen im Namen haben.

OK, wie wäre es ls -s | sort -n | tail -n 1 | sed 's/[^ ]* *[0-9]* *//'? Funktioniert einwandfrei, bis Sie eine Datei mit einer neuen Zeile im Namen haben.

Hilft das Hinzufügen -qvon lsArgumenten zu, wenn der Dateiname eine neue Zeile enthält? Möglicherweise sieht es so aus, bis Sie zwei verschiedene Dateien haben, die ein nicht druckbares Zeichen an derselben Stelle im Dateinamen enthalten. In der lsAusgabe können Sie dann nicht unterscheiden, welche der Dateien die größten waren. Schlimmer noch, um das "?" Zu erweitern, greift er wahrscheinlich auf seine Shell zurück eval- was zu Problemen führt, wenn er eine Datei mit dem Namen "?"

foo`/tmp/malicious_script`bar

Hilft das --quoting-style=shell(wenn Ihr es lsüberhaupt unterstützt)? Nein, zeigt noch? Bei nicht druckbaren Zeichen ist es also immer noch nicht eindeutig, welche der mehreren Übereinstimmungen die größte war. --quoting-style=literal? Nein, das gleiche. --quoting-style=localeoder --quoting-style=ckönnte helfen, wenn Sie nur den Namen der größten Datei eindeutig drucken müssen, aber wahrscheinlich nicht, wenn Sie danach etwas mit der Datei tun müssen - es wäre ein Haufen Code, um das Anführungszeichen rückgängig zu machen und zum wirklichen Dateinamen zurückzukehren dass Sie es weitergeben können, sagen wir, gzip.

Und am Ende all dieser Arbeiten, auch wenn das, was er hat, sicher und korrekt für alle möglichen Dateinamen ist, ist es unlesbar und nicht mehr zu pflegen und hätte viel einfacher, sicherer und lesbarer in Python oder Perl oder Ruby gemacht werden können.

Oder sogar mit anderen Shell-Werkzeugen - von oben nach unten, ich denke, das sollte den Trick machen:

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

Und sollte mindestens so tragbar sein, wie es --quoting-styleist.

godlygeek
quelle
Oh, wahr in Bezug auf die Größe - ich könnte das wahrscheinlich tun, wenn ich es versuchen würde - sollte ich? Ich bin ein bisschen müde oder das Ganze - ich mag Ihre Antwort, weil Sie nicht sagen können oder nicht oder nie, aber tatsächlich Beispiele geben, warum vielleicht nicht und vergleichbar, wie sonst - danke.
mikeserv
Ich denke, wenn Sie es versuchen würden, würden Sie feststellen, dass es viel schwieriger ist, als Sie denken. Also, ja, ich würde empfehlen es zu versuchen. Ich gebe gerne Dateinamen weiter, die für Sie nicht mehr funktionieren, solange ich an sie denken kann. :)
godlygeek
Kommentare sind nicht für eine längere Diskussion gedacht. Diese Unterhaltung wurde in den Chat verschoben .
Terdon
@mikeserv und godlygeek, ich habe diesen Kommentarthread in den Chat verschoben . Bitte hab keine langen Diskussionen wie diese in den Kommentaren, dafür ist der Chat gedacht.
Terdon