すべての要素をループすることなく配列全体([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部分のみを取得/印刷するよりきれいな方法はありますか?
そこで2つのことを尋ねていると思います。
ループなしでこの情報をbashに印刷させる方法はありますか?
はい。ただし、ループを使用する場合ほど優れていません。
出力のkey = value部分のみを取得/印刷するよりきれいな方法はありますか?
はい、for
ループ。これには、外部プログラムを必要とせず、簡単で、驚くことなく正確な出力形式を簡単に制御できるという利点があります。
declare -p
(typeset -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
declare -p array
declare -A array='([a2]="2" [a1]="1" [zz]="Hello World" [b1]="bbb" [f50]="abcd" )'
多分これ:
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
またはこれ:
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
ループです。
より良い連想配列をサポートするシェルを探している場合は、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})
_
以来 タイプセット なぜ出力を編集しないのですか?
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つのオプションは、必要なすべての変数と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バージョンが非常に高速であるため、これを使用します。