Wie analysiere ich die Ausgabe des Befehls find, wenn Dateinamen Leerzeichen enthalten?

12

Verwenden einer Schleife wie

for i in `find . -name \*.txt` 

wird unterbrochen, wenn einige Dateinamen Leerzeichen enthalten.

Mit welcher Technik kann ich dieses Problem vermeiden?

Scott C Wilson
quelle
1
Beachten Sie, dass Dateien auch Zeilenumbrüche in ihrem Dateinamen enthalten können. Deshalb gibt es find -print0und xargs -0.
Daniel Beck

Antworten:

12

Im Idealfall machen Sie das überhaupt nicht so, da es immer schwierig ist, Dateinamen in einem Shell-Skript richtig zu analysieren (korrigieren Sie es für Leerzeichen, Sie haben immer noch Probleme mit anderen eingebetteten Zeichen, insbesondere Zeilenumbrüchen). Dies ist sogar als erster Eintrag auf der BashPitfalls-Seite aufgeführt.

Es gibt jedoch eine Möglichkeit, fast das zu tun, was Sie wollen:

oIFS=$IFS
IFS=$'\n'

find . -name '*.txt' | while read -r i; do
  # use "$i" with whatever you're doing
done

IFS=$oIFS

Denken Sie daran, auch $ibei der Verwendung zu zitieren , um zu vermeiden, dass andere Dinge die Leerzeichen später interpretieren. Denken Sie auch daran, $IFSnach der Verwendung zurückzusetzen, da dies später zu verwirrenden Fehlern führen kann.

Dies hat noch eine weitere Einschränkung: Was innerhalb der whileSchleife passiert , kann in einer Subshell stattfinden, abhängig von der genauen Shell, die Sie verwenden, sodass die variablen Einstellungen möglicherweise nicht bestehen bleiben. Die forLoop-Version vermeidet dies, aber zu dem Preis, dass $IFSSie selbst dann Probleme bekommen, wenn Sie findzu viele Dateien zurückgeben , selbst wenn Sie die Lösung anwenden , um Probleme mit Leerzeichen zu vermeiden .

Irgendwann wird die richtige Lösung für all dies in einer Sprache wie Perl oder Python anstelle von Shell ausgeführt.

Geekosaurier
quelle
1
Ich mag die Idee, nur Python zu verwenden, um all dies zu vermeiden.
Scott C Wilson
12

Verwenden find -print0und leiten Sie xargs -0es an Ihr kleines C-Programm weiter oder schreiben Sie es an Ihr kleines C-Programm. Dafür wurden -print0und -0wurden erfunden.

Shell-Skripte sind nicht der beste Weg, um Dateinamen mit Leerzeichen zu behandeln: Sie können dies tun, aber es wird klobig.

DW
quelle
Funktioniert auf meiner Maschine ^ TM!
Mcandre
2

Sie können das "interne Feldtrennzeichen" ( IFS) auf etwas anderes als Platz für die Aufteilung des Schleifenarguments setzen, z

ORIGIFS=${IFS}
NL='
'
IFS=${NL}
for i in $(find . -name '*.txt'); do
    IFS=${ORIGIFS}
    #do stuff
done
IFS=${ORIGIFS}

Ich habe IFSnach der Verwendung in find zurückgesetzt, hauptsächlich, weil es gut aussieht, denke ich. Ich habe keine Probleme damit gesehen, es auf Newline zu setzen, aber ich denke, das ist "sauberer".

Eine andere Methode, je nachdem, was Sie mit der Ausgabe von tun möchten find, besteht darin, entweder direkt -execmit dem findBefehl zu verwenden oder ihn zu verwenden -print0und in ihn weiterzuleiten xargs -0. Im ersten Fall findwird dafür gesorgt, dass der Dateiname nicht mehr angezeigt wird. In diesem -print0Fall wird finddie Ausgabe mit einem Nulltrennzeichen gedruckt und anschließend xargsaufgeteilt. Da kein Dateiname dieses Zeichen enthalten kann (was ich weiß), ist dies auch immer sicher. Dies ist meistens in einfachen Fällen nützlich; und ist normalerweise kein guter Ersatz für eine vollständige forSchleife.

Daniel Andersson
quelle
1

Verwenden find -print0mitxargs -0

