ใช้วนซ้ำเช่น
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
หมายเหตุ
ฉันไม่เห็นด้วยกับbash
bashers เพราะ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 -print0
xargs -0