web-dev-qa-db-ja.com

BASH連想配列印刷

すべての要素をループすることなく配列全体([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%% )*}"

出力のkey = value部分のみを取得/印刷するよりきれいな方法はありますか?

18
Dani_l

そこで2つのことを尋ねていると思います。

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

はい。ただし、ループを使用する場合ほど優れていません。

出力のkey = value部分のみを取得/印刷するよりきれいな方法はありますか?

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


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

たとえば、拡張b="${a##*(}"は、キー/値に左括弧が含まれている場合、いくつかの値を使用します。これは、##を使用したためです。これにより、longestプレフィックスが削除されます。 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\'

そこから、それ以外にsimpleで出力形式を変更します(キーの前後の角かっこを削除し、すべてのキーと値のペアを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
16
ilkkachu
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

リマーク

両方の(forked)ソリューションはalignmentを使用するため、いずれかの変数にnewlineが含まれている場合、どちらも機能しません。この場合、唯一の方法はforループです。

10
F. Hauri

より良い連想配列をサポートするシェルを探している場合は、zshを試してください。

zsh(ksh93では1993、bashでは2009と比較して、結合配列が1998年に追加された)では、_$var_または${(v)var}は(空ではない)に展開されます- valuesハッシュ、${(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}")
_

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

zsh配列の圧縮機能も参照してください。これらの機能は通常、連想配列で使用する必要があります。

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

以来 タイプセット なぜ出力を編集しないのですか?

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 コマンド。かなりの印刷好みに合うようにそれらを修正します。

1
Nadreck

もう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バージョンが非常に高速であるため、これを使用します。

1
xer0x