BASH-assoziatives Array-Drucken

16

Gibt es eine Möglichkeit, ein gesamtes Array ([key] = value) zu drucken, ohne alle Elemente zu durchlaufen?

Angenommen, ich habe ein Array mit folgenden Elementen erstellt:

declare -A array
array=([a1]=1 [a2]=2 ... [b1]=bbb ... [f500]=abcdef)

Ich kann das gesamte Array mit ausdrucken

for i in "${!array[@]}"
do
echo "${i}=${array[$i]}"
done

Es scheint jedoch, dass Bash bereits weiß, wie alle Array-Elemente auf einmal abgerufen werden - sowohl Schlüssel ${!array[@]}als auch Werte ${array[@]}.

Gibt es eine Möglichkeit, Bash dazu zu bringen, diese Informationen ohne die Schleife auszudrucken?

Edit:
typeset -p arraymacht das!
Ich kann jedoch nicht Präfix und Suffix in einer einzelnen Ersetzung entfernen:

a="$(typeset -p array)"
b="${a##*(}"
c="${b%% )*}"

Gibt es eine sauberere Methode, um nur den Teil key = value der Ausgabe abzurufen / auszudrucken?

Dani_l
quelle

Antworten:

14

Ich denke, Sie fragen dort zwei verschiedene Dinge.

Gibt es eine Möglichkeit, Bash dazu zu bringen, diese Informationen ohne die Schleife auszudrucken?

Ja, aber sie sind nicht so gut wie nur mit der Schleife.

Gibt es eine sauberere Methode, um nur den Teil key = value der Ausgabe abzurufen / auszudrucken?

Ja, die forSchleife. Es hat den Vorteil, dass keine externen Programme erforderlich sind, es ist unkompliziert und es ist ziemlich einfach, das genaue Ausgabeformat ohne Überraschungen zu steuern.


Jede Lösung, die versucht, die Ausgabe von declare -p( typeset -p) zu verarbeiten, muss sich mit a) der Möglichkeit befassen, dass die Variablen selbst Klammern oder Klammern enthalten, b) dem Anführungszeichen, declare -pdas hinzugefügt werden muss, damit die Ausgabe für die Shell gültig ist.

Zum Beispiel b="${a##*(}"frisst Ihre Erweiterung einige der Werte, wenn ein Schlüssel / Wert eine öffnende Klammer enthält. Dies liegt daran, dass Sie verwendet haben ##, wodurch das längste Präfix entfernt wird. Gleiches gilt für c="${b%% )*}". Selbstverständlich könnten Sie das von Ihnen gedruckte Boilerplate declaregenauer zuordnen, aber es würde Ihnen immer noch schwer fallen, wenn Sie nicht alle Zitate wünschen, die es bietet.

Das sieht nicht sehr schön aus, wenn Sie es nicht brauchen.

$ declare -A array=([abc]="'foobar'" [def]='"foo bar"')
$ declare -p array
declare -A array='([def]="\"foo bar\"" [abc]="'\''foobar'\''" )'

Mit der forSchleife ist es einfacher, das Ausgabeformat nach Belieben auszuwählen:

# without quoting
$ for x in "${!array[@]}"; do printf "[%s]=%s\n" "$x" "${array[$x]}" ; done
[def]="foo bar"
[abc]='foobar'

# with quoting
$ for x in "${!array[@]}"; do printf "[%q]=%q\n" "$x" "${array[$x]}" ; done
[def]=\"foo\ bar\"
[abc]=\'foobar\'

Von dort aus ist es auch einfach , das Ausgabeformat anderweitig zu ändern (entfernen Sie die Klammern um den Schlüssel, setzen Sie alle Schlüssel / Wert-Paare in eine einzelne Zeile ...). Wenn Sie ein Angebot für etwas anderes als die Shell selbst benötigen, müssen Sie es dennoch selbst erstellen, aber Sie haben zumindest die Rohdaten, an denen Sie arbeiten können. (Wenn Sie Zeilenumbrüche in den Schlüsseln oder Werten haben, müssen Sie wahrscheinlich etwas zitieren.)

