BASH連想配列印刷


16

すべての要素をループすることなく、配列全体([key] = value)を印刷する方法はありますか?

いくつかの要素を持つ配列を作成したと仮定します。

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

配列全体を印刷して戻すことができます

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

ただし、bashはすべての配列要素を1つの "go"(キー${!array[@]}と値の両方)で取得する方法を既に知っているようです${array[@]}

ループなしでこの情報をbashに出力させる方法はありますか?

編集:
typeset -p arrayそれは!
ただし、1回の置換でプレフィックスとサフィックスの両方を削除することはできません。

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

出力のキー=値部分のみを取得/印刷するよりクリーンな方法はありますか?

回答:


14

あなたはそこに2つの異なることを求めていると思います。

ループなしでこの情報をbashに出力させる方法はありますか?

はい、ただし、ループを使用するほど良くありません。

出力のキー=値部分のみを取得/印刷するよりクリーンな方法はありますか?

はい、forループ。外部プログラムを必要とせず、簡単で、驚くことなく正確な出力形式を制御するのがかなり簡単になるという利点があります。


declare -ptypeset -p)の出力を処理しようとするソリューションは、a)括弧または角括弧を含む変数自体の可能性、b)declare -pシェルの出力を有効にするために追加する必要がある引用符を処理する必要があります。

たとえば、b="${a##*(}"キー/値に開き括弧が含まれている場合、展開は値の一部を消費します。これは##最長のプレフィックスを削除するを使用したためです。同じですc="${b%% )*}"。もちろん、declareより正確に印刷されたボイラープレートと一致させることもできますが、引用をすべてしたくない場合は、まだ苦労します。

あなたがそれを必要としない限り、これはあまり良く見えません。

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

forループ、それはあなたが好きなように出力形式を選択する方が簡単です:

# 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\'

そこから、出力形式を変更することも簡単です(キーを囲む括弧を削除し、すべてのキー/値のペアを1行に配置します...)。シェル自体以外のものを引用する必要がある場合でも、それを自分で行う必要がありますが、少なくとも作業する生データはあります。(キーまたは値に改行がある場合、おそらく引用符が必要になります。)

現在のBash(4.4、私は思う)では、のprintf "[%s]=%s" "${x@Q}" "${array[$x]@Q}"代わりに使用することもできますprintf "%q=%q"。引用符で囲まれた形式の方がやや優れていますが、作成するのを忘れないでください。(そして@、それ%qは引用符ではない配列キーとしてのコーナーケースを引用します。)

forループが疲れすぎて記述できない場合は、どこかに関数を保存します(ここでは引用しません)。

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

そしてそれを使うだけです:

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

インデックス付き配列でも動作します:

$ b=(abba acdc)
$ printarr b
0=abba
1=acdc

printf ...%q...配列に@キーがあり、%qで引用されておらずa=([@]=value)、の構文エラーである場合、バリアントの出力はシェルへの再入力には適していませんbash
ステファンシャゼラス

@StéphaneChazelas、どうやら。"${x@Q}"それもすべての文字列を引用するので、引用します(そしてより良く見える)。その使用に関するメモを追加しました。
イルッカチュ

はい、mkshからコピーしました。他のほとんどと組み合わせることができない、さらに異なる形状の別の演算子。繰り返しzshますが、より良い設計のために、変数展開フラグ(bashよりも数十年前に登場し、引用スタイルを選択できる$ {(q)var}、$ {(qq)var} ...)を参照してください。bashには、空の文字列を引用しないという点でmkshと同じ問題があります(bashは空のキーをサポートしていないため、ここでは問題になりません)。(引用単一引用符以外のスタイル使用する場合にも、${var@Q}リゾートには$'...'いくつかの値のために)それは、コードが同じロケールで再入力することが重要です。
ステファンシャゼラス

@StéphaneChazelas、空の文字列ではなく、設定されていない値を意味すると思いますか?(x=; echo "${x@Q}"与えず''unset x; echo "${x@Q}"バッシュのは。何も与えない)@Qを好むように見える$'\n'、実際にいくつかの状況では良いかもしれリテラルの改行を、オーバー(私は他の人が好むものを言うことができません)。もちろん、そこに選択肢があることは悪くありません。
-ilkkachu

ああ、すみません、私はそれを理解していませんでした。それはmkshとの違いです。$'...'構文は次のようなもので、潜在的な問題であるLC_ALL=zh_HK.big5hkscs bash -c 'a=$'\''\n\u3b1'\''; printf "%s\n" "${a@Q}"'出力$'\n<0xa3><0x5c>'0x5c一人でその引用は、異なるロケールで解釈された場合には問題があると思いますので、バックスラッシュです。
ステファンシャゼラス

9
declare -p array
declare -A array='([a2]="2" [a1]="1" [zz]="Hello World" [b1]="bbb" [f50]="abcd" )'

2フォーク

