การเชื่อมโยงอาเรย์ของ BASH


17

มีวิธีพิมพ์อาร์เรย์ทั้งหมด ([คีย์] = ค่า) โดยไม่วนรอบองค์ประกอบทั้งหมดหรือไม่

สมมติว่าฉันได้สร้างอาร์เรย์ที่มีองค์ประกอบบางอย่าง:

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

ฉันสามารถพิมพ์อาเรย์ทั้งหมดด้วย

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

อย่างไรก็ตามดูเหมือนว่าทุบตีรู้วิธีการรับองค์ประกอบอาร์เรย์ทั้งหมดใน "ไป" - คีย์ทั้งสอง ${!array[@]}${array[@]}และค่านิยม

มีวิธีในการทุบตีพิมพ์ข้อมูลนี้โดยไม่วนซ้ำหรือไม่?

แก้ไข:
typeset -p arrayทำอย่างนั้น!
อย่างไรก็ตามฉันไม่สามารถลบทั้งคำนำหน้าและคำต่อท้ายในการทดแทนเดียว:

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

มีวิธีที่สะอาดกว่าในการรับ / พิมพ์เฉพาะส่วน key = value ของเอาต์พุตหรือไม่?

คำตอบ:


15

ฉันคิดว่าคุณกำลังถามสองสิ่งที่แตกต่างกันที่นั่น

มีวิธีในการทุบตีพิมพ์ข้อมูลนี้โดยไม่วนซ้ำหรือไม่?

ใช่ แต่มันไม่ดีเท่าการใช้ลูป

มีวิธีที่สะอาดกว่าในการรับ / พิมพ์เฉพาะส่วน key = value ของเอาต์พุตหรือไม่?

ใช่forวง มันมีข้อดีที่ไม่ต้องใช้โปรแกรมภายนอกตรงไปตรงมาและทำให้มันค่อนข้างง่ายในการควบคุมรูปแบบผลลัพธ์ที่แน่นอนโดยไม่ต้องประหลาดใจ


โซลูชันใด ๆ ที่พยายามจัดการกับผลลัพธ์ของdeclare -p( typeset -p) ต้องจัดการกับ a) ความเป็นไปได้ของตัวแปรที่มีวงเล็บหรือวงเล็บข) การอ้างอิงที่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\'

จากตรงนั้นมันก็ง่ายต่อการเปลี่ยนรูปแบบเอาต์พุตอื่น (ลบวงเล็บเหลี่ยมรอบคีย์วางคู่คีย์ / ค่าทั้งหมดในบรรทัดเดียว ... ) หากคุณต้องการอ้างถึงสิ่งอื่นนอกเหนือจากตัวเชลล์คุณจะต้องดำเนินการเอง แต่อย่างน้อยคุณก็มีข้อมูลดิบที่จะใช้งานได้ (ถ้าคุณมีบรรทัดใหม่ในคีย์หรือค่าคุณอาจจะต้องมีการพูด)

ด้วย 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...แตกต่างไม่เหมาะสำหรับ reinput กับเปลือกถ้าอาร์เรย์มี@คีย์% จากไตรมาสไม่ได้พูดมันและเป็นไวยากรณ์ผิดพลาดในa=([@]=value) bash
Stéphane Chazelas

@ StéphaneChazelasเห็นได้ชัดว่า "${x@Q}"อัญประกาศเช่นกันเนื่องจากเป็นเครื่องหมายคำพูดสตริงทั้งหมด (และดูดีกว่า) เพิ่มบันทึกเกี่ยวกับการใช้งาน
ilkkachu

ใช่คัดลอกมาจาก mksh ผู้ประกอบการรายอื่นที่มีรูปร่างแตกต่างกันซึ่งไม่สามารถใช้ร่วมกับผู้อื่นส่วนใหญ่ได้ ดูอีกครั้งzshด้วยแฟล็กการขยายตัวแปร (ซึ่งมีการกำหนดอีกครั้งล่วงหน้าของ bash ในหลายสิบปีและคุณสามารถเลือกรูปแบบการอ้างถึง: $ {(q) var}, $ {(qq) var} ... ) เพื่อการออกแบบที่ดีขึ้น bash มีปัญหาเช่นเดียวกับ mksh โดยที่มันไม่ได้อ้างอิงสตริงที่ว่างเปล่า (ไม่ใช่ปัญหาที่นี่เนื่องจาก bash นั้นไม่รองรับคีย์ที่ว่างเปล่า) นอกจากนี้เมื่อใช้รูปแบบการอ้างอิงนอกเหนือจากการอ้างอิงเดียว ( ${var@Q}เปลี่ยน$'...'เป็นค่าบางอย่าง) สิ่งสำคัญคือรหัสจะถูกส่งใหม่ในโลแคลเดียวกัน
Stéphane Chazelas

@ 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คนเดียวทับขวาดังนั้นคุณจะมีปัญหาถ้าอ้างว่าถูกตีความในสถานที่ที่แตกต่างกัน
Stéphane Chazelas

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วนซ้ำ


