(8 Antworten)
Vor 3 Jahren geschlossen.
Ich habe das folgende Skript geschrieben, um die Ausgaben von zwei Verzeichnissen mit denselben Dateien als solche zu unterscheiden:
#!/bin/bash
for file in `find . -name "*.csv"`
do
echo "file = $file";
diff $file /some/other/path/$file;
read char;
done
Ich weiß, dass es andere Möglichkeiten gibt, dies zu erreichen. Seltsamerweise schlägt dieses Skript jedoch fehl, wenn die Dateien Leerzeichen enthalten. Wie kann ich damit umgehen?
Beispielausgabe von find:
./zQuery - abc - Do Not Prompt for Date.csv
Akzeptierte Antwort:
Kurze Antwort (kommt Ihrer Antwort am nächsten, verarbeitet aber Leerzeichen)
OIFS="$IFS"
IFS=$'n'
for file in `find . -type f -name "*.csv"`
do
echo "file = $file"
diff "$file" "/some/other/path/$file"
read line
done
IFS="$OIFS"
Bessere Antwort (verarbeitet auch Platzhalter und Zeilenumbrüche in Dateinamen)
find . -type f -name "*.csv" -print0 | while IFS= read -r -d '' file; do
echo "file = $file"
diff "$file" "/some/other/path/$file"
read line </dev/tty
done
Beste Antwort (basierend auf der Antwort von Gilles)
find . -type f -name '*.csv' -exec sh -c '
file="$0"
echo "$file"
diff "$file" "/some/other/path/$file"
read line </dev/tty
' exec-sh {} ';'
Oder noch besser, um zu vermeiden, dass ein sh
ausgeführt wird pro Datei:
find . -type f -name '*.csv' -exec sh -c '
for file do
echo "$file"
diff "$file" "/some/other/path/$file"
read line </dev/tty
done
' exec-sh {} +
Lange Antwort
Sie haben drei Probleme:
- Standardmäßig teilt die Shell die Ausgabe eines Befehls auf Leerzeichen, Tabulatoren und Zeilenumbrüche auf
- Dateinamen könnten Platzhalterzeichen enthalten, die erweitert würden
- Was ist, wenn es ein Verzeichnis gibt, dessen Name auf
*.csv
endet? ?
1. Teilen nur bei Zeilenumbrüchen
Um herauszufinden, was file
einzustellen ist Dazu muss die Shell die Ausgabe von find
übernehmen und irgendwie interpretieren, sonst file
wäre nur die gesamte Ausgabe von find
.
Die Shell liest den IFS
Variable, die auf <space><tab><newline>
gesetzt ist standardmäßig.
Dann schaut es sich jedes Zeichen in der Ausgabe von find
an . Sobald es irgendein Zeichen sieht, das in IFS
ist , denkt es, dass dies das Ende des Dateinamens markiert, also setzt es file
zu den Zeichen, die es bisher gesehen hat, und führt die Schleife aus. Dann beginnt es dort, wo es aufgehört hat, um den nächsten Dateinamen zu erhalten, und führt die nächste Schleife usw. aus, bis es das Ende der Ausgabe erreicht.
Es macht also effektiv Folgendes:
for file in "zquery" "-" "abc" ...
Um es anzuweisen, die Eingabe nur bei Zeilenumbrüchen aufzuteilen, müssen Sie Folgendes tun
IFS=$'n'
vor Ihrem for ... find
Befehl.
Das setzt IFS
zu einem einzelnen Zeilenumbruch, so dass es nur bei Zeilenumbrüchen geteilt wird und nicht auch bei Leerzeichen und Tabulatoren.
Wenn Sie sh
verwenden oder dash
statt ksh93
, bash
oder zsh
, müssen Sie IFS=$'n'
schreiben stattdessen so:
IFS='
'
Das reicht wahrscheinlich aus, um Ihr Skript zum Laufen zu bringen, aber wenn Sie daran interessiert sind, einige andere Sonderfälle richtig zu handhaben, lesen Sie weiter …
2. Erweitern von $file
ohne Platzhalter
Innerhalb der Schleife, wo Sie tun
diff $file /some/other/path/$file
die Shell versucht, $file
zu expandieren (wieder!).
Es könnte Leerzeichen enthalten, aber da wir bereits IFS
gesetzt haben oben, das ist hier kein Problem.
Er kann aber auch Platzhalterzeichen wie *
enthalten oder ?
, was zu unvorhersehbarem Verhalten führen würde. (Danke an Gilles für den Hinweis.)
Um der Shell mitzuteilen, dass Platzhalterzeichen nicht erweitert werden sollen, setzen Sie die Variable in doppelte Anführungszeichen, z. B.
diff "$file" "/some/other/path/$file"
Dasselbe Problem könnte auch uns beißen
for file in `find . -name "*.csv"`
Wenn Sie zum Beispiel diese drei Dateien hätten
file1.csv
file2.csv
*.csv
(sehr unwahrscheinlich, aber dennoch möglich)
Verwandte:Wenn ich Berechtigungen für eine TAR-Datei ändere, gilt das für die darin enthaltenen Dateien?Es wäre, als ob Sie weggelaufen wären
for file in file1.csv file2.csv *.csv
die zu
erweitert wirdfor file in file1.csv file2.csv *.csv file1.csv file2.csv
verursacht file1.csv
und file2.csv
zweimal verarbeitet werden.
Stattdessen müssen wir tun
find . -name "*.csv" -print | while IFS= read -r file; do
echo "file = $file"
diff "$file" "/some/other/path/$file"
read line </dev/tty
done
read
liest Zeilen von der Standardeingabe, zerlegt die Zeile gemäß IFS
in Wörter und speichert sie in den von Ihnen angegebenen Variablennamen.
Hier teilen wir ihm mit, die Zeile nicht in Wörter aufzuteilen und die Zeile in $file
zu speichern .
Beachten Sie auch, dass read line
wurde in read line </dev/tty
geändert .
Dies liegt daran, dass innerhalb der Schleife die Standardeingabe von find
kommt über die Pipeline.
Wenn wir nur read
hätten , es würde einen Teil oder den gesamten Dateinamen verbrauchen, und einige Dateien würden übersprungen.
/dev/tty
ist das Terminal, von dem aus der Benutzer das Skript ausführt. Beachten Sie, dass dies einen Fehler verursacht, wenn das Skript über Cron ausgeführt wird, aber ich nehme an, dass dies in diesem Fall nicht wichtig ist.
Was ist dann, wenn ein Dateiname Zeilenumbrüche enthält?
Wir können das handhaben, indem wir -print
ändern zu -print0
und mit read -d ''
am Ende einer Pipeline:
find . -name "*.csv" -print0 | while IFS= read -r -d '' file; do
echo "file = $file"
diff "$file" "/some/other/path/$file"
read char </dev/tty
done
Dies macht find
Fügen Sie am Ende jedes Dateinamens ein Null-Byte ein. Nullbytes sind die einzigen Zeichen, die in Dateinamen nicht erlaubt sind, also sollte dies alle möglichen Dateinamen behandeln, egal wie seltsam.
Um den Dateinamen auf der anderen Seite zu erhalten, verwenden wir IFS= read -r -d ''
.
Wo wir read
verwendet haben oben haben wir das standardmäßige Zeilentrennzeichen von newline verwendet, aber jetzt find
verwendet null als Zeilentrennzeichen. In bash
, können Sie kein NUL-Zeichen in einem Argument an einen Befehl übergeben (auch nicht an eingebaute), aber bash
versteht -d ''
im Sinne von durch NUL getrennt . Also verwenden wir -d ''
um read
zu machen Verwenden Sie denselben Zeilenbegrenzer wie find
. Beachten Sie, dass -d $'