ฉันจะแยกวิเคราะห์เอาต์พุตของคำสั่ง find ได้อย่างไรเมื่อชื่อไฟล์มีช่องว่างอยู่ในนั้น


12

ใช้วนซ้ำเช่น

for i in `find . -name \*.txt` 

จะแตกถ้าชื่อไฟล์บางอันมีช่องว่างอยู่

ฉันสามารถใช้เทคนิคใดในการหลีกเลี่ยงปัญหานี้


1
โปรดทราบว่าไฟล์สามารถมีบรรทัดใหม่ในชื่อไฟล์ นั่นเป็นเหตุผลที่มีและfind -print0 xargs -0
Daniel Beck

คำตอบ:


12

เป็นการดีที่คุณไม่ทำอย่างนั้นเพราะการแยกชื่อไฟล์อย่างถูกต้องในเชลล์สคริปต์นั้นยากเสมอ (แก้ไขช่องว่างคุณจะยังคงมีปัญหากับอักขระฝังตัวอื่น ๆ ในบรรทัดใหม่โดยเฉพาะ) นี่คือรายการที่เป็นรายการแรกในหน้า 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 แทนเชลล์


1
ฉันชอบความคิดที่จะใช้ Python เพื่อหลีกเลี่ยงปัญหาทั้งหมดนี้
Scott C Wilson

12

ใช้find -print0และไพพ์ไปที่xargs -0หรือเขียนโปรแกรม C ตัวน้อยของคุณเองและไพพ์ลงในโปรแกรม C ตัวน้อยของคุณ นี่คือสิ่งที่-print0และ-0ถูกคิดค้นขึ้นสำหรับ

เชลล์สคริปไม่ใช่วิธีที่ดีที่สุดในการจัดการชื่อไฟล์ด้วยการเว้นวรรค: คุณสามารถทำได้


ทำงานบนเครื่องของฉัน ^ TM!
mcandre

2

คุณสามารถตั้งค่า "ตัวคั่นเขตข้อมูลภายใน" ( 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วงเต็ม


1

ใช้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
หมายเหตุ

1

ฉันไม่เห็นด้วยกับ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\; `)


1
มีความถูกต้องทั้งมุมมอง เมื่อฉันทำงานกับไฟล์ของตัวเองฉันจะใช้ find และไม่ต้องกังวลเพราะไฟล์ของฉันไม่มีช่องว่าง (หรือ carriage return!) ในชื่อของพวกเขา แต่เมื่อคุณเริ่มทำงานกับไฟล์ของคนอื่นคุณต้องใช้เทคนิคที่แข็งแกร่งกว่านี้
Scott C Wilson
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.