วิธีการเปลี่ยนชื่อหลาย ๆ ไฟล์โดยใช้การค้นหา


37

ฉันต้องการเปลี่ยนชื่อหลายไฟล์ (file1 ... filen เป็น file1_renamed ... filen_renamed) โดยใช้คำfindสั่ง command:

find . -type f -name 'file*' -exec mv filename='{}' $(basename $filename)_renamed ';'

แต่รับข้อผิดพลาดนี้:

mv: cannot stat filename=./file1’: No such file or directory

สิ่งนี้ไม่ทำงานเพราะชื่อไฟล์จะไม่ถูกตีความว่าเป็นตัวแปรเชลล์

คำตอบ:


46

ต่อไปนี้เป็นการแก้ไขแนวทางของคุณโดยตรง:

find . -type f -name 'file*' -exec sh -c 'x="{}"; mv "$x" "${x}_renamed"' \;

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

find . -type f -name 'file*' -print0 | xargs --null -I{} mv {} {}_renamed

นอกจากนี้ยังมีประโยชน์ในการทำงานกับไฟล์ที่มีชื่อแปลก ๆ หากfindรองรับมันสามารถลดได้ถึง

find . -type f -name 'file*' -exec mv {} {}_renamed \;

xargsรุ่นจะเป็นประโยชน์เมื่อไม่ได้ใช้งาน{}ในขณะที่

find .... -print0 | xargs --null rm

ที่นี่rmจะถูกเรียกหนึ่งครั้ง (หรือมีหลายไฟล์หลายครั้ง) แต่ไม่ใช่สำหรับทุกไฟล์

ฉันออกbasenameในคำถามคุณเพราะมันอาจจะเป็นความผิดที่คุณจะย้ายfoo/bar/file8ไปไม่ได้file8_renamedfoo/bar/file8_renamed

การแก้ไข (ตามที่แนะนำในความคิดเห็น):

  • เพิ่มสั้นลงfindโดยไม่ต้องxargs
  • เพิ่มสติกเกอร์ความปลอดภัย

ในกรณีที่xไม่มีประโยชน์: find . -type f -name 'file*' -exec mv {} "{}_renamed" \; xargsรุ่นมีประสิทธิภาพเช่นเดียวกับตัวอย่างแรก /
Costas

xจะมีเพียง แต่จะโดยตรงแก้ไขวิธีการถามของ
Thomas Erker

2
(1) มันอันตรายมากที่จะใช้{}โดยตรงในsh -c "…"คำสั่งshell ( ) - คุณควรส่งมันเป็นอาร์กิวเมนต์ (2) ไม่findรองรับการ{}_renamedสร้างทุกรุ่น (3) ฉันไม่เข้าใจคำสั่งของคุณที่xargsมีประโยชน์ในการลบไฟล์ (ตรงกันข้ามกับการเปลี่ยนชื่อไฟล์)
G-Man กล่าวว่า 'Reinstate Monica'

@ G-Man: ความแตกต่างกับการxargsไม่ได้mvเทียบกับrmแต่ใช้{}กับโดยไม่ต้อง อดีตคือคล้ายกับในขณะที่หลังเป็นmv file1 file1_renamed; mv file2 file2_renamed rm file1 file2
Thomas Erker

1
และถ้าคุณต้องการเปลี่ยนไฟล์ _renamed กลับไปเป็นชื่อเดิมคุณจะทำอย่างไร
mcmillab

17

หลังจากลองคำตอบแรกและเล่นกับมันเล็กน้อยฉันพบว่าสามารถทำได้สั้นลงเล็กน้อยและซับซ้อนน้อยลงโดยใช้-execdir:

find . -type f -name 'file*' -execdir mv {} {}_renamed ';'

ดูเหมือนว่าควรทำสิ่งที่คุณต้องการ


2
ด้วยfindการใช้งานที่สนับสนุน-execdirและ{}ไม่ได้เป็นสิ่งที่ปลอดภัยที่สุด คุณอาจต้องการที่จะเพิ่ม-iไปmvแม้ว่า (และ-Tถ้าคุณmvสนับสนุน)
Stéphane Chazelas

