การอ่านผลลัพธ์ของคำสั่งในอาร์เรย์ใน Bash


114

ฉันต้องการอ่านผลลัพธ์ของคำสั่งในสคริปต์ของฉันลงในอาร์เรย์ คำสั่งคือตัวอย่าง:

ps aux | grep | grep | x 

และให้เอาต์พุตทีละบรรทัดดังนี้:

10
20
30

ฉันต้องการอ่านค่าจากเอาต์พุตคำสั่งลงในอาร์เรย์จากนั้นฉันจะทำงานบางอย่างหากขนาดของอาร์เรย์น้อยกว่าสาม


5
เฮ้ @barp ตอบคำถามของคุณเกรงว่าประเภทของคุณจะเป็นการระบายกับคนทั้งชุมชน
เจมส์

9
@ เจมส์ปัญหาไม่ใช่เพราะเขาไม่ตอบคำถาม ... นี่คือไซต์ถาม / ตอบ เขาไม่ได้ทำเครื่องหมายเป็นคำตอบ เขาควรทำเครื่องหมายไว้ คำใบ้ @ barp
DDPWNAGE

4
กรุณา @barp ทำเครื่องหมายคำถามว่าตอบแล้ว
smonff

ที่เกี่ยวข้อง: การวนซ้ำเนื้อหาของไฟล์ใน Bashเนื่องจากการอ่านผลลัพธ์ของคำสั่งผ่านการแทนที่กระบวนการจะคล้ายกับการอ่านจากไฟล์
codeforester

คำตอบ:


166

คำตอบอื่น ๆ จะแตกถ้าผลลัพธ์ของคำสั่งมีช่องว่าง (ซึ่งค่อนข้างบ่อย) หรือ glob ตัวละครชอบ*, ,?[...]

เพื่อให้ได้ผลลัพธ์ของคำสั่งในอาร์เรย์โดยมีหนึ่งบรรทัดต่อองค์ประกอบโดยพื้นฐานแล้วมี 3 วิธี:

  1. ด้วยการใช้Bash≥4 mapfile- มีประสิทธิภาพมากที่สุด:

    mapfile -t my_array < <( my_command )
  2. มิฉะนั้นลูปที่อ่านเอาต์พุต (ช้าลง แต่ปลอดภัย):

    my_array=()
    while IFS= read -r line; do
        my_array+=( "$line" )
    done < <( my_command )
  3. ตามที่ Charles Duffy แนะนำในความคิดเห็น (ขอบคุณ!) สิ่งต่อไปนี้อาจทำงานได้ดีกว่าวิธีการวนซ้ำในข้อ 2:

    IFS=$'\n' read -r -d '' -a my_array < <( my_command && printf '\0' )

    โปรดตรวจสอบว่าคุณใช้แบบฟอร์มนี้ทุกประการเช่นตรวจสอบว่าคุณมีสิ่งต่อไปนี้:

    • IFS=$'\n' ในบรรทัดเดียวกับreadคำสั่ง:สิ่งนี้จะตั้งค่าตัวแปรสภาพแวดล้อมIFS สำหรับreadคำสั่งเท่านั้น ดังนั้นจะไม่มีผลกับสคริปต์ที่เหลือเลย วัตถุประสงค์ของตัวแปรนี้คือการบอกที่จะทำลายกระแสที่ตัวละครread EOL\n
    • -r: นี้เป็นสิ่งสำคัญ. มันบอกread ให้ไม่ตีความแบ็กสแลชเป็นลำดับการหลีกเลี่ยง
    • -d '': โปรดทราบช่องว่างระหว่างที่ตัวเลือกและอาร์กิวเมนต์-d ''หากคุณไม่เว้นวรรคตรง''นี้จะไม่มีใครเห็นเนื่องจากจะหายไปในขั้นตอนการลบใบเสนอราคาเมื่อ Bash แยกวิเคราะห์คำสั่ง สิ่งนี้บอกreadให้หยุดอ่านที่ศูนย์ไบต์ บางคนเขียนเป็น-d $'\0'แต่จริงๆแล้วไม่จำเป็น -d ''จะดีกว่า.
    • -a my_arrayบอกreadให้เติมอาร์เรย์my_arrayขณะอ่านสตรีม
    • คุณต้องใช้printf '\0'คำสั่งหลัง my_commandเพื่อให้readผลตอบแทน0; จริงๆแล้วมันไม่ใช่เรื่องใหญ่ถ้าคุณไม่ทำ (คุณจะได้รับรหัสส่งคืน1ซึ่งก็โอเคถ้าคุณไม่ใช้set -e- ซึ่งคุณไม่ควรอยู่แล้ว) แต่จงจำไว้ มันสะอาดและถูกต้องตามความหมายมากขึ้น โปรดทราบว่าสิ่งนี้แตกต่างจากprintf ''ที่ไม่แสดงผลอะไรเลย printf '\0'พิมพ์ null ไบต์ซึ่งจำเป็นreadสำหรับการหยุดอ่านอย่างมีความสุข (จำ-d ''ตัวเลือกนี้ได้หรือไม่)

