web-dev-qa-db-de.com

Wie kann ich die Ergebnisse des Suchbefehls als Array in Bash speichern

Ich versuche, das Ergebnis als Arrays zu speichern ... Hier ist mein Code:

#!/bin/bash

echo "input : "
read input

echo "searching file with this pattern '${input}' under present directory"
array=`find . -name ${input}`

len=${#array[*]}
echo "found : ${len}"

i=0

while [ $i -lt $len ]
do
echo ${array[$i]}
let i++
done

Ich bekomme 2 .txt-Dateien unter dem aktuellen Verzeichnis . Daher erwarte ich '2' als Ergebnis von ${len}. Es wird jedoch 1 ..__ ausgegeben. Der Grund dafür ist, dass alle Ergebnisse von find als ein Element verwendet werden . Wie kann ich das beheben? Vielen Dank.

P.S Ich habe in Stack OverFlow mehrere Lösungen zu einem ähnlichen Problem gefunden. Es ist jedoch etwas anders, so dass ich mich bei mir bewerben kann. Ich muss das Ergebnis vor der Schleife in einer Variablen speichern. Danke noch einmal.

46
Juneyoung Oh

Hier ist eine Lösung, um die Ausgabe von find in ein bash-Array zu bekommen:

array=()
while IFS=  read -r -d $'\0'; do
    array+=("$REPLY")
done < <(find . -name "${input}" -print0)

Dies ist schwierig, da Dateinamen im Allgemeinen Leerzeichen, neue Zeilen und andere Skript-feindliche Zeichen enthalten können. Die einzige Möglichkeit, find zu verwenden und die Dateinamen sicher voneinander zu trennen, besteht in der Verwendung von -print0, bei dem die Dateinamen mit einem Nullzeichen getrennt werden. Dies wäre nicht unbequem, wenn die readarray/mapfile-Funktionen von bash nullgetrennte Zeichenketten unterstützen, dies aber nicht. Bashs read tut und das führt uns zu der Schleife darüber.

Wie es funktioniert

  1. Die erste Zeile erstellt ein leeres Array: array=()

  2. Bei jeder Ausführung der Anweisung read wird ein durch Null getrennter Dateiname aus der Standardeingabe gelesen. Die -r-Option weist read an, Backslash-Zeichen in Ruhe zu lassen. Der -d $'\0' teilt der read mit, dass die Eingabe nullgetrennt sein wird. Da wir den Namen auf read auslassen, setzt die Shell die Eingabe in den Standardnamen: REPLY.

  3. Die array+=("$REPLY")-Anweisung fügt den neuen Dateinamen an das Array array an.

  4. Die letzte Zeile kombiniert Umleitung und Befehlssubstitution, um die Ausgabe von find an die Standardeingabe der while-Schleife zu übergeben. 

Warum Prozessersatz verwenden?

Wenn wir keine Prozessersetzung verwendeten, könnte die Schleife folgendermaßen geschrieben werden:

array=()
find . -name "${input}" -print0 >tmpfile
while IFS=  read -r -d $'\0'; do
    array+=("$REPLY")
done <tmpfile
rm -f tmpfile

Im obigen Abschnitt wird die Ausgabe von find in einer temporären Datei gespeichert und diese Datei wird als Standardeingabe für die while-Schleife verwendet. Die Idee der Prozessersetzung besteht darin, solche temporären Dateien unnötig zu machen. Anstatt die while-Schleife von stdin von tmpfile abrufen zu lassen, können wir ihre stdin von <(find . -name ${input} -print0) bekommen.

Prozessersetzung ist weithin nützlich. An vielen Stellen, an denen ein Befehl read aus einer Datei verwenden möchte, können Sie anstelle des Dateinamens die Prozessersetzung <(...) angeben. Es gibt ein analoges Formular, >(...), das anstelle eines Dateinamens verwendet werden kann, bei dem der Befehl write in die Datei schreiben möchte.

Wie Arrays ist die Prozessersetzung ein Merkmal von bash und anderen erweiterten Shells. Es gehört nicht zum POSIX-Standard.

Zusätzliche Bemerkungen

Der folgende Befehl erstellt eine Shell-Variable, kein Shell-Array:

array=`find . -name "${input}"`

Wenn Sie ein Array erstellen möchten, müssen Sie die Ausgabe von find mit Parens versehen. Naiv könnte man:

array=(`find . -name "${input}"`)  # don't do this

Das Problem ist, dass die Shell eine Word-Aufteilung für die Ergebnisse von find durchführt, so dass die Elemente des Arrays nicht garantiert Ihren Vorstellungen entsprechen.

69
John1024

Wenn Sie bash 4 oder höher verwenden, können Sie Ihre Verwendung von find durch ersetzen

shopt -s globstar nullglob
array=( **/*"$input"* )

Das **-Muster, das von globstar aktiviert wird, stimmt mit 0 oder mehr Verzeichnissen überein, sodass das Muster mit einer beliebigen Tiefe im aktuellen Verzeichnis übereinstimmen kann. Ohne die Option nullglob wird das Muster (nach der Parametererweiterung) buchstäblich behandelt. Ohne Übereinstimmungen haben Sie ein Array mit einer einzelnen Zeichenfolge anstelle eines leeren Arrays.

Fügen Sie die Option dotglob auch in der ersten Zeile hinzu, wenn Sie versteckte Verzeichnisse (wie .ssh) durchsuchen und ausgeblendete Dateien (wie .bashrc) abgleichen möchten. 

11
chepner

sie können etwas wie .__ versuchen.

array=(`find . -type f | sort -r | head -2`)
, und um die Array-Werte zu drucken, können Sie etwas wie echo "${array[*]}" versuchen

7
Ahmed Al-Haffar

Bash 4.4 hat eine -d-Option für readarraymapfile EINGEF&UUML;HRT, SO DASS DIES NUN MIT GEL&OUML;ST WERDEN KANN

readarray -d '' array < <(find . -name "$input" -print0)

F&UUML;R EINE METHODE, DIE MIT BELIEBIGEN DATEINAMEN ARBEITET, EINSCHLIE&SZLIG;LICH LEERZEICHEN, ZEILENUMBR&UUML;CHEN UND GLOBBING-ZEICHEN.

AUS DEM MANUAL (UNTER AUSLASSUNG ANDERER OPTIONEN):

mapfile [-d delim] [array]

-d
DAS ERSTE ZEICHEN VON _/delim
wird verwendet, um jede Eingabezeile zu beenden, anstatt eine Zeilenschaltung. Wenn delim die leere Zeichenfolge ist, beendet mapfile eine Zeile, wenn sie ein NUL-Zeichen liest.

Und readarray ist nur ein Synonym für mapfile.

1
Benjamin W.

In Bash hilft $(<any_Shell_cmd>), einen Befehl auszuführen und die Ausgabe zu erfassen. Die Übergabe an IFS mit \n als Trennzeichen hilft beim Konvertieren in ein Array.

IFS='\n' read -r -a txt_files <<< $(find /path/to/dir -name "*.txt")
0
rashok