1
@ StéphaneChazelasดียิ่งขึ้นแทนที่จะพึ่งmvพรอมต์หรือนอกเหนือจากนั้นคุณสามารถ (ไม่ต้องสงสัยเลยว่าการใช้งานของคุณfindรองรับหรือไม่) ยังใช้-okdirซึ่งจะส่งออกคำสั่งที่จะดำเนินการก่อนที่จะดำเนินการ
Matijs

เป็นมูลค่าการกล่าวขวัญว่า-depthเป็นความคิดที่ดีหากคุณจะสัมผัสชื่อไดเรกทอรีด้วย
Ciro Santilli 新疆改造中心法轮功六四事件

โอ๊ะเพียงพบว่า-execdirจะมีหนึ่งข้อเสียที่น่ารำคาญมากfindปฏิเสธที่จะทำอะไรถ้าPATHมีทางญาติใด ๆ ... askubuntu.com/questions/621132/... find: The relative path XXX is included in the PATH environment
Ciro Santilli新疆改造中心法轮功六四事件

6

อีกวิธีหนึ่งคือการใช้การwhile readวนซ้ำผ่านfindเอาต์พุต นี้จะช่วยให้การเข้าถึงแต่ละชื่อไฟล์เป็นตัวแปรที่สามารถจัดการได้โดยไม่ต้องกังวลเกี่ยวกับค่าใช้จ่ายเพิ่มเติม / ปัญหาด้านความปลอดภัยที่อาจเกิดขึ้นจากการวางไข่แยกsh -cขั้นตอนการใช้findของ-execตัวเลือก

find . -type f -name 'file*' |
    while IFS= read file_name; do
        mv "$file_name" "${file_name##*\/}_renamed"
    done

และหากเชลล์ที่ใช้สนับสนุน-dตัวเลือกเพื่อระบุreadตัวคั่นคุณสามารถรองรับไฟล์ที่มีชื่อแปลก (เช่นกับ newline) โดยใช้สิ่งต่อไปนี้:

find . -type f -name 'file*' -print0 |
    while IFS= read -d '' file_name; do
        mv "$file_name" "${file_name##*\/}_renamed"
    done

3

ฉันต้องการขยายคำตอบแรกและโปรดทราบว่าวิธีนี้จะไม่สามารถต่อท้ายชื่อไฟล์ได้เนื่องจาก./คำนำหน้าเส้นทางมีอยู่ในอาร์กิวเมนต์ชื่อไฟล์

การแก้ไขคำตอบของ Thomas Erker ฉันพบว่าวิธีนี้เป็นวิธีทั่วไป

find . -name PATTERN -printf "%f\0" | xargs --null -I{} mv {} "prefix {} suffix"

ตัวเลือก xargs:

--nullบ่งชี้ว่าแต่ละอาร์กิวเมนต์ที่ส่งผ่านstdinลงท้ายด้วยอักขระ null ( \0) วิธีนี้ชื่อไฟล์สามารถมีช่องว่างมิฉะนั้นแต่ละคำจะถูกสร้างเป็นพารามิเตอร์ที่แตกต่างกันสำหรับmvคำสั่ง

-I replace-strทุก ocurrence ของจะถูกแทนที่ด้วยอ่านข้อโต้แย้งจากreplace-str stdinดังนั้นคุณสามารถเปลี่ยนเป็นสตริงอื่นได้หากต้องการ


ทำไมpringf "%f\0"แทนที่จะเป็นprint0?
slm

1
@sim, เพราะ-print0จะสร้าง./คำนำหน้าหากPATTERNมีเชลล์เมตาอักขระที่ได้รับในทางเมื่อเปลี่ยนชื่อเป็นสิ่งที่นำหน้าชื่อเดิม (เช่นการเปลี่ยนชื่อ0 - foo.txtไป00 - foo.txt, 1 - bar.txtการ01 - bar.txtฯลฯ )
das-G

2

ผมสามารถที่จะทำสิ่งที่คล้ายกับfor, และfindmv

for i in $(find . -name 'config.yml'); do mv $i $i.bak; done

พบconfig.ymlไฟล์ทั้งหมดและเปลี่ยนชื่อเป็นconfig.yml.bak


สิ่งนี้มีความได้เปรียบในการใช้การขยายตัวแปรเช่น $ {i: r} คำตอบที่ดี
ซาลามี่

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