หากคุณสามารถทำได้เช่นถ้าคุณแน่ใจว่าโค้ดของคุณจะทำงานบนBash≥4ให้ใช้วิธีแรก และคุณจะเห็นว่ามันสั้นลงด้วย

หากคุณต้องการใช้readการวนซ้ำ (วิธีที่ 2) อาจมีข้อได้เปรียบเหนือวิธีที่ 3 หากคุณต้องการประมวลผลบางอย่างเมื่ออ่านบรรทัด: คุณสามารถเข้าถึงได้โดยตรง (ผ่าน$lineตัวแปรในตัวอย่างที่ฉันให้) และ คุณยังสามารถเข้าถึงบรรทัดที่อ่านแล้ว (ผ่านอาร์เรย์${my_array[@]}ในตัวอย่างที่ฉันให้)

โปรดทราบว่าmapfileมีวิธีการประเมินการเรียกกลับในแต่ละบรรทัดที่อ่านและในความเป็นจริงคุณสามารถบอกได้ว่าให้เรียกเฉพาะการโทรกลับนี้ทุกๆNบรรทัดที่อ่าน ดูhelp mapfileและตัวเลือก-Cและใน-cนั้น (ความคิดเห็นของฉันเกี่ยวกับเรื่องนี้คือมันค่อนข้างเกะกะเล็กน้อย แต่สามารถใช้ได้ในบางครั้งหากคุณมีเพียงสิ่งง่ายๆที่ต้องทำ - ฉันไม่เข้าใจจริงๆว่าทำไมสิ่งนี้ถึงถูกนำมาใช้ตั้งแต่แรก!)


ตอนนี้ฉันจะบอกคุณว่าทำไมต้องใช้วิธีต่อไปนี้:

my_array=( $( my_command) )

เสียเมื่อมีช่องว่าง:

$ # I'm using this command to test:
$ echo "one two"; echo "three four"
one two
three four
$ # Now I'm going to use the broken method:
$ my_array=( $( echo "one two"; echo "three four" ) )
$ declare -p my_array
declare -a my_array='([0]="one" [1]="two" [2]="three" [3]="four")'
$ # As you can see, the fields are not the lines
$
$ # Now look at the correct method:
$ mapfile -t my_array < <(echo "one two"; echo "three four")
$ declare -p my_array
declare -a my_array='([0]="one two" [1]="three four")'
$ # Good!

จากนั้นบางคนจะแนะนำให้ใช้IFS=$'\n'เพื่อแก้ไข:

$ IFS=$'\n'
$ my_array=( $(echo "one two"; echo "three four") )
$ declare -p my_array
declare -a my_array='([0]="one two" [1]="three four")'
$ # It works!

แต่ตอนนี้เรามาใช้คำสั่งอื่นกับglobs :

$ echo "* one two"; echo "[three four]"
* one two
[three four]
$ IFS=$'\n'
$ my_array=( $(echo "* one two"; echo "[three four]") )
$ declare -p my_array
declare -a my_array='([0]="* one two" [1]="t")'
$ # What?

นั่นเป็นเพราะฉันมีไฟล์ที่เรียกว่าtในไดเร็กทอรีปัจจุบัน ... และชื่อไฟล์นี้ถูกจับคู่โดยglob [three four] ... ณ จุดนี้บางคนแนะนำให้ใช้set -fเพื่อปิดการใช้งาน globbing: แต่ดูที่มัน: คุณต้องเปลี่ยนIFSและใช้set -fเพื่อให้สามารถแก้ไข เทคนิคหัก (และคุณไม่ได้ซ่อมจริงๆ)! เมื่อทำว่าเรากำลังจริงๆต่อสู้กับเปลือกที่ไม่ได้ทำงานกับเปลือก