Mit einem aktuellen Bash (4.4, glaube ich) könnte man auch printf "[%s]=%s" "${x@Q}" "${array[$x]@Q}"statt verwenden printf "%q=%q". Es erzeugt ein etwas schöneres zitiertes Format, aber es ist natürlich ein bisschen mehr Arbeit, sich daran zu erinnern, es zu schreiben. (Und es zitiert den Eckfall @als Array-Schlüssel, der %qnicht zitiert wird.)

Wenn die for-Schleife zum Schreiben zu müde erscheint, speichern Sie sie an einer beliebigen Stelle (ohne hier zu zitieren):

printarr() { declare -n __p="$1"; for k in "${!__p[@]}"; do printf "%s=%s\n" "$k" "${__p[$k]}" ; done ;  }  

Und dann benutze einfach das:

$ declare -A a=([a]=123 [b]="foo bar" [c]="(blah)")
$ printarr a
a=123
b=foo bar
c=(blah)

Funktioniert auch mit indizierten Arrays:

$ b=(abba acdc)
$ printarr b
0=abba
1=acdc
ilkkachu
quelle
Beachten Sie, dass die Ausgabe Ihrer printf ...%q...Variante nicht für die erneute Eingabe in die Shell geeignet ist, wenn das Array einen @Schlüssel hat, da% q ihn nicht in Anführungszeichen setzt und a=([@]=value)ein Syntaxfehler vorliegt bash.
Stéphane Chazelas
@ StéphaneChazelas, anscheinend. "${x@Q}"zitiert das auch, da es alle Zeichenketten zitiert (und schöner aussieht). hat einen Hinweis dazu hinzugefügt.
ilkkachu
Ja, von mksh kopiert. Ein weiterer Operator mit einer anderen Form, der mit den meisten anderen nicht kombiniert werden kann. Siehe auch hier zshdie Variablenerweiterungsflags (die um Jahrzehnte älter sind als Bash und mit denen Sie den Anführungsstil auswählen können: $ {(q) var}, $ {(qq) var} ...) für ein besseres Design. bash hat das gleiche Problem wie mksh, da der leere String nicht in Anführungszeichen gesetzt wird (hier kein Problem, da bash ohnehin keine leeren Schlüssel unterstützt). Auch bei der Verwendung unter Angabe Arten andere als Apostroph ( ${var@Q}resorts $'...'für einige Werte) ist es wichtig , dass der Code REINPUT in der gleichen Örtlichkeit abweichen.
Stéphane Chazelas
@ StéphaneChazelas, ich denke du meinst einen nicht gesetzten Wert, keine leere Zeichenkette? ( x=; echo "${x@Q}"gibt '', unset x; echo "${x@Q}"gibt nichts.) Bashs @Qscheint $'\n'eine wörtliche Newline vorzuziehen, was in manchen Situationen tatsächlich gut sein kann (aber ich kann nicht sagen, was andere bevorzugen). Natürlich wäre es nicht schlecht, eine Wahl zu haben.
ilkkachu
Oh ja, sorry, das hatte ich nicht gemerkt. Das ist dann ein Unterschied zu MKSH. Die $'...'Syntax ist ein potenzielles Problem bei bestimmten LC_ALL=zh_HK.big5hkscs bash -c 'a=$'\''\n\u3b1'\''; printf "%s\n" "${a@Q}"'Ausgaben $'\n<0xa3><0x5c>'und 0x5cnur ein Backslash. Wenn dieses Zitat in einem anderen Gebietsschema interpretiert wird, tritt ein Problem auf.
Stéphane Chazelas
9
declare -p array
declare -A array='([a2]="2" [a1]="1" [zz]="Hello World" [b1]="bbb" [f50]="abcd" )'

2 Gabel

Vielleicht das:

printf "%s\n" "${!array[@]}"
a2
a1
f50
zz
b1

printf "%s\n" "${array[@]}"
2
1
abcd
Hello World
bbb

printf "%s\n" "${!array[@]}" "${array[@]}" | pr -2t
a2                              2
a1                              1
f50                             abcd
zz                              Hello World
b1                              bbb

3 Gabeln

oder dieses:

paste -d= <(printf "%s\n" "${!array[@]}") <(printf "%s\n" "${array[@]}")
a2=2
a1=1
f50=abcd
zz=Hello World
b1=bbb

Keine Gabel

zu vergleichen mit

