(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
*.csvendet? ?
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 $'