ฉันมีตัวแปรที่มีเอาต์พุตหลายบรรทัดของคำสั่ง วิธีที่มีประสิทธิภาพมากที่สุดในการอ่านบรรทัดเอาต์พุตโดยบรรทัดจากตัวแปรคืออะไร
ตัวอย่างเช่น:
jobs="$(jobs)"
if [ "$jobs" ]; then
# read lines from $jobs
fi
ฉันมีตัวแปรที่มีเอาต์พุตหลายบรรทัดของคำสั่ง วิธีที่มีประสิทธิภาพมากที่สุดในการอ่านบรรทัดเอาต์พุตโดยบรรทัดจากตัวแปรคืออะไร
ตัวอย่างเช่น:
jobs="$(jobs)"
if [ "$jobs" ]; then
# read lines from $jobs
fi
คำตอบ:
คุณสามารถใช้วนลูปพร้อมกับการแทนที่กระบวนการ:
while read -r line
do
echo "$line"
done < <(jobs)
วิธีที่ดีที่สุดในการอ่านตัวแปรหลายบรรทัดคือการตั้งค่าIFS
ตัวแปรว่างและprintf
ตัวแปรที่มีบรรทัดใหม่ต่อท้าย:
# Printf '%s\n' "$var" is necessary because printf '%s' "$var" on a
# variable that doesn't end with a newline then the while loop will
# completely miss the last line of the variable.
while IFS= read -r line
do
echo "$line"
done < <(printf '%s\n' "$var")
หมายเหตุ: ตามshellcheck sc2031การใช้ substition ของกระบวนการจะดีกว่าไปป์เพื่อหลีกเลี่ยง [subtly] สร้าง subshell
นอกจากนี้โปรดทราบว่าการตั้งชื่อตัวแปรjobs
อาจทำให้เกิดความสับสนเนื่องจากเป็นชื่อของคำสั่งเชลล์ทั่วไป
echo
เพื่อprintf %s
ให้สคริปต์ของคุณจะทำงานได้แม้กับอินพุตที่ไม่เชื่อง
/tmp
ไดเรกทอรีที่สามารถเขียนได้เสมอเนื่องจากมันขึ้นอยู่กับความสามารถในการสร้างไฟล์งานชั่วคราว หากคุณเคยพบว่าตัวเองอยู่ในระบบที่ถูก จำกัด ด้วย/tmp
การอ่านอย่างเดียว (และไม่สามารถเปลี่ยนแปลงได้โดยคุณ) คุณจะมีความสุขกับความเป็นไปได้ที่จะใช้โซลูชันทางเลือกเช่นกับprintf
ไปป์
printf "%s\n" "$var" | while IFS= read -r line
ในการประมวลผลเอาต์พุตของบรรทัดคำสั่งทีละบรรทัด ( คำอธิบาย ):
jobs |
while IFS= read -r line; do
process "$line"
done
หากคุณมีข้อมูลในตัวแปรอยู่แล้ว:
printf %s "$foo" | …
printf %s "$foo"
เกือบจะเหมือนกันecho "$foo"
แต่พิมพ์$foo
ตัวอักษรในขณะที่echo "$foo"
อาจตีความ$foo
เป็นตัวเลือกคำสั่ง echo ถ้าเริ่มต้นด้วย-
และอาจขยายลำดับแบ็กสแลช$foo
ในเชลล์บางตัว
โปรดทราบว่าในเชลล์บางตัว (ash, bash, pdksh แต่ไม่ใช่ ksh หรือ zsh) ทางด้านขวาของไพพ์ไลน์จะทำงานในกระบวนการแยกต่างหากดังนั้นตัวแปรใด ๆ ที่คุณตั้งค่าในลูปจะหายไป ตัวอย่างเช่นสคริปต์นับบรรทัดต่อไปนี้พิมพ์ 0 ในเชลล์เหล่านี้:
n=0
printf %s "$foo" |
while IFS= read -r line; do
n=$(($n + 1))
done
echo $n
วิธีแก้ปัญหาคือการวางส่วนที่เหลือของสคริปต์ (หรืออย่างน้อยส่วนที่ต้องการค่า$n
จากลูป) ในรายการคำสั่ง:
n=0
printf %s "$foo" | {
while IFS= read -r line; do
n=$(($n + 1))
done
echo $n
}
หากการแสดงบนบรรทัดที่ไม่ว่างว่างนั้นดีพอและอินพุตไม่ใหญ่คุณสามารถใช้การแยกคำได้:
IFS='
'
set -f
for line in $(jobs); do
# process line
done
set +f
unset IFS
คำอธิบาย: การตั้งค่าIFS
เป็นหนึ่งบรรทัดใหม่ทำให้การแยกคำเกิดขึ้นที่บรรทัดใหม่เท่านั้น (ตรงข้ามกับอักขระช่องว่างใด ๆ ภายใต้การตั้งค่าเริ่มต้น) set -f
จะปิด globbing (เช่นการขยายตัวสัญลักษณ์แทน) ซึ่งจะเกิดขึ้นกับผลของการทดแทนคำสั่ง$(jobs)
หรือ $foo
substitutino for
วงทำหน้าที่เกี่ยวกับชิ้นส่วนทั้งหมดของ$(jobs)
ซึ่งเป็นทุกสายไม่ว่างเปล่าในการส่งออกคำสั่ง ในที่สุดคืนค่าการโค้งและIFS
การตั้งค่า
local IFS=something
ภายในฟังก์ชั่นการใช้งาน มันจะไม่ส่งผลกระทบต่อค่าขอบเขตทั่วโลก IIRC unset IFS
ไม่นำคุณกลับไปสู่ค่าเริ่มต้น (และไม่สามารถใช้งานได้หากไม่ใช่ค่าเริ่มต้นล่วงหน้า)
ปัญหา: หากคุณใช้ในขณะที่ลูปมันจะทำงานใน subshell และตัวแปรทั้งหมดจะหายไป วิธีแก้ปัญหา: ใช้สำหรับลูป
# change delimiter (IFS) to new line.
IFS_BAK=$IFS
IFS=$'\n'
for line in $variableWithSeveralLines; do
echo "$line"
# return IFS back if you need to split new line by spaces:
IFS=$IFS_BAK
IFS_BAK=
lineConvertedToArraySplittedBySpaces=( $line )
echo "{lineConvertedToArraySplittedBySpaces[0]}"
# return IFS back to newline for "for" loop
IFS_BAK=$IFS
IFS=$'\n'
done
# return delimiter to previous value
IFS=$IFS_BAK
IFS_BAK=
while read
ลูปใน bash หมายถึงขณะที่ลูปอยู่ใน subshell ดังนั้นตัวแปรจึงไม่ใช่โกลบอล while read;do ;done <<< "$var"
ทำให้ร่างกายลูปไม่ใช่ subshell (ทุบตีล่าสุดมีตัวเลือกที่จะทำให้ร่างกายของการเป็นcmd | while
ห่วงไม่ได้อยู่ใน subshell เช่น ksh ได้เคยมี.)
ในเวอร์ชัน bash ล่าสุดให้ใช้mapfile
หรือreadarray
อ่านเอาต์พุตคำสั่งในอาร์เรย์อย่างมีประสิทธิภาพ
$ readarray test < <(ls -ltrR)
$ echo ${#test[@]}
6305
คำเตือน: ตัวอย่างที่น่ากลัว แต่คุณสามารถใช้คำสั่งที่ดีกว่าในการใช้งานได้มากกว่าคำสั่ง ls ด้วยตัวคุณเอง
readarray
ฟังก์ชั่นและเรียกใช้ฟังก์ชันสองสามครั้ง
-r
เป็นความคิดที่ดีเช่นกัน มันช่วยป้องกัน\` interpretation... (it is in your links, but its probably worth mentioning, just to round out your
IFS = `(ซึ่งเป็นสิ่งสำคัญในการป้องกันการสูญเสียช่องว่าง)
คุณสามารถใช้ <<< เพื่ออ่านจากตัวแปรที่มีข้อมูลที่คั่นด้วยบรรทัดใหม่:
while read -r line
do
echo "A line of input: $line"
done <<<"$lines"
while IFS= read
.... ถ้าคุณต้องการป้องกันการตีความ \ ให้ใช้read -r