$ mapfile -t my_array < <( echo "* one two"; echo "[three four]")
$ declare -p my_array
declare -a my_array='([0]="* one two" [1]="[three four]")'

ที่นี่เรากำลังทำงานกับเชลล์!


4
ยอดเยี่ยมมากฉันไม่เคยได้ยินมาmapfileก่อนนี่คือสิ่งที่ฉันขาดหายไปมาหลายปี ฉันเดาว่าเวอร์ชันล่าสุดbashมีคุณสมบัติใหม่ ๆ ที่ดีมากมายฉันควรใช้เวลาสองสามวันในการอ่านเอกสารและเขียนกลโกงที่ดี
Gene Pavlovsky

6
Btw ในการใช้ไวยากรณ์นี้< <(command)ในเชลล์สคริปต์บรรทัด Shebang ควรเป็น#!/bin/bash- ถ้ารันเป็น#!/bin/shbash จะออกพร้อมข้อผิดพลาดทางไวยากรณ์
Gene Pavlovsky

1
การขยายหมายเหตุที่เป็นประโยชน์ของ @ GenePavlovsky สคริปต์จะต้องทำงานด้วยคำสั่ง bash bash my_script.shไม่ใช่คำสั่ง shsh my_script.sh
Vito

2
@Vito: แน่นอนคำตอบนี้มีไว้สำหรับ Bash เท่านั้น แต่นี่ไม่ควรเป็นปัญหาเนื่องจากเชลล์ POSIX ที่ปฏิบัติตามอย่างเคร่งครัดไม่ได้ใช้อาร์เรย์ ( shและdashไม่รู้เกี่ยวกับอาร์เรย์เลยยกเว้นแน่นอนสำหรับ$@อาร์เรย์พารามิเตอร์ตำแหน่ง)
gniourf_gniourf

3
เป็นทางเลือกอื่นที่ไม่ต้องใช้ bash 4.0 ให้พิจารณาIFS=$'\n' read -r -d '' -a my_array < <(my_command && printf '\0')- ทั้งสองทำงานได้อย่างถูกต้องใน bash 3.x และยังส่งผ่านสถานะการออกที่ล้มเหลวจากmy_commandไปยังไฟล์read.
Charles Duffy

87

คุณสามารถใช้ได้

my_array=( $(<command>) )

ในการจัดเก็บผลลัพธ์ของคำสั่งลงในอาร์เรย์<command>my_array

คุณสามารถเข้าถึงความยาวของอาร์เรย์นั้นได้โดยใช้

my_array_length=${#my_array[@]}

ตอนนี้ความยาวถูกเก็บไว้ในmy_array_length.


19
จะเกิดอะไรขึ้นถ้าผลลัพธ์ของ $ (command) มีช่องว่างและหลายบรรทัดพร้อมช่องว่าง? ฉันเพิ่ม "$ (คำสั่ง)" และวางเอาต์พุตทั้งหมดจากทุกบรรทัดลงในองค์ประกอบ [0] แรกของอาร์เรย์
ikwyl6

3
@ ikwyl6 วิธีแก้ปัญหาคือการกำหนดเอาต์พุตคำสั่งให้กับตัวแปรจากนั้นสร้างอาร์เรย์ด้วยหรือเพิ่มลงในอาร์เรย์ VAR="$(<command>)"แล้วmy_array=("$VAR")หรือmy_array+=("$VAR")
Vito

11

ลองนึกภาพว่าคุณกำลังจะใส่ไฟล์และชื่อไดเร็กทอรี (ภายใต้โฟลเดอร์ปัจจุบัน) ไปยังอาร์เรย์และนับรายการต่างๆ บทจะเป็นอย่างไร;

my_array=( `ls` )
my_array_length=${#my_array[@]}
echo $my_array_length

หรือคุณสามารถทำซ้ำบนอาร์เรย์นี้ได้โดยเพิ่มสคริปต์ต่อไปนี้:

for element in "${my_array[@]}"
do
   echo "${element}"
done

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


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