Wenn Sie die Ausgabe speichern müssen und ein Array möchten, mapfile
ist dies einfach.
Überlegen Sie zunächst, ob Sie die Ausgabe Ihres Befehls überhaupt speichern müssen . Wenn Sie dies nicht tun, führen Sie einfach den Befehl aus.
Wenn Sie die Ausgabe eines Befehls als Zeilenarray lesen möchten, können Sie das Globbing deaktivieren, das Aufteilen IFS
in Zeilen festlegen , die Befehlssubstitution in der (
)
Syntax zur Arrayerstellung verwenden und anschließend zurücksetzen IFS
. Das ist komplexer als es scheint. Terdons Antwort deckt einen Teil dieses Ansatzes ab. Ich schlage jedoch vor, dass Sie stattdessen den inmapfile
die Bash-Shell integrierten Befehl zum Lesen von Text als Zeilenarray verwenden. Hier ist ein einfacher Fall, in dem Sie aus einer Datei lesen:
mapfile < filename
Dies liest Zeilen in ein Array namens MAPFILE
. Ohne die Umleitung der Eingabe würden Sie anstelle der von benannten Datei von der Standardeingabe der Shell (normalerweise Ihrem Terminal) lesen . Übergeben Sie den Namen, um in ein anderes als das Standardarray einzulesen . Dies liest beispielsweise in das Array ein :< filename
filename
MAPFILE
lines
mapfile lines < filename
Ein weiteres Standardverhalten, das Sie möglicherweise ändern möchten, besteht darin, dass die Zeilenumbrüche an den Zeilenenden beibehalten werden. Sie werden als letztes Zeichen in jedem Array-Element angezeigt (es sei denn, die Eingabe endete ohne ein Zeilenumbruchzeichen. In diesem Fall hat das letzte Element kein Zeichen). Um diese Zeilenumbrüche chomp , so dass sie in dem Array nicht angezeigt werden , übergeben Sie die -t
Option mapfile
. Dies liest beispielsweise in das Array records
und schreibt keine nachgestellten Zeilenumbrüche in seine Array-Elemente:
mapfile -t records < filename
Sie können auch verwenden, -t
ohne einen Array-Namen zu übergeben. Das heißt, es funktioniert auch mit einem impliziten Array-Namen von MAPFILE
.
Die integrierte mapfile
Shell unterstützt andere Optionen und kann alternativ als aufgerufen werden readarray
. Führen Sie help mapfile
(und help readarray
) für Details aus.
Sie möchten jedoch nicht aus einer Datei lesen, sondern aus der Ausgabe eines Befehls. Verwenden Sie dazu die Prozessersetzung . Dieser Befehl liest Zeilen aus dem Befehl some-command
mit arguments...
seinen Befehlszeilenargumenten und platziert sie im mapfile
Standardarray MAPFILE
, wobei die abschließenden Zeilenumbruchzeichen entfernt werden:
mapfile -t < <(some-command arguments...)
Die Prozessersetzung wird durch einen tatsächlichen Dateinamen ersetzt, aus dem die Ausgabe von running gelesen werden kann. Die Datei ist eher eine Named Pipe als eine reguläre Datei. Unter Ubuntu wird sie wie folgt benannt (manchmal mit einer anderen Nummer als ), aber Sie müssen sich nicht um die Details kümmern, da sich die Shell darum kümmert alles hinter den Kulissen.<(some-command arguments...)
some-command arguments...
/dev/fd/63
63
Sie könnten denken, Sie könnten auf die Prozessersetzung verzichten, indem Sie stattdessen verwenden, aber das funktioniert nicht, da Bash , wenn Sie eine Pipeline mit mehreren durch getrennten Befehlen haben , alle Befehle in Subshells ausführt . Somit werden beide und in ihren eigenen Umgebungen ausgeführt , die von der Umgebung der Shell, in der Sie die Pipeline ausführen, initialisiert, aber von dieser getrennt sind. In der Subshell, in der ausgeführt wird, wird das Array zwar gefüllt , aber dieses Array wird verworfen, wenn der Befehl endet. wird niemals für den Anrufer erstellt oder geändert.some-command arguments... | mapfile -t
|
some-command arguments...
mapfile -t
mapfile -t
MAPFILE
MAPFILE
So sieht das Beispiel in Terdons Antwort vollständig aus, wenn Sie Folgendes verwenden mapfile
:
mapfile -t dirs < <(find . -type d)
for d in "${dirs[@]}"; do
echo "DIR: $d"
done
Das ist es. Sie müssen nicht überprüfenIFS
, ob und mit welchem Wert festgelegt wurde, ob es nicht festgelegt wurde, auf eine neue Zeile setzen und später zurücksetzen oder wieder deaktivieren. Sie müssen das Globbing nicht deaktivieren (z. B. mit set -f
) - was wirklich erforderlich ist, wenn Sie diese Methode ernsthaft verwenden möchten , da Dateinamen möglicherweise enthalten *
, ?
und - und [
anschließend erneut aktivieren ( set +f
).
Sie können diese bestimmte Schleife auch vollständig durch einen einzelnen printf
Befehl ersetzen - obwohl dies eigentlich kein Vorteil ist mapfile
, da Sie dies unabhängig davon tun können, ob Sie mapfile
das Array oder eine andere Methode zum Auffüllen des Arrays verwenden. Hier ist eine kürzere Version:
mapfile -t < <(find . -type d)
printf 'DIR: %s\n' "${MAPFILE[@]}"
Es ist wichtig zu beachten, dass, wie Terdon erwähnt , das zeilenweise Arbeiten nicht immer angemessen ist und mit Dateinamen, die Zeilenumbrüche enthalten, nicht ordnungsgemäß funktioniert. Ich empfehle, Dateien nicht so zu benennen, aber es kann passieren, auch aus Versehen.
Es gibt keine einheitliche Lösung.
Sie haben nach "einem generischen Befehl für alle Szenarien" und dem Ansatz gefragt, mapfile
etwas näher an dieses Ziel heranzugehen, aber ich fordere Sie auf, Ihre Anforderungen zu überdenken.
Die oben gezeigte Aufgabe wird besser mit nur einem einzigen find
Befehl erreicht:
find . -type d -printf 'DIR: %p\n'
Sie können auch einen externen Befehl verwenden , der am Anfang jeder Zeile sed
hinzugefügt DIR:
werden soll. Dies ist wohl etwas hässlich, und im Gegensatz zu diesem Befehl find werden zusätzliche "Präfixe" in Dateinamen hinzugefügt, die Zeilenumbrüche enthalten. Es funktioniert jedoch unabhängig von der Eingabe, sodass es Ihren Anforderungen entspricht, und es ist immer noch vorzuziehen, die Ausgabe in a einzulesen Variable oder Array :
find . -type d | sed 's/^/DIR: /'
Wenn Sie für jedes gefundene Verzeichnis eine Aktion auflisten und ausführen müssen, z. B. das Ausführen some-command
und Übergeben des Verzeichnispfads als Argument, find
können Sie dies auch tun:
find . -type d -print -exec some-command {} \;
Kehren wir als weiteres Beispiel zur allgemeinen Aufgabe zurück, jeder Zeile ein Präfix hinzuzufügen. Angenommen, ich möchte die Ausgabe von sehen, help mapfile
aber die Zeilen nummerieren. Ich würde mapfile
dafür weder eine andere Methode verwenden, die es in eine Shell-Variable oder ein Shell-Array einliest. Angenommen, help mapfile | cat -n
es wird nicht die gewünschte Formatierung angegeben, kann ich Folgendes verwenden awk
:
help mapfile | awk '{ printf "%3d: %s\n", NR, $0 }'
Das Einlesen der gesamten Ausgabe eines Befehls in eine einzelne Variable oder ein einzelnes Array ist manchmal nützlich und angemessen, hat jedoch große Nachteile. Sie müssen sich nicht nur mit der zusätzlichen Komplexität der Verwendung Ihrer Shell auseinandersetzen, wenn ein vorhandener Befehl oder eine Kombination vorhandener Befehle möglicherweise bereits das tut, was Sie benötigen, oder besser, sondern die gesamte Ausgabe des Befehls muss im Speicher gespeichert werden. Manchmal wissen Sie vielleicht, dass dies kein Problem ist, aber manchmal verarbeiten Sie eine große Datei.
Eine Alternative, die häufig versucht wird - und manchmal richtig gemacht wird -, besteht darin, die Eingabe Zeile für Zeile read -r
in einer Schleife zu lesen . Wenn Sie während der Bearbeitung späterer Zeilen keine vorherigen Zeilen speichern müssen und lange Eingaben verwenden müssen, ist dies möglicherweise besser als mapfile
. Aber es sollte auch in Fällen vermieden werden, in denen Sie es einfach an einen Befehl weiterleiten können, der die Arbeit erledigen kann, was in den meisten Fällen der Fall ist .