
Ich versuche, mit nach Dateien zu suchen find
und diese Dateien in ein Bash-Array zu legen, damit ich andere Operationen mit ihnen durchführen kann (z. B. ls
oder grep
sie). Aber ich kann nicht herausfinden, warum readarray
die Ausgabe nicht gelesen wird, find
während sie weitergeleitet wird.
Angenommen, ich habe zwei Dateien im aktuellen Verzeichnis file1.txt
und file2.txt
. Die find
Ausgabe lautet dann wie folgt:
$ find . -name "file*"
./file1.txt
./file2.txt
Daher möchte ich das in ein Array weiterleiten, dessen zwei Elemente die Zeichenfolgen "./file1.txt"
und sind "./file2.txt"
(natürlich ohne Anführungszeichen).
Ich habe unter anderem Folgendes ausprobiert:
$ declare -a FILES
$ find . -name "file*" | readarray FILES
$ echo "${FILES[@]}"; echo "${#FILES[@]}"
0
Wie Sie der echo
Ausgabe entnehmen können, ist mein Array leer.
Was genau mache ich hier also falsch? Warum wird die Ausgabe von nicht als Standardeingabe readarray
gelesen und diese Zeichenfolgen werden nicht in das Array eingefügt?find
Antwort1
Bei Verwendung einer Pipeline führt Bash die Befehle in Untershells¹ aus. Daher wird das Array zwar gefüllt, aber in einer Untershell, sodass die übergeordnete Shell keinen Zugriff darauf hat. Sie möchten wahrscheinlich auch die -t
Option, diese Zeilentrennzeichen nicht in den Array-Mitgliedern zu speichern, da sie nicht Teil der Dateinamen sind.
Prozesssubstitution verwenden:
readarray -t FILES < <(find .)
Beachten Sie, dass dies bei Dateien mit Zeilenumbrüchen in den Pfaden nicht funktioniert. Sofern Sie nicht garantieren können, dass dies nicht der Fall ist, sollten Sie durch NUL getrennte Datensätze anstelle von durch Zeilenumbrüche getrennten verwenden:
readarray -td '' < <(find . -print0)
(die -d
Option wurde in Bash 4.4 hinzugefügt)
¹ mit Ausnahme der letzten Pipe-Komponente bei Verwendung der lastpipe
Option, aber das gilt nur für nicht-interaktive Aufrufe von bash
.
Antwort2
Die richtige Lösung ist:
unset a; declare -a a
while IFS= read -r -u3 -d $'\0' file; do
a+=( "$file" ) # or however you want to process each file
done 3< <(find /tmp -type f -print0)
Das ist ähnlich wieGregs BashFAQ 020erklärt ausführlich unddiese Antwort deckt.
Hat kein Problem mit Dateien mit ungewöhnlichen Namen (die kein NUL im Namen enthalten), mit Leerzeichen oder neuen Zeilen. Und das Ergebnis wird in ein Array gesetzt, was es für die weitere Verarbeitung nützlich macht.
Antwort3
readarray
kann auch von stdin lesen
readarray FILES <<< "$(find . -name "file*")"; echo "${#FILES[@]}"
Antwort4
Mit shopt -s lastpipe
der in der ursprünglichen Frage gezeigten Lösung klappt es, da dann der Teil nach dem letzten |
im selben Prozess abläuft wie der Großteil des Skripts:
shopt -s lastpipe
# This is just the code form the original question.
# Of course it could be done with -print0, etc.
declare -a FILES
find . -name "name*" | readarray -t FILES
echo find and readarray exit status: "${PIPESTATUS[@]}"
echo "${FILES[@]}"
echo "${#FILES[@]}"
Dies ist jedoch nur dann praktisch, wenn lastpipe
es zu einem projektweiten Standard gemacht werden kann (vielleicht haben Sie bereits ein solches gemeinsames Setup für set -u
usw.). Beachten Sie auch, dass dies lastpipe
nur für nicht interaktive Shells funktioniert.
Beim anderen typischen Pipe-Ersatz < <(...)
(Prozesssubstitution) besteht das Problem, dass sich nur schwer überprüfen lässt, ob der untergeordnete Prozess erfolgreich war.