เหตุใด find -exec mv {} ./target/ + จึงไม่ทำงาน


98

ฉันต้องการที่จะรู้ว่าสิ่งที่{} \;และ{} \+และ| xargs ...ทำ โปรดชี้แจงสิ่งเหล่านี้พร้อมคำอธิบาย

คำสั่งด้านล่าง 3 คำสั่งรันและผลลัพธ์เดียวกัน แต่คำสั่งแรกใช้เวลาเล็กน้อยและรูปแบบก็แตกต่างกันเล็กน้อย

find . -type f -exec file {} \;
find . -type f -exec file {} \+
find . -type f | xargs file

เป็นเพราะตัวแรกรันfileคำสั่งสำหรับทุกไฟล์ที่มาจากfindคำสั่ง โดยทั่วไปแล้วจะทำงานเป็น:

file file1.txt
file file2.txt

แต่ 2 หลังพบด้วย-execคำสั่งเรียกใช้คำสั่งไฟล์หนึ่งครั้งสำหรับไฟล์ทั้งหมดเช่นด้านล่าง:

file file1.txt file2.txt

จากนั้นฉันเรียกใช้คำสั่งต่อไปนี้ซึ่งคำสั่งแรกทำงานโดยไม่มีปัญหา แต่คำสั่งที่สองให้ข้อความแสดงข้อผิดพลาด

find . -type f -iname '*.cpp' -exec mv {} ./test/ \;
find . -type f -iname '*.cpp' -exec mv {} ./test/ \+ #gives error:find: missing argument to `-exec'

สำหรับคำสั่ง{} \+มันทำให้ฉันมีข้อความแสดงข้อผิดพลาด

