ใช้วนซ้ำเช่น
for i in `find . -name \*.txt`
จะแตกถ้าชื่อไฟล์บางอันมีช่องว่างอยู่
ฉันสามารถใช้เทคนิคใดในการหลีกเลี่ยงปัญหานี้
ใช้วนซ้ำเช่น
for i in `find . -name \*.txt`
จะแตกถ้าชื่อไฟล์บางอันมีช่องว่างอยู่
ฉันสามารถใช้เทคนิคใดในการหลีกเลี่ยงปัญหานี้
คำตอบ:
เป็นการดีที่คุณไม่ทำอย่างนั้นเพราะการแยกชื่อไฟล์อย่างถูกต้องในเชลล์สคริปต์นั้นยากเสมอ (แก้ไขช่องว่างคุณจะยังคงมีปัญหากับอักขระฝังตัวอื่น ๆ ในบรรทัดใหม่โดยเฉพาะ) นี่คือรายการที่เป็นรายการแรกในหน้า BashPitfalls
ที่กล่าวว่ามีวิธีที่จะทำสิ่งที่คุณต้องการ:
oIFS=$IFS
IFS=$'\n'
find . -name '*.txt' | while read -r i; do
# use "$i" with whatever you're doing
done
IFS=$oIFS
อย่าลืมอ้างคำพูด$iเมื่อใช้มันด้วยเพื่อหลีกเลี่ยงสิ่งอื่น ๆ ที่เป็นการตีความช่องว่างในภายหลัง โปรดจำไว้ว่าให้ตั้งค่า$IFSกลับมาหลังจากใช้งานเพราะไม่ทำเช่นนั้นจะทำให้เกิดข้อผิดพลาดที่สับสนในภายหลัง
สิ่งนี้จะมีข้อแม้อื่นอีกหนึ่งอย่างแนบมา: สิ่งที่เกิดขึ้นภายในwhileลูปอาจเกิดขึ้นในเชลล์ย่อยขึ้นอยู่กับเชลล์ที่คุณใช้งานดังนั้นการตั้งค่าตัวแปรอาจไม่คงอยู่ forหลีกเลี่ยงรุ่นห่วง แต่ในราคาที่แม้ว่าคุณจะใช้$IFSวิธีการแก้ปัญหาการหลีกเลี่ยงการมีช่องว่างแล้วคุณจะได้รับในปัญหาถ้าfindไฟล์ผลตอบแทนที่มากเกินไป
ในบางจุดการแก้ไขที่ถูกต้องสำหรับสิ่งนี้จะทำในภาษาเช่น Perl หรือ Python แทนเชลล์
ใช้find -print0และไพพ์ไปที่xargs -0หรือเขียนโปรแกรม C ตัวน้อยของคุณเองและไพพ์ลงในโปรแกรม C ตัวน้อยของคุณ นี่คือสิ่งที่-print0และ-0ถูกคิดค้นขึ้นสำหรับ
เชลล์สคริปไม่ใช่วิธีที่ดีที่สุดในการจัดการชื่อไฟล์ด้วยการเว้นวรรค: คุณสามารถทำได้
คุณสามารถตั้งค่า "ตัวคั่นเขตข้อมูลภายใน" ( IFS) เป็นอย่างอื่นนอกเหนือจากช่องว่างสำหรับการแยกการวนรอบอาร์กิวเมนต์เช่น
ORIGIFS=${IFS}
NL='
'
IFS=${NL}
for i in $(find . -name '*.txt'); do
IFS=${ORIGIFS}
#do stuff
done
IFS=${ORIGIFS}
ฉันรีเซ็ตIFSหลังจากใช้งานในการค้นหาส่วนใหญ่เป็นเพราะมันดูดีฉันคิดว่า ฉันไม่เห็นปัญหาใด ๆ ในการตั้งค่าเป็นบรรทัดใหม่ แต่ฉันคิดว่านี่เป็น "สะอาดกว่า"
อีกวิธีหนึ่งก็ขึ้นอยู่กับสิ่งที่คุณต้องการจะทำอย่างไรกับการส่งออกจากfindคือการใช้ทั้งทางตรง-execกับfindคำสั่งหรือการใช้งานและท่อลงใน-print0 xargs -0ในกรณีแรกfindจะดูแลการหลีกเลี่ยงชื่อไฟล์ ใน-print0กรณีที่findพิมพ์ออกมาด้วยตัวคั่น null แล้วxargsแยกบนนี้ เนื่องจากชื่อไฟล์ไม่สามารถมีตัวละครนั้น (สิ่งที่ฉันรู้) จึงปลอดภัยเช่นกัน มันมีประโยชน์ส่วนใหญ่ในกรณีง่าย ๆ ; และมักจะไม่ได้เป็นตัวแทนที่ดีสำหรับforวงเต็ม
find -print0กับxargs -0การใช้งานfind -print0ร่วมกับxargs -0ชื่อไฟล์ทางกฎหมายนั้นสมบูรณ์และเป็นหนึ่งในวิธีที่สามารถขยายได้ ตัวอย่างเช่นสมมติว่าคุณต้องการรายชื่อไฟล์ PDF ทุกไฟล์ภายในไดเรกทอรีปัจจุบัน คุณสามารถเขียน
$ find . -iname '*.pdf' -print0 | xargs -0 -n 1 echo
สิ่งนี้จะพบไฟล์ PDF ทุกไฟล์ (ผ่าน-iname '*.pdf') ในไดเรกทอรีปัจจุบัน ( .) และไดเรกทอรีย่อยใด ๆ และส่งแต่ละไฟล์เป็นอาร์กิวเมนต์ไปยังechoคำสั่ง เนื่องจากเราระบุ-n 1ตัวเลือกxargsเราจะส่งผ่านอาร์กิวเมนต์ได้ครั้งละหนึ่งอาร์กิวเมนต์echoเท่านั้น มีที่เรามองข้ามตัวเลือกที่จะได้ผ่านไปให้มากที่สุดเท่าที่เป็นไปได้xargs echo(คุณสามารถecho short input | xargs --show-limitsดูจำนวนไบต์ที่อนุญาตในบรรทัดคำสั่ง)
xargsทำว่า?เราสามารถเห็นได้ชัดเจนผลกระทบที่xargsมีต่อปัจจัยการผลิต - และผลกระทบของ-nโดยเฉพาะอย่างยิ่งใน - echoโดยใช้สคริปต์ซึ่งสะท้อนการขัดแย้งในลักษณะที่แม่นยำมากขึ้นกว่า
$ cat > echoArgs.sh <<'EOF'
#!/bin/bash
echo "Number of arguments: $#"
[[ $# -eq 0 ]] && exit
for i in $(seq 1 $#); do
echo "Arg $i: <$1>"
shift
done
EOF
$ find . -iname '*.pdf' -print0 | xargs -0 ./echoArgs.sh
$ find . -iname '*.pdf' -print0 | xargs -0 -n 1 ./echoArgs.sh
โปรดทราบว่าสามารถจัดการช่องว่างและบรรทัดใหม่ได้อย่างสมบูรณ์แบบ
$ touch 'A space-age
new line of vending machines.pdf'
$ find . -iname '*space*' -print0 | xargs -0 -n 1 ./echoArgs.sh
ซึ่งจะเป็นปัญหาโดยเฉพาะอย่างยิ่งกับการแก้ปัญหาทั่วไปต่อไปนี้:
chmod +x ./echoArgs.sh
for file in $(ls *spacey*); do
./echoArgs.sh "$file"
done
หมายเหตุ
ฉันไม่เห็นด้วยกับbashbashers เพราะbashพร้อมกับชุดเครื่องมือ * nix ค่อนข้างเชี่ยวชาญในการจัดการไฟล์ (รวมถึงคนที่มีชื่อมีช่องว่างฝังตัว)
ที่จริงfindช่วยให้คุณควบคุมเม็ดละเอียดมากกว่าการเลือกไฟล์ที่จะดำเนินการในด้าน ... ทุบตีที่คุณต้องการจริงๆเท่านั้นที่จะรู้ว่าคุณจะต้องทำให้คุณสตริงเข้าbash words; โดยทั่วไปแล้วโดยใช้ "เครื่องหมายคำพูดคู่" หรือกลไกอื่น ๆ เช่นใช้ IFS หรือค้นหา{}
โปรดทราบว่าในสถานการณ์ส่วนใหญ่ / หลายสถานการณ์คุณไม่จำเป็นต้องตั้งค่าและรีเซ็ต IFS เพียงใช้ IFS ในเครื่องตามที่แสดงในตัวอย่างด้านล่าง ทั้งสามจัดการช่องว่างได้ดี นอกจากนี้คุณไม่ต้องการโครงสร้างลูป "มาตรฐาน" เนื่องจากfind's \; เป็นลูปอย่างมีประสิทธิภาพ เพียงแค่ใส่ตรรกะวงของคุณลงในฟังก์ชั่นทุบตี (ถ้าคุณไม่ได้เรียกเครื่องมือมาตรฐาน)
IFS=$'\n' find ~/ -name '*.txt' -exec function-or-util {} \;
และอีกสองตัวอย่าง
IFS=$'\n' find ~/ -name '*.txt' -exec printf 'Hello %s\n' {} \;
IFS=$'\n' find ~/ -name '*.txt' -exec echo {} \+ |sed 's/home//'
'find also allows you to pass multiple filenames as args to you script ..(if it suits your need: use+ instead\; `)
find -print0xargs -0