ฉันจะใช้การค้นหาได้อย่างไรเมื่อชื่อไฟล์มีช่องว่าง


17

ฉันต้องการไพพ์ชื่อไฟล์ไปยังโปรแกรมอื่น แต่พวกมันทั้งหมดทำให้หายใจไม่ออกเมื่อชื่อมีช่องว่าง

สมมติว่าฉันมีไฟล์ที่เรียกว่า

foo bar

ฉันfindจะคืนชื่อที่ถูกต้องได้อย่างไร

เห็นได้ชัดว่าฉันต้องการ:

foo\ bar

หรือ:

"foo bar"

แก้ไข : ฉันไม่ต้องการผ่านxargsฉันต้องการดึงสตริงที่จัดรูปแบบอย่างถูกต้องออกมาfindเพื่อที่ฉันจะได้ไพพ์สตริงของชื่อไฟล์โดยตรงไปยังโปรแกรมอื่น


5
คุณกำลังจะไปท่ออะไร คุณรู้จัก-execธงด้วยfindหรือไม่ คุณอาจจะสามารถบรรเทาข้อผิดพลาดนี้และทำให้คำสั่งของคุณมีประสิทธิภาพมากขึ้นโดยทำ-execแทนการไพพ์ไปยังคำสั่งอื่น แค่ $ .02 ของฉัน
h3rrmiller

6
@bug: จัดfindรูปแบบชื่อไฟล์ที่ใช้ได้ พวกเขาเขียนหนึ่งชื่อต่อบรรทัด (แน่นอนว่าสิ่งนี้คลุมเครือถ้าชื่อไฟล์มีอักขระขึ้นบรรทัดใหม่) ดังนั้นปัญหาก็คือ "สำลัก" ที่ได้รับเมื่อสิ้นสุดพื้นที่ซึ่งหมายความว่าคุณต้องบอกเราว่าจุดสิ้นสุดนั้นคืออะไรถ้าคุณต้องการคำตอบที่มีความหมาย .
rici

2
สิ่งที่คุณเรียกว่า "จัดรูปแบบอย่างถูกต้อง" คือ "หลบหนีเพื่อการบริโภคโดยเชลล์" ยูทิลิตี้ส่วนใหญ่ที่สามารถอ่านชื่อไฟล์หลาย ๆ ไฟล์จะสำลักกับชื่อเชลล์ที่หนีออกมา แต่ในความเป็นจริงมันสมเหตุสมผลแล้วที่findจะเสนอทางเลือกให้กับชื่อไฟล์ที่ส่งออกในรูปแบบที่เหมาะสมกับเชลล์ โดยทั่วไปแล้วส่วนขยาย-print0GNU นั้นfindใช้ได้ดีสำหรับสถานการณ์อื่น ๆ (เช่นกัน) และคุณควรเรียนรู้ที่จะใช้มันในทุกกรณี
tripleee

2
@bug: โดยวิธีการที่ไม่ถูกป้อนผ่านรายการls $(command...) stdinมันทำให้เอาท์พุทของ$(command...)โดยตรงในบรรทัดคำสั่ง ในกรณีนั้นมันคือเชลล์ที่อ่านจาก c และมันจะใช้ค่าปัจจุบันของ$IFSเพื่อตัดสินใจว่าจะใช้คำพูดกับเอาต์พุตอย่างไร xargsโดยทั่วไปคุณดีกว่าการใช้ คุณจะไม่สังเกตเห็นถึงประสิทธิภาพ
rici