for i in "${!array[@]}";do printf "%s=%s\n" "$i" "${array[$i]}";done
a2=2
a1=1
f50=abcd
zz=Hello World
b1=bbb

Ausführungszeitvergleich

Da die letzte Syntax keine Verzweigung verwendet, könnten sie schneller sein:

time printf "%s\n" "${!array[@]}" "${array[@]}" | pr -2t | wc
      5      11      76
real    0m0.005s
user    0m0.000s
sys     0m0.000s

time paste -d= <(printf "%s\n" "${!array[@]}") <(printf "%s\n" "${array[@]}") | wc
      5       6      41
real    0m0.008s
user    0m0.000s
sys     0m0.000s

time for i in "${!array[@]}";do printf "%s=%s\n" "$i" "${array[$i]}";done | wc
      5       6      41
real    0m0.002s
user    0m0.000s
sys     0m0.001s

Aber diese Bestätigung bleibt nicht wahr, wenn das Array groß wird; Wenn das Reduzieren von Gabeln bei kleinen Prozessen effizient ist, ist der Einsatz dedizierter Werkzeuge bei größeren Prozessen effizienter.

for i in {a..z}{a..z}{a..z};do array[$i]=$RANDOM;done


time printf "%s\n" "${!array[@]}" "${array[@]}" | pr -2t | wc
  17581   35163  292941
real    0m0.150s
user    0m0.124s
sys     0m0.036s

time paste -d= <(printf "%s\n" "${!array[@]}") <(printf "%s\n" "${array[@]}") | wc
  17581   17582  169875
real    0m0.140s
user    0m0.000s
sys     0m0.004s

time for i in "${!array[@]}";do printf "%s=%s\n" "$i" "${array[$i]}";done | wc
  17581   17582  169875
real    0m0.312s
user    0m0.268s
sys     0m0.076s

Anmerkung

Da beide ( gegabelten ) Lösungen die Ausrichtung verwenden , funktioniert keine von ihnen, wenn eine Variable eine neue Zeile enthält . In diesem Fall ist der einzige Weg eine forSchleife.

F. Hauri
quelle
Beide Methoden sehen zwar clever aus, sind aber weniger effizient als a for. Welches ist eine Schande, wirklich.
Satō Katsura
@SatoKatsura Ich stimme zu, aber wenn langsamer, prist die Syntaxverwendung kürzer ... Ich bin mir nicht sicher, ob die prSyntax auch bei großen Arrays langsamer bleibt!
F. Hauri
2
@MiniMax Weil es nicht das richtige Ergebnis liefert (gleiche Elemente, falsche Reihenfolge). Sie müssten Arrays komprimieren ${!array[@]}und ${array[@]}erst, damit das funktioniert.
Satō Katsura
1
Das letzte Snippet mit pasteist länger als die forSchleife in der Frage in einer Zeile for i in "${!array[@]}"; do echo "$i=${array[$i]}" ; done, erfordert jedoch zwei Subshells und ein externes Programm. Wie ist das ordentlicher? Die Lösung mit prunterbricht auch, wenn es viele Elemente gibt, da sie versucht, die Ausgabe zu paginieren. Sie müssten so etwas verwenden, | pr -2t -l"${#array[@]}"das im Vergleich zur einfachen Schleife immer schwerer zu merken ist und das wiederum länger ist als es.
ilkkachu
1
In bash, cmd1 | cmd2Mittel 2 Gabeln, auch wenn cmd1 oder cmd2 oder beide builtin.
Stéphane Chazelas
2

Wenn Sie nach einer Shell mit besserer Unterstützung für assoziative Arrays suchen, versuchen Sie es zsh.

In zsh(wo assoziative Arrays im Jahr 1998 hinzugefügt wurden, verglichen mit 1993 für ksh93 und 2009 für bash) $varoder ${(v)var}erweitert auf die (nicht leeren) Werte des Hash ${(k)var}zu den (nicht leeren) Schlüsseln (in derselben Reihenfolge), und ${(kv)var}sowohl auf Schlüssel als auch auf Werte.

Um die leeren Werte wie bei Arrays beizubehalten, müssen Sie das @Flag in Anführungszeichen setzen und verwenden .

Um die Schlüssel und Werte auszudrucken, ist es nur eine Frage von

