มีหลายวิธีที่ใช้การได้เพื่อให้บรรลุเป้าหมายนี้
หากคุณต้องการที่จะยึดติดอยู่กับรุ่นเดิมของคุณอย่างใกล้ชิดมันสามารถทำได้ด้วยวิธีนี้:
getlist() {
IFS=$'\n'
for file in $(find . -iname 'foo*') ; do
printf 'File found: %s\n' "$file"
done
}
สิ่งนี้จะยังคงล้มเหลวหากชื่อไฟล์มีการขึ้นบรรทัดใหม่ตามตัวอักษร แต่ช่องว่างจะไม่แตก
อย่างไรก็ตามการ messing กับ IFS นั้นไม่จำเป็น นี่คือวิธีที่ฉันต้องการทำ:
getlist() {
while IFS= read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done < <(find . -iname 'foo*' -print0)
}
หากคุณพบว่า< <(command)
ไวยากรณ์ที่ไม่คุ้นเคยที่คุณควรอ่านเกี่ยวกับขั้นตอนการเปลี่ยนตัว ข้อดีของการทำเช่นนี้for file in $(find ...)
คือการจัดการไฟล์ที่มีช่องว่างการขึ้นบรรทัดใหม่และอักขระอื่น ๆ อย่างถูกต้อง สิ่งนี้ได้ผลเพราะfind
ด้วย-print0
จะใช้null
(aka \0
) เป็นตัวยุติสำหรับชื่อไฟล์แต่ละชื่อและไม่เหมือนกับบรรทัดใหม่ null ไม่ได้เป็นอักขระตามกฎหมายในชื่อไฟล์
ข้อได้เปรียบนี้มากกว่ารุ่นเกือบเทียบเท่า
getlist() {
find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done
}
คือการกำหนดตัวแปรใด ๆ ในเนื้อความของ while loop นั้นถูกสงวนไว้ นั่นคือถ้าคุณไปwhile
ที่ด้านบนแล้วเนื้อหาของwhile
อยู่ใน subshell ซึ่งอาจไม่เป็นสิ่งที่คุณต้องการ
ข้อได้เปรียบของรุ่นการทดแทนกระบวนการที่find ... -print0 | xargs -0
น้อยที่สุด: xargs
รุ่นนั้นใช้ได้ถ้าคุณต้องการพิมพ์บรรทัดหรือทำการดำเนินการเดี่ยวบนไฟล์ แต่ถ้าคุณต้องการดำเนินการหลายขั้นตอนเวอร์ชันลูปจะง่ายขึ้น
แก้ไข : นี่คือสคริปต์ทดสอบที่ดีเพื่อให้คุณสามารถรับทราบความแตกต่างระหว่างความพยายามที่แตกต่างกันในการแก้ปัญหานี้
#!/usr/bin/env bash
dir=/tmp/getlist.test/
mkdir -p "$dir"
cd "$dir"
touch 'file not starting foo' foo foobar barfoo 'foo with spaces'\
'foo with'$'\n'newline 'foo with trailing whitespace '
# while with process substitution, null terminated, empty IFS
getlist0() {
while IFS= read -d $'\0' -r file ; do
printf 'File found: '"'%s'"'\n' "$file"
done < <(find . -iname 'foo*' -print0)
}
# while with process substitution, null terminated, default IFS
getlist1() {
while read -d $'\0' -r file ; do
printf 'File found: '"'%s'"'\n' "$file"
done < <(find . -iname 'foo*' -print0)
}
# pipe to while, newline terminated
getlist2() {
find . -iname 'foo*' | while read -r file ; do
printf 'File found: '"'%s'"'\n' "$file"
done
}
# pipe to while, null terminated
getlist3() {
find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do
printf 'File found: '"'%s'"'\n' "$file"
done
}
# for loop over subshell results, newline terminated, default IFS
getlist4() {
for file in "$(find . -iname 'foo*')" ; do
printf 'File found: '"'%s'"'\n' "$file"
done
}
# for loop over subshell results, newline terminated, newline IFS
getlist5() {
IFS=$'\n'
for file in $(find . -iname 'foo*') ; do
printf 'File found: '"'%s'"'\n' "$file"
done
}
# see how they run
for n in {0..5} ; do
printf '\n\ngetlist%d:\n' $n
eval getlist$n
done
rm -rf "$dir"