2
find -printf '"%p"\n'จะเพิ่มเครื่องหมายคำพูดคู่รอบชื่อที่พบ แต่จะไม่อ้างอิงเครื่องหมายคำพูดคู่ใด ๆ ในชื่อไฟล์อย่างถูกต้อง ถ้าชื่อไฟล์ของคุณไม่ได้มีคำพูดคู่ใด ๆ ที่ฝังตัวคุณสามารถละเว้นปัญหา: sed 's/"/&&/g;s/^""/"/;s/""$/"/'หรือท่อผ่าน หากชื่อไฟล์ของคุณถูกจัดการโดยเชลล์คุณควรใช้เครื่องหมายคำพูดเดี่ยวแทนเครื่องหมายคำพูดคู่ (แต่sweet$HOMEจะกลายเป็นอย่างอื่นsheet/home/you) และนี่ยังคงไม่แข็งแกร่งมากเมื่อเทียบกับชื่อไฟล์ที่มีบรรทัดใหม่ในนั้น คุณต้องการจัดการสิ่งเหล่านั้นอย่างไร?
tripleee

คำตอบ:


18

POSIXLY:

find . -type f -exec sh -c '
  for f do
    : command "$f"
  done
' sh {} +

ด้วยการfindสนับสนุน-print0และxargsรองรับ-0:

find . -type f -print0 | xargs -0 <command>

-0 ตัวเลือกบอกให้ xargs ใช้อักขระ ASCII NUL แทนเว้นวรรคเพื่อสิ้นสุด (แยก) ชื่อไฟล์

ตัวอย่าง:

find . -maxdepth 1 -type f -print0 | xargs -0 ls -l

ใช้งานไม่ได้ เมื่อฉันเรียกใช้ls $(find . -maxdepth 1 -type f -print0 | xargs -0)ฉันได้รับ ls: cannot access ./foo: No such file or directory ls: cannot access bar: No such file or directory
ข้อผิดพลาด

1
คุณลองวิธีที่ Gnouc เขียนจริง ๆ หรือไม่? หากคุณยืนยันในการทำในแบบของคุณให้ลองใส่$(..)เครื่องหมายคำพูดคู่"$(..)"
evilsoup

3
@bug: คำสั่งของคุณผิด ลองว่าฉัน Worte และอ่าน manpage ของและfind xargs
cuonglm

ฉันเห็นแล้วอีกครั้งฉันต้องการรับสตริงที่จัดรูปแบบซึ่งฉันสามารถไพพ์โดยตรง
ข้อผิดพลาด

1
@bug: ใช้ xargs -0 <โปรแกรมของคุณ>
cuonglm

10

การใช้-print0เป็นตัวเลือกเดียว แต่ไม่ใช่ทุกโปรแกรมที่สนับสนุนการใช้สตรีมข้อมูลที่มีค่า nullbyte ดังนั้นคุณจะต้องใช้xargsกับ-0ตัวเลือกสำหรับบางสิ่งตามที่ระบุไว้ในคำตอบของ Gnouc

ทางเลือกที่จะไปใช้find's -execหรือ-execdirตัวเลือก รายการแรกของรายการต่อไปนี้จะป้อนชื่อไฟล์somecommandทีละรายการในขณะที่รายการที่สองจะขยายเป็นรายการไฟล์:

find . -type f -exec somecommand '{}' \;
find . -type f -exec somecommand '{}' +

คุณอาจพบว่าคุณดีกว่าการใช้การปัดเศษในหลาย ๆ กรณี หากคุณมีเปลือกที่ทันสมัย ​​(ทุบตี 4+, zsh, ksh) คุณจะได้รับการวนซ้ำแบบซ้ำด้วยglobstar( **) ในทุบตีคุณต้องตั้งค่านี้:

shopt -s globstar
somecommand ./**/*.txt ## feeds all *.txt files to somecommand, recursively

ฉันมีบรรทัดที่พูดshopt -s globstar extglobใน. bashrc ของฉันดังนั้นนี่จึงเปิดใช้งานสำหรับฉันเสมอ (และมีการขยายช่วงเวลาซึ่งเป็นประโยชน์เช่นกัน)