find: missing argument to `-exec'

ทำไมเป็นอย่างนั้น? ใครช่วยอธิบายหน่อยได้ไหมว่าฉันทำอะไรผิด?


คำถามจริงง่ายๆทำไมอันแรกใช้งานได้และอันที่สองไม่ (1) ค้นหา - พิมพ์ f - ชื่อ ' .cpp' -exec mv {} ./test/ \; (2) ค้นหา -type f -iname ' .cpp' -exec mv {} ./test/ \ +
Shahadat Hossain

คำตอบ:


186

หน้าคู่มือ (หรือคู่มือ GNU ออนไลน์ ) สวยมากอธิบายทุกอย่าง

ค้นหาคำสั่ง -exec {} \;

สำหรับแต่ละผลลัพธ์command {}จะถูกดำเนินการ การเกิดขึ้นทั้งหมด{}จะถูกแทนที่ด้วยชื่อไฟล์ ;ถูกนำหน้าด้วยเครื่องหมายทับเพื่อป้องกันไม่ให้เชลล์แปลความหมาย

ค้นหาคำสั่ง -exec {} +

ผลลัพธ์แต่ละรายการจะถูกต่อท้ายcommandและดำเนินการในภายหลัง เมื่อคำนึงถึงข้อจำกัดความยาวของคำสั่งฉันเดาว่าคำสั่งนี้อาจถูกเรียกใช้หลายครั้งโดยที่หน้าคู่มือรองรับฉัน:

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

สังเกตคำพูดนี้จากหน้าคู่มือ:

บรรทัดคำสั่งสร้างขึ้นในลักษณะเดียวกับที่ xargs สร้างบรรทัดคำสั่ง

นั่นเป็นเหตุผลที่ไม่อนุญาตให้ใช้อักขระระหว่าง{}และ+ยกเว้นเว้นวรรค ทำให้พบว่าตรวจพบว่าข้อโต้แย้งที่ควรจะต่อท้ายคำสั่งเช่นเดียวกับ+xargs

การแก้ไขปัญหา

โชคดีที่การดำเนินงานของ GNU mvสามารถยอมรับไดเรกทอรีเป้าหมายเป็นอาร์กิวเมนต์ด้วยหรือพารามิเตอร์อีกต่อไป-t --targetการใช้งานจะเป็น:

mv -t target file1 file2 ...

findคำสั่งของคุณกลายเป็น:

find . -type f -iname '*.cpp' -exec mv -t ./test/ {} \+

จากหน้าคู่มือ:

คำสั่ง -exec;

ดำเนินการคำสั่ง; true ถ้าสถานะ 0 ถูกส่งกลับ อาร์กิวเมนต์ที่จะค้นหาต่อไปนี้ทั้งหมดจะถูกนำไปเป็นอาร์กิวเมนต์ของคำสั่งจนกว่าอาร์กิวเมนต์ประกอบด้วย `; ' พบ สตริง `{} 'จะถูกแทนที่ด้วยชื่อไฟล์ปัจจุบันที่ถูกประมวลผลทุกที่ที่เกิดขึ้นในอาร์กิวเมนต์ของคำสั่งไม่ใช่เฉพาะในอาร์กิวเมนต์ที่อยู่เพียงอย่างเดียวเหมือนใน find บางเวอร์ชัน สิ่งปลูกสร้างทั้งสองนี้อาจจำเป็นต้องมีการ Escape (ด้วย \ ') หรือยกมาเพื่อป้องกันไม่ให้เปลือกขยายออก ดูส่วน EXAMPLES สำหรับตัวอย่างการใช้อ็อพชัน -exec คำสั่งที่ระบุจะรันหนึ่งครั้งสำหรับแต่ละไฟล์ที่ตรงกัน คำสั่งถูกดำเนินการในไดเร็กทอรีเริ่มต้น มีปัญหาด้านความปลอดภัยที่หลีกเลี่ยงไม่ได้เกี่ยวกับการใช้ -exec action; คุณควรใช้ตัวเลือก -execdir แทน

คำสั่ง -exec {} +

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


1
ฉันรู้ว่ามันทำงานอย่างไรฉันอ่านคู่มือนี้หลายครั้งแล้ว แต่ฉันได้รับข้อความแสดงข้อผิดพลาดในการใช้ {} + แม้ว่าจะใช้ได้กับ {} \; และฉันใช้ Cygwin ใน windows
Shahadat Hossain

1
@Shahadat: คุณเคยอ่านส่วน "จากหน้าคู่มือ" ก่อนหรือไม่? คุณใส่./test/ระหว่าง{}และ+แต่ไม่อนุญาตให้ใช้อักขระที่ไม่ใช่ช่องว่างระหว่างอักขระเหล่านี้
Lekensteyn

ru บอกว่าฉันไม่ควรใส่. / test/ อยู่ระหว่าง {} และ + แล้วคำสั่ง mv จะทำงานอย่างไร; mv ต้องการแหล่งที่มาซึ่งเป็น {} และต้องการปลายทางซึ่งก็คือ. / test/ และการยุติด้วย + คุณช่วยเขียนคำสั่งในสิ่งที่คุณคิดได้ไหม
Shahadat Hossain

@ Shahadat: ฉันเห็นสิ่งที่คุณพยายามจะบรรลุ Windows ทำงานช้าในการเรียกใช้โปรแกรมดังนั้นคุณต้องการรวมเป็นคำสั่งเดียว ฉันจะเพิ่มทางเลือกให้กับคำตอบ
Lekensteyn

1
+คำสั่งเป็นบิต AFAIU แปลกเพราะมันเกาะติดไฟล์ที่ "ที่สุด" (และไม่ได้อยู่ในสถานที่{}) ดังนั้นทำไมใช้{}ที่ทุกคน - นี่คือความสับสน ขอบคุณสำหรับ-tตัวเลือกที่ฉันไม่รู้จักดูเหมือนว่าตัวเลือกนั้นถูกสร้างขึ้นเพื่อแก้ปัญหานั้น-exec +!
e2-e4

6

ฉันพบปัญหาเดียวกันในMac OSXโดยใช้เชลล์ZSH : ในกรณีนี้ไม่มี-tตัวเลือกmvให้ฉันจึงต้องหาวิธีแก้ไขอื่น อย่างไรก็ตามคำสั่งต่อไปนี้สำเร็จ:

find .* * -maxdepth 0 -not -path '.git' -not -path '.backup' -exec mv '{}' .backup \;

เป็นความลับที่จะพูดวงเล็บ ไม่จำเป็นต้องให้เครื่องหมายวงเล็บอยู่ท้ายexecคำสั่ง

ฉันทดสอบภายใต้Ubuntu 14.04 (ด้วยBASHและZSHเชลล์) มันใช้งานได้เหมือนกัน

อย่างไรก็ตามเมื่อใช้+เครื่องหมายดูเหมือนว่าจะต้องอยู่ท้ายexecคำสั่ง


{}ความต้องการที่จะยกมาในfishและrcเปลือกหอย แต่ไม่ได้อยู่ในzsh, bashหรือหอยอื่น ๆ ของบอร์นหรือครอบครัว csh
Stephane Chazelas

@StephaneChazelas ใช่ทดสอบใหม่ภายใต้ Ubuntu ด้วยbashคำพูดไม่จำเป็นจริงๆ อยากรู้อยากเห็นฉันมีปัญหาหากไม่ได้อ้างถึงใน MacOS (โดยใช้zsh) แต่ฉันไม่มี Mac ให้ลองอีกครั้ง ...
arvymetal

3

มาตรฐานเทียบเท่าfind -iname ... -exec mv -t dest {} +สำหรับfindการใช้งานที่ไม่รองรับ-inameหรือmvการใช้งานที่ไม่รองรับ-tคือการใช้เชลล์เพื่อเรียงลำดับอาร์กิวเมนต์ใหม่:

find . -name '*.[cC][pP][pP]' -type f -exec sh -c '
  exec mv "$@" /dest/dir/' sh {} +

โดยใช้-name '*.[cC][pP][pP]'เรายังหลีกเลี่ยงการพึ่งพาสถานที่ปัจจุบันในการตัดสินใจสิ่งที่รุ่นพิมพ์ใหญ่ของหรือcp

โปรดทราบว่า+ตรงกันข้าม;ไม่ได้มีความพิเศษในเชลล์ใด ๆ ดังนั้นจึงไม่จำเป็นต้องยกมา (แม้ว่าการอ้างอิงจะไม่เป็นอันตราย แต่แน่นอนว่าเชลล์แบบrcนั้นไม่รองรับ\เป็นตัวดำเนินการอ้างอิง)

ต่อท้าย/ใน/dest/dir/เพื่อให้mvล้มเหลวด้วยข้อผิดพลาดแทนที่จะเปลี่ยนชื่อfoo.cppไป/dest/dirในกรณีที่มีเพียงหนึ่งcppไฟล์ถูกพบและ/dest/dirไม่ได้อยู่หรือไม่ได้ไดเรกทอรี (หรือ symlink ไป directory)


+1 ... การทำงานในเชลล์เป็นพื้นฐานในการเรียกใช้คำสั่งนั้นมีประโยชน์จริงสำหรับกรณีการใช้งานที่หลากหลาย ... ดี
Cbhihe

0
find . -name "*.mp3" -exec mv --target-directory=/home/d0k/Музика/ {} \+

โปรดเพิ่มคำอธิบายให้กับคำตอบของคุณเพื่อให้ผู้อื่นสามารถเรียนรู้ได้
Nico Haase

คุณต้องตอบคำถามซึ่งขอคำอธิบาย รหัสเท่านั้นไม่ใช่คำตอบ
Lajos Arpad

-1

ไม่ความแตกต่างระหว่าง+และ\;ควรกลับด้าน +ต่อท้ายไฟล์ต่อท้ายคำสั่ง exec จากนั้นรันคำสั่ง exec และ\;รันคำสั่งสำหรับแต่ละไฟล์

ปัญหาที่เกิดขึ้นมีที่find . -type f -iname '*.cpp' -exec mv {} ./test/ \+ควรจะfind . -type f -iname '*.cpp' -exec mv {} ./test/ + ไม่จำเป็นที่จะหลบหนีหรือยุติ+

xargs ฉันไม่ได้ใช้มานาน แต่ฉันคิดว่ามันใช้งานได้เหมือน +


ฉันได้ลองด้วย แต่ได้รับข้อความแสดงข้อผิดพลาดเดียวกัน ยิ่งไปกว่านั้นทุกที่ที่ฉันพบว่าใช้เพียง + แต่ใน cygwin ของฉันฉันต้องใช้ \ + หรือ "+" ในการทำงาน
Shahadat Hossain

โอ้นี่คือสภาพแวดล้อมไซกวิน ขอโทษทีฉันไม่รู้ฉันไม่ได้ใช้เปลือก cygwin ฉันแค่ใช้ a * nix
Mike Ramirez

1
@Shahadat Hossain พยายาม-name "*.cpp"ฉันแทบจะไม่ใช้ -iname เว้นแต่ฉันต้องการค้นหา regex ที่ยาก ๆ เช่น -iname '??? work * \. cpp'
Mike Ramirez

1
@ ไมค์: ฉันคิดว่าคุณเข้าใจผิดความแตกต่างระหว่างและ-iname เป็นเวอร์ชันที่ไม่คำนึงถึงตัวพิมพ์เล็กและใหญ่และไม่มีความแตกต่างในการจัดการนิพจน์ทั่วไป ฉันขอแนะนำให้ลองใช้คำสั่งก่อนโพสต์คำสั่งของคุณล้มเหลวในเชลล์ของฉันเช่นกัน -name-iname-name
Lekensteyn

1
@Lekensteyn เป็นที่ยอมรับแล้วว่าเป็นกรณีก่อนที่คุณจะแสดงความคิดเห็น ฉันคิดว่าฉันได้รับทราบ Shahadat ก่อนที่จะโพสต์ของคุณมันเป็นเรื่องธรรมดา ไม่ฉันไม่ได้เรียกใช้ด้วยตนเองฉันทำจากด้านบนของหัวของฉันและไม่ค่อยใช้รูปแบบการค้นหา regex กับ find มันเป็นเพียงสิ่งที่ 'อาจช่วยได้'
Mike Ramirez
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.