Die Verwendung in find -print0Kombination mit xargs -0ist absolut robust gegenüber legalen Dateinamen und eine der erweiterbarsten verfügbaren Methoden. Angenommen, Sie möchten eine Liste aller PDF-Dateien im aktuellen Verzeichnis. Du könntest schreiben

$ find . -iname '*.pdf' -print0 | xargs -0 -n 1 echo

Dadurch wird jedes PDF (via -iname '*.pdf') im aktuellen Verzeichnis ( .) und in jedem Unterverzeichnis gefunden und jedes davon als Argument an den echoBefehl übergeben. Da wir die -n 1Option angegeben haben, xargswird jeweils nur ein Argument an übergeben echo. Hätten wir diese Option weggelassen, xargswären so viele wie möglich an sie vorbeigekommen echo. (Sie können echo short input | xargs --show-limitssehen, wie viele Bytes in einer Befehlszeile zulässig sind.)

Was macht xargsgenau?

Wir können deutlich sehen, welche Auswirkungen dies xargsauf die Eingabe hat - und insbesondere auf die Auswirkungen -n-, indem wir ein Skript verwenden, das seine Argumente genauer wiedergibt als echo.

$ cat > echoArgs.sh <<'EOF'
#!/bin/bash
echo "Number of arguments: $#"

[[ $# -eq 0 ]] && exit

for i in $(seq 1 $#); do
    echo "Arg $i: <$1>"
    shift
done
EOF

$ find . -iname '*.pdf' -print0 | xargs -0 ./echoArgs.sh
$ find . -iname '*.pdf' -print0 | xargs -0 -n 1 ./echoArgs.sh

Beachten Sie, dass Leerzeichen und Zeilenumbrüche perfekt verarbeitet werden.

$ touch 'A space-age
new line of vending machines.pdf'
$ find . -iname '*space*' -print0 | xargs -0 -n 1 ./echoArgs.sh

Dies wäre besonders problematisch bei der folgenden allgemeinen Lösung:

chmod +x ./echoArgs.sh
for file in $(ls *spacey*); do
  ./echoArgs.sh "$file"
done
Anmerkungen
jpaugh
quelle
1

Ich bin mit den bashBashern nicht einverstanden, da sie bashzusammen mit dem * nix-Tool-Set sehr gut mit Dateien umgehen können (einschließlich solcher, deren Namen Leerzeichen enthalten).

Tatsächlich haben findSie eine genaue Kontrolle über die Auswahl der zu verarbeitenden Dateien ... Auf der Bash-Seite müssen Sie wirklich nur erkennen, dass Sie Zeichenfolgen erstellen müssen bash words. in der Regel durch Verwendung von "doppelten Anführungszeichen" oder eines anderen Mechanismus wie der Verwendung von IFS oder Finds{}

Beachten Sie, dass Sie in den meisten / vielen Situationen IFS nicht einstellen und zurücksetzen müssen. Verwenden Sie IFS einfach lokal, wie in den folgenden Beispielen gezeigt. Alle drei behandeln Leerzeichen in Ordnung. Auch brauchen Sie nicht eine „Standard“ Loop - Struktur, weil FIND \; ist effektiv eine Schleife; Fügen Sie einfach Ihre Schleifenlogik in eine Bash-Funktion ein (wenn Sie kein Standardwerkzeug aufrufen).

IFS=$'\n' find ~/ -name '*.txt' -exec  function-or-util {} \;  

Und noch zwei Beispiele

IFS=$'\n' find ~/ -name '*.txt' -exec  printf 'Hello %s\n' {} \;  
IFS=$'\n' find ~/ -name '*.txt' -exec  echo {} \+ |sed 's/home//'  

'find also allows you to pass multiple filenames as args to you script ..(if it suits your need: use+ instead\; `)

Peter.O
quelle
1
Beide Perspektiven haben eine gewisse Gültigkeit. Wenn ich nur an meinen eigenen Dateien gearbeitet habe, habe ich nur find verwendet und mich nicht darum gekümmert, da meine Dateien keine Leerzeichen (oder Zeilenumbrüche!) In ihren Namen haben. Wenn Sie jedoch mit den Dateien anderer Personen arbeiten, müssen Sie robustere Techniken verwenden.
Scott C Wilson