หากคุณไม่ต้องการความซ้ำซากจำเจก็แค่ใช้./*.txtแทนเพื่อใช้ทุก * .txt ในไดเรกทอรีทำงาน findมีความสามารถในการค้นหาอย่างละเอียดที่มีประโยชน์มากและจำเป็นสำหรับไฟล์นับหมื่น (ณ จุดนี้คุณจะพบจำนวนอาร์กิวเมนต์สูงสุดของเชลล์) แต่สำหรับการใช้งานแบบวันต่อวันมักไม่จำเป็น


สวัสดี @evilsoup {} ทำอะไรในสคริปต์นี้
Ayusman

3

โดยส่วนตัวฉันจะใช้การ-execค้นหาเพื่อแก้ไขปัญหาประเภทนี้ หรือถ้าจำเป็นxargsซึ่งช่วยให้สามารถดำเนินการแบบขนานได้

อย่างไรก็ตามมีวิธีในfindการสร้างรายการชื่อไฟล์ที่สามารถอ่านได้ ไม่น่าแปลกใจที่มันใช้-execและbashโดยเฉพาะอย่างยิ่งส่วนขยายของprintfคำสั่ง:

find ... -exec bash -c 'printf "%q " "$@"' printf {} ';'

อย่างไรก็ตามในขณะที่จะพิมพ์ออกมาอย่างถูกต้องคำเปลือกหนีมันจะไม่สามารถใช้งานได้$(...)เพราะ$(...)ไม่ได้ตีความคำพูดหรือการหลบหนี (resut ของ$(...)อาจมีการแยกคำและการขยายชื่อพา ธ เว้นแต่ล้อมรอบด้วยคำพูด) ดังนั้นต่อไปนี้จะไม่ทำสิ่งที่คุณต้องการ:

ls $(find ... -exec bash -c 'printf "%q " "$@"' printf {} +)

สิ่งที่คุณต้องทำคือ:

eval "ls $(find ... -exec bash -c 'printf "%q " "$@"' printf {} +)"

(โปรดทราบว่าฉันไม่ได้พยายามทดสอบสิ่งประหลาดประหลาดข้างต้น)

แต่คุณก็อาจจะทำเช่นกัน:

find ... -exec ls {} +

ฉันไม่คิดว่าlsสถานการณ์จับกรณีการใช้ของ OP อย่างเพียงพอ แต่นี่เป็นเพียงการเก็งกำไรเนื่องจากเราไม่ได้แสดงให้เห็นว่าเขากำลังพยายามทำให้สำเร็จจริง ๆ วิธีนี้ใช้งานได้ดีจริง ๆ ; ฉันได้ผลลัพธ์ที่ฉันคาดหวังสำหรับชื่อไฟล์ตลกทั้งหมดที่ฉันพยายามรวมถึงtouch "$(tr a-z '\001-\026' <<<'the quick brown fox jumped over the lazy dogs')"
tripleee

@triplee: ฉันไม่รู้ว่า OP ต้องการทำอะไรเช่นกัน ประโยชน์เฉพาะที่แท้จริงของการสร้างสายยกเพื่อส่งผ่านไปevalคือการที่คุณจะได้ไม่ต้องผ่านมันไปevalยัง; คุณสามารถบันทึกไว้ในพารามิเตอร์และใช้ในภายหลังอาจจะหลายครั้งด้วยคำสั่งที่แตกต่างกัน อย่างไรก็ตาม OP ให้ข้อบ่งชี้ว่าเป็นกรณีที่ใช้ (และถ้ามันก็อาจจะดีกว่าที่จะใส่ชื่อไฟล์เป็นอาร์เรย์ แต่ที่ยุ่งยากเกินไป.)
RICI

0
find ./  | grep " "

คุณจะได้รับไฟล์และไดเรกทอรีที่มีช่องว่าง

find ./ -type f  | grep " " 

คุณจะได้รับไฟล์ที่มีช่องว่าง

find ./ -type d | grep " "

คุณจะได้รับไดเรกทอรีที่มีช่องว่าง


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.