printf '%s => %s\n' "${(@kv)var}"

Um einen möglicherweise leeren Hash zu berücksichtigen, sollten Sie Folgendes tun:

(($#var)) &&  printf '%s => %s\n' "${(@kv)var}"

Beachten Sie auch, dass zsh eine viel sinnvollere und nützlichere Array-Definitionssyntax verwendet als ksh93's (kopiert von bash):

typeset -A var
var=(k1 v1 k2 v2 '' empty '*' star)

Dies erleichtert das Kopieren oder Zusammenführen von assoziativen Arrays erheblich:

var2=("${(@kv)var1}")
var3+=("${(@kv)var2}")
var4=("${@kv)var4}" "${(@kv)var5}")

(Sie können einen Hash ohne eine Schleife nicht einfach mit kopieren bash, und beachten Sie, dass bashderzeit keine leeren Schlüssel oder Schlüssel / Werte mit NUL-Bytes unterstützt werden).

Siehe auch zshFunktionen zum Komprimieren von Arrays, die Sie normalerweise für assoziative Arrays benötigen:

keys=($(<keys.txt)) values=($(<values.txt))
hash=(${keys:^values})
Stéphane Chazelas
quelle
1

Da Schriftsatz macht was Sie wollen, warum nicht einfach seine Ausgabe bearbeiten?

typeset -p array | sed s/^.*\(// | tr -d ")\'\""  | tr "[" "\n" | sed s/]=/' = '/

gibt

a2 = 2  
a1 = 1  
b1 = bbb 

Wo

array='([a2]="2" [a1]="1" [b1]="bbb" )'

Ausführlich, aber es ist ziemlich einfach zu verstehen, wie die Formatierung funktioniert: Führen Sie einfach die Pipeline mit immer mehr sed- und tr- Befehlen aus. Ändern Sie sie, um hübschen Druckgeschmack zu entsprechen.

Nadreck
quelle
Diese Art von Pipeline wird in dem Moment fehlschlagen, in dem einige der Schlüssel oder Werte des Arrays Zeichen enthalten, die Sie ersetzen, z. B. Klammern, Klammern oder Anführungszeichen. Und eine Pipeline von seds und trs ist nicht viel einfacher als eine forSchleife mit printf.
ilkkachu
Wissen Sie auch, dass trZeichen für Zeichen übersetzt werden und dass es nicht mit Zeichenfolgen übereinstimmt? tr "]=" " ="Ändert "]" zu einem Leerzeichen und =zu einem =, unabhängig von der Position. Sie könnten also wahrscheinlich alle drei trzu einem kombinieren .
ilkkachu
Sehr wahr in Bezug auf einige der nicht alphanumerischen Zeichen, die dies verfälschen. Alles, was mit ihnen zu tun hat, wird jedoch um eine Größenordnung komplexer und weniger lesbar, es sei denn, es gibt einen wirklich guten Grund, sie in Ihrem Daten-Feed zu haben. Sollte immer Ihre ausdrückliche Einschränkung haben. Ich finde diese Pipelines einfacher, zum Beispiel und zu Debugging-Zwecken, als ein printf-Glob, der entweder perfekt funktioniert oder in die Luft sprengt. Hier nehmen Sie eine einfache Änderung pro Element vor, testen sie und fügen 1 weitere hinzu.
Nadreck
Mein Fehler! Habe meine _tr_s und _sed_s total durcheinander gebracht! In der letzten Bearbeitung behoben.
Nadreck
1

Eine weitere Option besteht darin, alle Variablen aufzulisten und nach der gewünschten zu suchen.

set | grep -e '^aa='

Ich benutze dies zum Debuggen. Ich bezweifle, dass es sehr performant ist, da es alle Variablen auflistet.

Wenn Sie dies oft machen würden, könnten Sie es zu einer Funktion wie der folgenden machen:

aap() { set | grep -e "^$1="; }

Leider, wenn wir die Leistung mit der Zeit überprüfen:

$ time aap aa aa=([0]="abc") . real 0m0.014s user 0m0.003s sys 0m0.006s

Wenn Sie dies sehr oft tun würden, würden Sie @ F.Hauris NO FORKS-Version wünschen, weil sie so viel schneller ist.

xer0x
quelle