ฉันจะเปลี่ยนนามสกุลของหลายไฟล์ซ้ำจากบรรทัดคำสั่งได้อย่างไร


73

ฉันมีไฟล์จำนวนมากที่มี.abcนามสกุลและต้องการเปลี่ยนเป็น.edefg
วิธีการทำจากบรรทัดคำสั่ง?

ฉันมีโฟลเดอร์รูทที่มีโฟลเดอร์ย่อยมากมายดังนั้นโซลูชันควรทำงานซ้ำ


คำตอบ:


85

วิธีการพกพา (ซึ่งจะทำงานบนระบบที่สอดคล้องกับ POSIX ใด ๆ ):

find /the/path -depth -name "*.abc" -exec sh -c 'mv "$1" "${1%.abc}.edefg"' _ {} \;

ใน bash4 คุณสามารถใช้ globstar เพื่อรับ globs ซ้ำ (**):

shopt -s globstar
for file in /the/path/**/*.abc; do
  mv "$file" "${file%.abc}.edefg"
done

renameคำสั่ง(perl) ใน Ubuntu สามารถเปลี่ยนชื่อไฟล์โดยใช้ไวยากรณ์การแสดงออกปกติของ Perl ซึ่งคุณสามารถรวมกับ globstar หรือfind:

# Using globstar
shopt -s globstar
files=(/the/path/**/*.abc)  

