![2個叉子](https://rvso.com/image/112013/2%E5%80%8B%E5%8F%89%E5%AD%90.png)
有沒有辦法列印整個陣列([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 似乎已經知道如何一次獲取所有數組元素——鍵${!array[@]}
和值${array[@]}
。
有沒有辦法讓 bash 在沒有循環的情況下列印這些資訊?
編輯:
typeset -p array
這樣做!
但是我無法在一次替換中同時刪除前綴和後綴:
a="$(typeset -p array)"
b="${a##*(}"
c="${b%% )*}"
有沒有更乾淨的方法來僅獲取/列印輸出的 key=value 部分?
答案1
我認為你在那裡問兩件不同的事情。
有沒有辦法讓 bash 在沒有循環的情況下列印這些資訊?
是的,但它們不如只使用循環好。
有沒有更乾淨的方法來僅獲取/列印輸出的 key=value 部分?
是的,for
循環。它的優點是不需要外部程序,簡單,並且可以輕鬆控制精確的輸出格式而不會出現意外。
任何嘗試處理declare -p
( typeset -p
) 輸出的解決方案都必須處理 a) 變數本身包含圓括號或中括號的可能性,b)declare -p
必須新增的參考以使其輸出成為 shell 的有效輸入。
例如,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\'
從那裡開始,也是簡單的否則更改輸出格式(刪除鍵周圍的括號,將所有鍵/值對放在一行上...)。如果您需要引用 shell 本身以外的其他內容,您仍然需要自己完成,但至少您擁有可以處理的原始資料。 (如果鍵或值中有換行符,則可能需要一些引用。)
printf "[%s]=%s" "${x@Q}" "${array[$x]@Q}"
對於當前的 Bash(我認為是 4.4),您也可以使用printf "%q=%q"
.它產生了一種更好的引用格式,但當然需要記住編寫更多的工作。 (它引用了as 數組鍵的極端情況,但它不引用。)@
%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
答案2
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
循環。
StackOverflow 上有更可靠、更詳細的答案
答案3
K
Bash 5.1 允許透過使用如下所示的值以非常直接的方式顯示關聯數組${arr[@]@K}
:
$ declare -A arr
$ arr=(k1 v1 k2 v2)
$ printf "%s\n" "${arr[@]@K}"
k1 "v1" k2 "v2"
呵呵。新的“K”參數轉換將關聯數組顯示為鍵值對。
中對此有很好的解釋Bash 參考手冊 → 3.5.3 Shell 參數擴展:
${parameter@operator}
K
產生參數值的可能引用版本,不同之處在於它將索引數組和關聯數組的值列印為引用鍵值對的序列(請參閱數組)。
答案4
自從排版做你想要的為什麼不直接編輯它的輸出呢?
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和t命令。修改它們以適應漂亮的印刷品味。