たぶんこれ:

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つのフォーク

またはこれ:

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

フォークなし

と比較される

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

実行時間の比較

最後の構文はforkを使用しないため、より速くなる可能性があります。

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

しかし、配列が大きくなった場合、この確認は真実のままではありません。フォークを減らすことが小さなプロセスで効率的であれば、専用のツールを使用すると、大きなプロセスでより効率的になります。

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

リマーク

両方の(分岐した)ソリューションがアラインメントを使用するので、変数が改行を含んでいるならそれらのどれも動きません。この場合、唯一の方法はforループです。


賢いように見えますが、両方の方法はaよりも効率的ですfor。それは本当に残念です。
桂佐藤

@SatoKatsura同意しますが、遅い場合、構文の使用prは短くなります... pr大きい配列であっても、構文が遅くなるかどうかはわかりません!
F. HAURI

2
@MiniMax正しい結果(同じ要素、間違った順序)を生成しないため。アレイを圧縮する必要があり${!array[@]}${array[@]}最初にそれを機能させる必要があります。
桂さとう

1
最後のスニペットpasteは、1行に書かれた質問のループより長いですが、2つのサブシェルと外部プログラムが必要です。どうですか?また、多くの要素がある場合、出力をページ分割しようとするため、ソリューションは壊れます。単純なループに比べて覚えにくいものを使い始め、それよりも長いものを使用する必要があります。forfor i in "${!array[@]}"; do echo "$i=${array[$i]}" ; donepr| pr -2t -l"${#array[@]}"
イルッカチュ

1
ではbash、cmd1またはcmd2、あるいはその両方が組み込まれている場合でも、2つのフォークをcmd1 | cmd2意味します。
ステファンシャゼラス

2

連想配列のサポートが向上したシェルを探している場合は、を試してくださいzsh

zsh(連想配列は、bashのためには、ksh93および2009年1993年に比べ、1998年に追加された場合)、$varまたは${(v)var}(非エンプティ)に展開ハッシュの、${(k)var}(同じ順序で)(空でない)キーに、そして${(kv)var}、キーと値の両方に。

配列のように空の値を保持するには、@フラグを引用して使用する必要があります。

キーと値を印刷するには、それはただの問題です

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

空のハッシュを説明するために、次のことを行う必要があります。

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

また、zshはksh93(によってコピーされたbash)よりもはるかに賢明で便利な配列定義構文を使用していることに注意してください。

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

これにより、連想配列のコピーまたはマージが非常に簡単になります。

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

(を使用してループなしでハッシュを簡単にコピーすることはできません。現在、NULバイトの空のキーまたはキー/値をサポートbashbashていないことに注意してください)。

zsh連想配列を操作するのに通常必要な配列の圧縮機能も参照してください。

keys=($(<keys.txt)) values=($(<values.txt))
hash=(${keys:^values})

1

以来組版は何をしたい理由だけで、その出力を編集しないのですか?

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

与える

a2 = 2  
a1 = 1  
b1 = bbb 

どこ

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

冗長ですが、書式設定がどのように機能するかを見るのは非常に簡単です。sedコマンドとtrコマンドを徐々に増やしてパイプラインを実行するだけです。印刷の好みに合わせて変更します。


この種のパイプラインは、配列のキーまたは値の一部に、かっこ、角かっこ、引用符などの置換する文字が含まれていると、失敗することになります。そしてsedsとsのパイプラインはtr、でのforループよりも単純ではありませんprintf
イルッカチュ

また、あなたはtr文字ごとに翻訳することを知っています、それは文字列と一致しませんか?tr "]=" " ="スペースへの変更「]」と==かかわらず位置の、。したがって、おそらく3つすべてtrを1つに結合できます。
イルカチュ

英数字以外の文字の一部がこれを妨害していることについては、非常に真実です。しかし、それらを処理しなければならないものは、はるかに複雑で読みにくくなりますので、データフィードにそれらを含める正当な理由がない限り、それはここに来る前にフィルターで除外されていると思われる質問に記載されています。常に明示的な警告が必要です。これらのパイプラインは、たとえば、デバッグ目的など、完全に機能するか、顔を爆破するprintfグロブよりも単純であることがわかります。ここでは、要素ごとに1つの簡単な変更を加えてテストし、さらに1つ追加します。
ナドレック

私の悪い!_tr_sと_sed_sが完全に混同されました!最新の編集で修正されました。
ナドレック

1

もう1つのオプションは、必要な変数のすべての変数とgrepをリストすることです。

set | grep -e '^aa='

これをデバッグに使用します。すべての変数がリストされているため、非常にパフォーマンスが高いとは思えません。

これを頻繁に行う場合は、次のような機能にすることができます。

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

残念ながら、時間を使用してパフォーマンスをチェックする場合:

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

したがって、これを非常に頻繁に行っている場合は、@ F.HauriのNO FORKSバージョンが必要です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.