Ich möchte eine Liste von Dateien durchlaufen. Diese Liste ist das Ergebnis eines find
Befehls, daher habe ich Folgendes gefunden:
getlist() {
for f in $(find . -iname "foo*")
do
echo "File found: $f"
# do something useful
done
}
Es ist in Ordnung, außer wenn eine Datei Leerzeichen in ihrem Namen hat:
$ ls
foo_bar_baz.txt
foo bar baz.txt
$ getlist
File found: foo_bar_baz.txt
File found: foo
File found: bar
File found: baz.txt
Was kann ich tun, um die Aufteilung auf Leerzeichen zu vermeiden?
Antworten:
Sie können die wortbasierte Iteration durch eine zeilenbasierte ersetzen:
quelle
touch "$(printf "foo\nbar")"
IFS= while read -r f
stattdessen, um eine Interpretation der Eingabe (Backslashes, führende und nachfolgende Leerzeichen) zu verhindern.find
und einer while-Schleife.-exec
wird es sauberer sein als eine explizite Schleife :find . -iname "foo*" -exec echo "File found: {}" \;
. Außerdem können Sie in vielen Fällen den letzten\;
durch ersetzen,+
um viele Dateien in einem Befehl abzulegen.Es gibt verschiedene praktikable Möglichkeiten, um dies zu erreichen.
Wenn Sie sich eng an Ihre Originalversion halten möchten, können Sie dies folgendermaßen tun:
Dies schlägt immer noch fehl, wenn Dateinamen wörtliche Zeilenumbrüche enthalten, Leerzeichen diese jedoch nicht beschädigen.
Es ist jedoch nicht erforderlich, mit IFS zu spielen. Hier ist meine bevorzugte Methode, um dies zu tun:
Wenn Sie die
< <(command)
Syntax nicht kennen, sollten Sie sich über die Prozessersetzung informieren . Dies hat den Vorteil,for file in $(find ...)
dass Dateien mit Leerzeichen, Zeilenumbrüchen und anderen Zeichen korrekt behandelt werden. Dies funktioniert, weilfind
with-print0
einnull
(aka\0
) als Abschlusszeichen für jeden Dateinamen verwendet und im Gegensatz zu newline null kein zulässiges Zeichen in einem Dateinamen ist.Der Vorteil gegenüber der nahezu gleichwertigen Version
Ist das eine Variablenzuweisung im Körper der while-Schleife erhalten bleibt. Das heißt, wenn Sie
while
wie obenwhile
pfeifen, befindet sich der Körper des in einer Unterschale, die möglicherweise nicht das ist, was Sie wollen.Der Vorteil der Prozessersetzungsversion gegenüber der Version
find ... -print0 | xargs -0
ist minimal: Diexargs
Version ist in Ordnung, wenn Sie lediglich eine Zeile drucken oder einen einzelnen Vorgang für die Datei ausführen müssen. Wenn Sie jedoch mehrere Schritte ausführen müssen, ist die Schleifenversion einfacher.BEARBEITEN : Hier ist ein schönes Testskript, damit Sie sich ein Bild über den Unterschied zwischen verschiedenen Versuchen machen können, dieses Problem zu lösen
quelle
$IFS
und die< <(cmd)
Syntax. Trotzdem bleibt mir eines unklar, warum das$
in$'\0'
? Vielen Dank.while IFS= read
... hinzufügen , um Dateien zu verarbeiten, die mit Leerzeichen beginnen oder enden.bash
damit ich mich mit Bash-spezifischen Funktionen sicher fühlte. Die Prozessersetzung ist nicht auf andere Shells übertragbar (sh selbst wird wahrscheinlich nie ein so bedeutendes Update erhalten).IFS=$'\n'
mit wirdfor
die zeileninterne Wortteilung verhindert, die resultierenden Zeilen werden jedoch weiterhin einem Globbing unterzogen, sodass dieser Ansatz nicht vollständig robust ist (es sei denn, Sie deaktivieren auch zuerst das Globbing). Während esread -d $'\0'
funktioniert, ist es insofern leicht irreführend, als es darauf hindeutet, dass Sie$'\0'
NULs erstellen können - Sie können nicht: a\0
in einer ANSI-Zeichenfolge mit C-Anführungszeichen beendet die Zeichenfolge effektiv , sodass dies praktisch-d $'\0'
dasselbe ist wie-d ''
.Es gibt auch eine sehr einfache Lösung: Verlassen Sie sich auf Bash Globbing
Beachten Sie, dass ich nicht sicher bin, ob dieses Verhalten das Standardeinstellung ist, aber ich sehe keine spezielle Einstellung in meinem Shopt, also würde ich sagen, dass es "sicher" sein sollte (getestet auf OSX und Ubuntu).
quelle
quelle
Siehe
man xargs
.quelle
Da Sie mit keiner anderen Art der Filterung arbeiten
find
, können Sie abbash
4.0 Folgendes verwenden :Das entspricht
**/
null oder mehr Verzeichnissen, sodass das vollständige Musterfoo*
im aktuellen Verzeichnis oder in einem beliebigen Unterverzeichnis übereinstimmt .quelle
Ich mag Loops und Array-Iterationen sehr, daher denke ich, dass ich diese Antwort dem Mix hinzufügen werde ...
Ich mochte auch Marchelblings dummes Dateibeispiel. :) :)
Im Testverzeichnis:
Dadurch wird jede Dateilistenzeile in ein Bash-Array
arr
eingefügt, dessen Name mit einer nachgestellten neuen Zeile entfernt wird.Nehmen wir an, wir möchten diesen Dateien bessere Namen geben ...
$ {! arr [@]} wird auf 0 1 2 erweitert, sodass "$ {arr [$ i]}" das i- te Element des Arrays ist. Die Anführungszeichen um die Variablen sind wichtig, um die Leerzeichen zu erhalten.
Das Ergebnis sind drei umbenannte Dateien:
quelle
find
hat ein-exec
Argument, das die Suchergebnisse durchläuft und einen beliebigen Befehl ausführt. Beispielsweise:Hier werden
{}
die gefundenen Dateien dargestellt. Wenn Sie sie einschließen,""
kann der resultierende Shell-Befehl Leerzeichen im Dateinamen verarbeiten.In vielen Fällen können Sie den letzten
\;
(der einen neuen Befehl startet) durch einen ersetzen\+
, der mehrere Dateien in einen Befehl einfügt (jedoch nicht unbedingt alle gleichzeitig, sieheman find
für weitere Details).quelle
In einigen Fällen, wenn Sie nur eine Liste von Dateien kopieren oder verschieben müssen, können Sie diese Liste auch an awk weiterleiten.
Wichtig ist das
\"" "\"
Feld$0
(kurz Ihre Dateien, eine Zeilenliste = eine Datei).quelle
Ok - mein erster Beitrag zu Stack Overflow!
Obwohl meine Probleme damit immer darin bestanden haben, dass die von mir vorgestellte Lösung nicht in beiden Fällen funktioniert, bin ich mir sicher, dass sie in beiden Fällen funktionieren wird. Das Problem liegt in der Interpretation der "ls" -Renditen durch die Shell. Wir können "ls" aus dem Problem entfernen, indem wir einfach die Shell-Erweiterung des
*
Platzhalters verwenden. Dies führt jedoch zu einem "no match" -Fehler, wenn sich keine Dateien im aktuellen (oder angegebenen Ordner) befinden. Um dies zu umgehen, erweitern wir einfach das Erweiterung um Punktdateien also:* .*
- Dies führt immer zu Ergebnissen seit den Dateien. und .. wird immer anwesend sein. Also können wir in csh dieses Konstrukt verwenden ...Wenn Sie die Standard-Punktdateien herausfiltern möchten, ist das einfach genug ...
Der Code im ersten Beitrag zu diesem Thread würde folgendermaßen geschrieben:
Hoffe das hilft!
quelle
Eine andere Lösung für den Job ...
Ziel war:
quelle