forขณะที่มองฉลาดทั้งสองวิธีมีประสิทธิภาพน้อยกว่า ซึ่งเป็นความอัปยศจริงๆ
Satō Katsura

@SatoKatsura ฉันเห็นด้วย แต่ถ้าช้ากว่าการใช้ไวยากรณ์prจะสั้นกว่า ... ฉันไม่แน่ใจว่าprไวยากรณ์จะช้าลงแม้จะมีอาร์เรย์ขนาดใหญ่!
F. Hauri

2
@MiniMax เนื่องจากไม่ได้ผลลัพธ์ที่ถูกต้อง (องค์ประกอบเดียวกันลำดับที่ไม่ถูกต้อง) คุณต้องใส่รหัสไปรษณีย์${!array[@]}และ${array[@]}ก่อนอื่นให้ใช้งานได้
Satō Katsura

1
ตัวอย่างสุดท้ายpasteนั้นมีความยาวมากกว่าforลูปในคำถามที่เขียนบนหนึ่งบรรทัดfor i in "${!array[@]}"; do echo "$i=${array[$i]}" ; doneแต่ต้องใช้สอง subshells และโปรแกรมภายนอก ผู้ชนะเป็นอย่างไร การแก้ปัญหาด้วยprยังแบ่งถ้ามีหลายองค์ประกอบในขณะที่มันพยายามที่จะแบ่งหน้าออก คุณต้องใช้สิ่ง| pr -2t -l"${#array[@]}"ที่เริ่มจำยากเมื่อเปรียบเทียบกับลูปแบบง่าย ๆ และนานกว่านั้นอีก
ilkkachu

1
ในbash, cmd1 | cmd2หมายถึง2งาแม้ว่า cmd1 หรือ CMD2 หรือทั้งสองมีในตัว
Stéphane Chazelas

2

zshหากคุณกำลังมองหาเปลือกด้วยการสนับสนุนอาเรย์ที่ดีกว่าลอง

ในzsh(ที่เชื่อมโยงอาร์เรย์ที่เพิ่มขึ้นในปี 1998 เมื่อเทียบกับปี 1993 ksh93 และปี 2009 สำหรับทุบตี) $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}")

(คุณไม่สามารถคัดลอกแฮชได้อย่างง่ายดายหากไม่มีลูปด้วยbashและโปรดทราบว่าbashในปัจจุบันไม่รองรับคีย์ว่างหรือคีย์ / ค่าด้วย NUL ไบต์)

ดูเพิ่มเติม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 ที่ก้าวหน้ามากขึ้น ปรับเปลี่ยนให้เหมาะกับรสนิยมการพิมพ์ที่น่ารัก


ไปป์ไลน์ชนิดนั้นถูกผูกไว้เพื่อล้มเหลวในขณะที่บางคีย์หรือค่าของอาร์เรย์มีอักขระใด ๆ ที่คุณกำลังแทนที่เช่นวงเล็บวงเล็บหรือเครื่องหมายคำพูด และท่อส่งของsedและtr's ไม่ได้มากง่ายกว่าวงด้วยfor printf
ilkkachu

นอกจากนี้คุณรู้หรือไม่ว่าการtrแปลตัวอักษรทีละตัวมันไม่ตรงกับสตริง? tr "]=" " ="เปลี่ยน "]" เป็นช่องว่างและ=เป็น=, โดยไม่คำนึงถึงตำแหน่ง ดังนั้นคุณอาจจะรวมทั้งสามtrเป็นหนึ่งเข้าด้วยกัน
ilkkachu

จริงมากเกี่ยวกับตัวละครที่ไม่ใช่ตัวอักษรและตัวเลขบางตัวที่ผสมสีนี้ อย่างไรก็ตามสิ่งที่ต้องจัดการกับพวกมันนั้นมีความซับซ้อนและไม่สามารถอ่านได้ดังนั้นถ้าไม่มีเหตุผลที่ดีจริงๆที่มีพวกมันอยู่ในฟีดข้อมูลของคุณและที่ระบุไว้ในคำถามที่ฉันคิดว่า ควรมีคำเตือนที่ชัดเจนของคุณอยู่เสมอ ฉันพบว่าท่อเหล่านี้เรียบง่ายกว่าตัวอย่างและจุดประสงค์ในการดีบั๊กกว่า printf glob ที่ทำงานได้อย่างสมบูรณ์หรือระเบิดขึ้นบนใบหน้าของคุณ ที่นี่คุณทำการเปลี่ยนแปลงง่าย ๆหนึ่งรายการต่อองค์ประกอบทดสอบแล้วเพิ่มอีก 1 รายการ
Nadreck

ความผิดฉันเอง! รับ _tr_s และ _sed_s ของฉันไปปนกันโดยสิ้นเชิง! แก้ไขในการแก้ไขล่าสุด
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 เพราะเร็วกว่ามาก

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.