# Best to process the files in chunks to avoid exceeding the maximum argument 
# length. 100 at a time is probably good enough. 
# See http://mywiki.wooledge.org/BashFAQ/095
for ((i = 0; i < ${#files[@]}; i += 100)); do
  rename 's/\.abc$/.edefg/' "${files[@]:i:100}"
done

# Using find:
find /the/path -depth -name "*.abc" -exec rename 's/\.abc$/.edefg/' {} +

ดูhttp://mywiki.wooledge.org/BashFAQ/030 ด้วย


อันแรกก็แค่ต่อเติมส่วนขยายใหม่เข้ากับตัวเก่ามันก็ไม่ได้แทนที่ ...
user2757729

3
@ user2757729 มันจะแทนที่ "${1%.abc}"ขยายเป็นค่า$1ยกเว้นโดยไม่มี.abcที่สิ้นสุด ดูfaq 100สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการปรับเปลี่ยนสตริงของเชลล์
geirha

ฉันรันสิ่งนี้ในโครงสร้างไฟล์ไฟล์ ~ 70k ... อย่างน้อยบนเชลล์ของฉันมันแน่นอนที่สุดไม่ได้แทนที่มัน
user2757729

1
@ user2757729 คุณอาจทิ้ง "abc" ใน${1%.abc}...
kvnn

1
ในกรณีที่คนอื่นจะสงสัยว่าสิ่งที่ขีดจะทำในคำสั่งแรกก็จำเป็นเพราะมีsh -c อาร์กิวเมนต์แรกได้รับมอบหมายให้ $ 0 และข้อโต้แย้งใด ๆ ที่เหลือได้รับมอบหมายให้พารามิเตอร์ตำแหน่ง ดังนั้น_คืออาร์กิวเมนต์หุ่นและ '{}' sh -cเป็นอาร์กิวเมนต์ที่ตำแหน่งแรกที่
hellobenallan

48

สิ่งนี้จะทำงานที่จำเป็นหากไฟล์ทั้งหมดอยู่ในโฟลเดอร์เดียวกัน

rename 's/.abc$/.edefg/' *.abc

หากต้องการเปลี่ยนชื่อไฟล์ให้ใช้สิ่งนี้ซ้ำ:

find /path/to/root/folder -type f -name '*.abc' -print0 | xargs -0 rename 's/.abc$/.edefg/'

8
หรือrename 's/.abc$/.edefg/' /path/to/root/folder/**/*.abcใน Bash รุ่นทันสมัย
Adam Byrtek

ขอขอบคุณอดัมที่ให้คำแนะนำวิธีการใช้ * .abc ในโฟลเดอร์แบบเรียกซ้ำ!
RafałCieślak

เคล็ดลับยอดเยี่ยมขอบคุณ! ฉันจะหาเอกสารเพิ่มเติมเกี่ยวกับไวยากรณ์เล็กน้อยของ regex ได้ที่ไหน ชอบอะไรคือsจุดเริ่มต้นและตัวเลือกอื่น ๆ ที่ฉันสามารถใช้ได้ในนั้น ขอบคุณมาก!
Anto

@ AdamByrtek ฉันเพิ่งล้มเหลวด้วยคำแนะนำของคุณใน Utopic ('ไม่มีไฟล์ดังกล่าว' หรือบางไฟล์)
Jonathan Y.

14

ปัญหาอย่างหนึ่งของการเปลี่ยนชื่อแบบเรียกซ้ำคือวิธีใดก็ตามที่คุณใช้เพื่อค้นหาไฟล์มันจะส่งผ่านพา ธ ทั้งหมดไปที่renameไม่ใช่แค่ชื่อไฟล์ ทำให้ยากที่จะทำการเปลี่ยนชื่อที่ซับซ้อนในโฟลเดอร์ซ้อนกัน

ผมใช้findของ-execdirการดำเนินการเพื่อแก้ปัญหานี้ หากคุณใช้-execdirแทน-execคำสั่งที่ระบุจะถูกเรียกใช้จากไดเรกทอรีย่อยที่มีไฟล์ที่ตรงกัน ดังนั้นแทนที่จะผ่านเส้นทางทั้งไปก็เพียงผ่านrename ./filenameทำให้การเขียน regex ง่ายขึ้น

find /the/path -type f \
               -name '*.abc' \
               -execdir rename 's/\.\/(.+)\.abc$/version1_$1.abc/' '{}' \;

ในรายละเอียด:

  • -type f หมายถึงค้นหาเฉพาะไฟล์ไม่ใช่ไดเรกทอรี
  • -name '*.abc' หมายถึงการจับคู่ชื่อไฟล์ที่ลงท้ายด้วย. abc เท่านั้น
  • '{}'เป็นตัวยึดตำแหน่งที่ทำเครื่องหมายสถานที่ที่-execdirจะแทรกเส้นทางที่พบ ต้องการอัญประกาศเดี่ยวเพื่ออนุญาตให้จัดการกับชื่อไฟล์ด้วยช่องว่างและอักขระเชลล์
  • แบ็กสแลชหลัง-typeและ-nameเป็นอักขระต่อเนื่องของ bash ฉันใช้มันเพื่อทำให้ตัวอย่างนี้อ่านง่ายขึ้น แต่พวกเขาไม่จำเป็นถ้าคุณทำให้คำสั่งทั้งหมดในบรรทัดเดียว
  • อย่างไรก็ตามต้องใช้แบ็กสแลชที่ท้าย-execdirบรรทัด -execdirมันจะมีที่จะหลบหนีอัฒภาคซึ่งยุติการทำงานคำสั่งโดย สนุก!

คำอธิบายของ regex:

  • s/ เริ่มต้นของ regex
  • \.\/จับคู่กับ. / that -execdir ผ่านเข้าไปใช้ \ เพื่อหลีกเลี่ยง และ / metacharacters (หมายเหตุ: ส่วนนี้จะแตกต่างกันไปขึ้นอยู่กับรุ่นของfindคุณดูความคิดเห็นจากผู้ใช้ @apollo)
  • (.+)ตรงกับชื่อไฟล์ วงเล็บจับการแข่งขันเพื่อใช้ในภายหลัง
  • \.abc หนีจุดจับคู่ abc
  • $ สมอการแข่งขันที่ส่วนท้ายของสตริง

  • / ทำเครื่องหมายจุดสิ้นสุดของส่วน "จับคู่" ของ regex และจุดเริ่มต้นของส่วน "แทนที่"

  • version1_ เพิ่มข้อความนี้ให้กับทุกชื่อไฟล์

  • $1อ้างอิงชื่อไฟล์ที่มีอยู่เพราะเราจับมันด้วยเครื่องหมายวงเล็บ หากคุณใช้วงเล็บหลายชุดในส่วน "จับคู่" คุณสามารถอ้างอิงได้ที่นี่โดยใช้ $ 2, $ 3, ฯลฯ
  • .abcชื่อไฟล์ใหม่จะลงท้ายด้วย. abc ไม่จำเป็นต้องหลบหลีกตัวชี้จุดที่นี่ในส่วน "แทนที่"
  • / ในตอนท้ายของ regex

ก่อน

tree --charset=ascii

|-- a_file.abc
|-- Another.abc
|-- Not_this.def
`-- dir1
    `-- nested_file.abc

หลังจาก

tree --charset=ascii

|-- version1_a_file.abc
|-- version1_Another.abc
|-- Not_this.def
`-- dir1
    `-- version1_nested_file.abc

คำแนะนำ: renameตัวเลือก -n มีประโยชน์ มันทำงานแบบแห้งและแสดงให้คุณเห็นว่าชื่อจะเปลี่ยนไป แต่ไม่ได้ทำการเปลี่ยนแปลงใด ๆ


ขอบคุณสำหรับคำอธิบายรายละเอียด แต่แปลกเมื่อฉันลองนี้สิ่งที่--execdirไม่ผ่านในการนำ./เพื่อให้\.\/ส่วนใน regex สามารถละเว้น อาจเป็นเพราะฉันใช้ OSX ไม่ใช่ Ubuntu
apollo

5

วิธีพกพาอื่น:

find /the/path -depth -type f -name "*.abc" -exec sh -c 'mv -- "$1" "$(dirname "$1")/$(basename "$1" .abc).edefg"' _ '{}' \;

4
ยินดีต้อนรับสู่ถาม Ubuntu! ในขณะที่นี่เป็นคำตอบที่มีค่าฉันขอแนะนำให้ขยาย (โดยการแก้ไข) เพื่ออธิบายวิธีและคำสั่งนั้นทำงานอย่างไร
Eliah Kagan

3
# Rename all *.txt to *.text
for f in *.txt; do 
mv -- "$f" "${f%.txt}.text"
done

lsนอกจากนี้ยังดูรายการเกี่ยวกับเหตุผลที่คุณไม่ควรแยก

แก้ไข: ถ้าคุณต้องใช้ชื่อไฟล์ไวยากรณ์ของคุณจะเป็น:

for f in *.txt; do
mv -- "$f" "$(basename "$f" .txt).text"
done

https://unix.stackexchange.com/questions/19654/changing-extension-to-multiple-files


0

นี่คือสิ่งที่ฉันทำและทำงานในแบบที่ฉันต้องการ ฉันใช้mvคำสั่ง ฉันมีหลาย.3gpไฟล์และฉันต้องการเปลี่ยนชื่อพวกเขาทั้งหมดเป็น.mp4

นี่เป็นตัวย่อสำหรับมัน:

for i in *.3gp; do mv -- "$i" "ren-$i.mp4"; done

ซึ่งเพียงสแกนผ่านไดเรกทอรีปัจจุบันรับไฟล์. 3gp ทั้งหมดจากนั้นเปลี่ยนชื่อ (โดยใช้ mv) เป็น ren-name_of_file.mp4


นั่นจะเปลี่ยนชื่อfile.3gpเป็นfile.3gp.mp4ซึ่งอาจไม่ใช่สิ่งที่คนส่วนใหญ่ต้องการ
muru

@muru แต่หลักการยังคงมีอยู่ เพียงเลือกส่วนขยายใดก็ได้จากนั้นระบุนามสกุลปลายทางเพื่อเปลี่ยนชื่อ .3gpหรือ.mp4นี่เป็นเพียงเพื่อวัตถุประสงค์ภาพประกอบ
KhPPHI

ไวยากรณ์เป็นที่นิยมมากกว่าหนึ่งซับและง่ายต่อการเข้าใจ อย่างไรก็ตามฉันจะใช้basename "$i" .mp4เพื่อลบส่วนขยายก่อนหน้าแทน "ren- $ i.mp4"
TaoPR

จริง จุดดี.
KhopPhi

0

ฉันพบวิธีง่ายๆในการบรรลุเป้าหมายนี้ หากต้องการเปลี่ยนนามสกุลของไฟล์จำนวนมากจาก jpg เป็น pdf ให้ใช้:

for file in /path/to; do mv $file $(basename -s jpg $file)pdf ; done


0

เปลี่ยนชื่อไฟล์และไดเรกทอรีด้วย find -execdir | rename

หากคุณจะเปลี่ยนชื่อทั้งไฟล์และไดเรกทอรีไม่ใช่เพียงแค่คำต่อท้ายนี่เป็นรูปแบบที่ดี:

PATH="$(echo "$PATH" | sed -E 's/(^|:)[^\/][^:]*//g')" \
  find . -depth -execdir rename 's/findme/replaceme/' '{}' \;

น่ากลัว-execdirตัวเลือกที่ไม่ได้cdลงในไดเรกทอรีก่อนดำเนินการคำสั่งซึ่งแตกต่างจากrename-exec

-depth ตรวจสอบให้แน่ใจว่าการเปลี่ยนชื่อเกิดขึ้นครั้งแรกกับเด็ก ๆ และจากนั้นในผู้ปกครองเพื่อป้องกันปัญหาที่อาจเกิดขึ้นกับไดเรกทอรีหลักที่ขาดหายไป

-execdir จำเป็นต้องมีเนื่องจากการเปลี่ยนชื่อเล่นได้ไม่ดีกับเส้นทางเข้าที่ไม่ใช่ชื่อไฟล์เช่นต่อไปนี้ล้มเหลว:

rename 's/findme/replaceme/g' acc/acc

การPATHแฮ็คเป็นสิ่งจำเป็นเนื่องจาก-execdirมีข้อเสียเปรียบที่น่ารำคาญอย่างหนึ่ง: findมีความเห็นอย่างมากและปฏิเสธที่จะทำทุกอย่าง-execdirหากคุณมีเส้นทางญาติในPATHตัวแปรสภาพแวดล้อมของคุณเช่น./node_modules/.binล้มเหลวด้วย:

find: พา ธ สัมพัทธ์ './node_modules/.bin' รวมอยู่ในตัวแปรสภาพแวดล้อม PATH ซึ่งไม่ปลอดภัยเมื่อใช้ร่วมกับการดำเนินการ -execdir ของการค้นหา โปรดลบรายการนั้นออกจาก $ PATH

ดูเพิ่มเติม: เหตุใดการใช้การกระทำ '-execdir' จึงไม่ปลอดภัยสำหรับไดเรกทอรีที่อยู่ใน PATH

-execdirเป็น GNU พบส่วนขยายไปยัง POSIX renameเป็นภาษา Perl และมาจากrenameแพ็คเกจ ทดสอบใน Ubuntu 18.10

เปลี่ยนชื่อวิธีแก้ปัญหา lookahead

หากเส้นทางอินพุตของคุณไม่ได้มาจากfindหรือหากคุณมีความรำคาญในเส้นทางสัมพัทธ์เพียงพอเราสามารถใช้ Perl lookahead เพื่อเปลี่ยนชื่อไดเรกทอรีอย่างปลอดภัยใน:

git ls-files | sort -r | xargs rename 's/findme(?!.*\/)\/?$/replaceme/g' '{}'

ฉันไม่พบอะนาล็อกที่สะดวกสำหรับ-execdirด้วยxargs: https://superuser.com/questions/893890/xargs-change-working-directory-to-file-path-before-executing/915686

sort -rเป็นสิ่งจำเป็นเพื่อให้มั่นใจว่าไฟล์มาหลังจากไดเรกทอรีของตนตั้งแต่เส้นทางอีกต่อมาหลังจากที่คนสั้นด้วยคำนำหน้าเดียวกัน

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