Bash: „find“-Ausgabe in „readarray“ umleiten

Bash: „find“-Ausgabe in „readarray“ umleiten

Ich versuche, mit nach Dateien zu suchen findund diese Dateien in ein Bash-Array zu legen, damit ich andere Operationen mit ihnen durchführen kann (z. B. lsoder grepsie). Aber ich kann nicht herausfinden, warum readarraydie Ausgabe nicht gelesen wird, findwährend sie weitergeleitet wird.

Angenommen, ich habe zwei Dateien im aktuellen Verzeichnis file1.txtund file2.txt. Die findAusgabe 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 echoAusgabe entnehmen können, ist mein Array leer.

Was genau mache ich hier also falsch? Warum wird die Ausgabe von nicht als Standardeingabe readarraygelesen 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 -tOption, 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 -dOption wurde in Bash 4.4 hinzugefügt)


¹ mit Ausnahme der letzten Pipe-Komponente bei Verwendung der lastpipeOption, 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

readarraykann auch von stdin lesen

readarray FILES <<< "$(find . -name "file*")"; echo "${#FILES[@]}"

Antwort4

Mit shopt -s lastpipeder 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 lastpipees zu einem projektweiten Standard gemacht werden kann (vielleicht haben Sie bereits ein solches gemeinsames Setup für set -uusw.). Beachten Sie auch, dass dies lastpipenur 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.

verwandte Informationen