ใช้ sed เพื่อเปลี่ยนชื่อไฟล์จำนวนมาก


89

วัตถุประสงค์

เปลี่ยนชื่อไฟล์เหล่านี้:

  • F00001-0708-RG-biasliuyda
  • F00001-0708-CS-akgdlaul
  • F00001-0708-VF-hioulgigl

ไปยังชื่อไฟล์เหล่านี้:

  • F0001-0708-RG-biasliuyda
  • F0001-0708-CS-akgdlaul
  • F0001-0708-VF-hioulgigl

รหัสเชลล์

ทดสอบ:

ls F00001-0708-*|sed 's/\(.\).\(.*\)/mv & \1\2/'

ในการดำเนินการ:

ls F00001-0708-*|sed 's/\(.\).\(.*\)/mv & \1\2/' | sh

คำถามของฉัน

ฉันไม่เข้าใจรหัส sed ฉันเข้าใจคำสั่งการแทนที่

$ sed 's/something/mv'

หมายถึง. และฉันเข้าใจนิพจน์ทั่วไปบ้าง แต่ฉันไม่เข้าใจว่าเกิดอะไรขึ้นที่นี่:

\(.\).\(.*\)

หรือที่นี่:

& \1\2/

สำหรับฉันในอดีตดูเหมือนว่ามันหมายถึง: "อักขระเดี่ยวตามด้วยอักขระเดี่ยวตามด้วยลำดับความยาวของอักขระเดี่ยว" - แต่แน่นอนว่ามีอะไรมากกว่านั้น เท่าที่ส่วนหลัง:

& \1\2/

ฉันไม่รู้.


คำตอบ:


152

ก่อนอื่นฉันควรบอกว่าวิธีที่ง่ายที่สุดในการทำเช่นนี้คือใช้คำสั่ง prename หรือเปลี่ยนชื่อ

บน Ubuntu, OSX (แพ็คเกจ Homebrew rename, แพ็คเกจMacPorts p5-file-rename) หรือระบบอื่น ๆ ที่มีการเปลี่ยนชื่อ Perl (ชื่อล่วงหน้า):

rename s/0000/000/ F0000*

หรือบนระบบที่เปลี่ยนชื่อจาก util-linux-ng เช่น RHEL:

rename 0000 000 F0000*

นั่นเป็นเรื่องที่เข้าใจได้มากกว่าคำสั่ง sed ที่เทียบเท่า

แต่สำหรับการทำความเข้าใจคำสั่ง sed manpage sed จะเป็นประโยชน์ หากคุณเรียกใช้ man sed และค้นหา & (โดยใช้คำสั่ง / เพื่อค้นหา) คุณจะพบว่าเป็นอักขระพิเศษใน s / foo / bar / replacements

  s/regexp/replacement/
         Attempt  to match regexp against the pattern space.  If success‐
         ful,  replace  that  portion  matched  with  replacement.    The
         replacement may contain the special character & to refer to that
         portion of the pattern space  which  matched,  and  the  special
         escapes  \1  through  \9  to refer to the corresponding matching
         sub-expressions in the regexp.

ดังนั้นตรงกับตัวอักษรตัวแรกซึ่งสามารถอ้างอิงโดย\(.\) \1จากนั้น.ตรงกับตัวอักษรถัดไปซึ่งเป็นเสมอ 0 จากนั้นตรงกับส่วนที่เหลือของชื่อไฟล์ซึ่งสามารถนำมาอ้างอิงโดย\(.*\)\2

สตริงแทนที่รวมทั้งหมดเข้าด้วยกันโดยใช้&(ชื่อไฟล์ต้นฉบับ) และ\1\2เป็นทุกส่วนของชื่อไฟล์ยกเว้นอักขระตัวที่ 2 ซึ่งเป็น 0

นี่เป็นวิธีที่ค่อนข้างคลุมเครือในการทำ IMHO หากด้วยเหตุผลบางประการคำสั่งเปลี่ยนชื่อไม่พร้อมใช้งานและคุณต้องการใช้ sed เพื่อทำการเปลี่ยนชื่อ (หรือบางทีคุณกำลังทำบางสิ่งที่ซับซ้อนเกินไปสำหรับการเปลี่ยนชื่อ?) การมีความชัดเจนมากขึ้นใน regex ของคุณจะทำให้อ่านได้ง่ายขึ้น บางทีสิ่งที่ชอบ:

ls F00001-0708-*|sed 's/F0000\(.*\)/mv & F000\1/' | sh

ความสามารถในการดูว่ามีอะไรเปลี่ยนแปลงใน s / search / replacement / ทำให้อ่านได้ง่ายขึ้น นอกจากนี้มันจะไม่ดูดอักขระออกจากชื่อไฟล์ของคุณหากคุณเรียกใช้สองครั้งหรือบางอย่างโดยไม่ได้ตั้งใจ


1
บนเซิร์ฟเวอร์ RHEL ของฉันไวยากรณ์การเปลี่ยนชื่อจะเป็น "เปลี่ยนชื่อ 0000 000 F0000 *"
David LeBauer

1
เป็นไปได้มากว่านั่นrenameคือลิงก์"เปลี่ยนชื่อ" ie renameได้รับการ"เปลี่ยนชื่อ"จากprename.. เช่นใน Ubuntu: readlink -f $(which rename)เอาต์พุต/usr/bin/prename... ที่renameกล่าวถึงโดยDavidเป็นโปรแกรมที่แตกต่างกันโดยสิ้นเชิง
เตอร์โอ

1
จุดดีปีเตอร์ ฉันได้อัปเดตคำตอบสำหรับการเปลี่ยนชื่อยูทิลิตี้ทั้งสอง
Edward Anderson

3
ในการแก้ไขข้อบกพร่องนี้ให้ถอดท่อออกเป็น sh ที่ส่วนท้าย คำสั่งจะสะท้อนออกไปที่หน้าจอ
Ben Mathews

1
คุณแน่ใจหรือว่ามันเป็นคำแนะนำที่ดีเพื่อให้ข้อมูลแบบสุ่มท่อผ่านsh? สิ่งนี้อาจเป็นอันตรายเนื่องจากสามารถเรียกใช้รหัสที่กำหนดเองได้ (คุณถือว่าข้อมูลเป็นรหัส)
gniourf_gniourf

46

คุณมีคำอธิบาย sed ของคุณตอนนี้คุณสามารถใช้เพียงเชลล์โดยไม่ต้องใช้คำสั่งภายนอก

for file in F0000*
do
    echo mv "$file" "${file/#F0000/F000}"
    # ${file/#F0000/F000} means replace the pattern that starts at beginning of string
done

1
ดี แต่คุณไม่สามารถอ้างอิงด้วยวงเล็บได้
Leonidas Tsampros

28

ฉันเขียนโพสต์เล็ก ๆ พร้อมตัวอย่างเกี่ยวกับการเปลี่ยนชื่อแบทช์เมื่อsedสองสามปีก่อน:

http://www.guyrutenberg.com/2009/01/12/batch-renaming-using-sed/

ตัวอย่างเช่น:

for i in *; do
  mv "$i" "`echo $i | sed "s/regex/replace_text/"`";
done

หาก regex มีกลุ่ม (เช่น\(subregex\) แล้วคุณสามารถใช้พวกเขาในข้อความทดแทน\1\, \2ฯลฯ


โปรดทราบว่าคำตอบแบบลิงก์เท่านั้นไม่ได้รับการสนับสนุน (ลิงก์มักจะค้างเมื่อเวลาผ่านไป) โปรดพิจารณาแก้ไขคำตอบของคุณและเพิ่มเรื่องย่อที่นี่
kleopatra

ไม่ได้มีประสิทธิภาพขนาดนั้น แต่ทำงานให้เสร็จได้สองร้อยไฟล์ โหวตแล้ว
Varun Chandak

23

วิธีที่ง่ายที่สุดคือ:

for i in F00001*; do mv "$i" "${i/F00001/F0001}"; done

หรือแบบพกพา

for i in F00001*; do mv "$i" "F0001${i#F00001}"; done

สิ่งนี้แทนที่F00001คำนำหน้าในชื่อไฟล์ด้วยF0001. ให้เครดิตกับ mahesh ที่นี่: http://www.debian-administration.org/articles/150


3
คุณควรอ้างการแก้ไขตัวแปรอย่างถูกต้อง mv "$i" "${i/F00001/F0001}". แต่ +1
tripleee

7

sedคำสั่ง

s/\(.\).\(.*\)/mv & \1\2/

หมายถึงการแทนที่:

\(.\).\(.*\)

กับ:

mv & \1\2

เหมือนกับsedคำสั่งทั่วไป อย่างไรก็ตามวงเล็บ&และ\nเครื่องหมายเปลี่ยนไปเล็กน้อย

สตริงค้นหาจะจับคู่ (และจำเป็นรูปแบบ 1) อักขระเดี่ยวที่จุดเริ่มต้นตามด้วยอักขระเดี่ยวตามด้วยสตริงที่เหลือ (จำเป็นรูปแบบ 2)

ในสตริงการแทนที่คุณสามารถอ้างถึงรูปแบบที่ตรงกันเหล่านี้เพื่อใช้เป็นส่วนหนึ่งของการแทนที่ &นอกจากนี้คุณยังสามารถอ้างถึงส่วนที่ตรงกับทั้งเป็น

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

mv F00001-0708-RG-biasliuyda F0001-0708-RG-biasliuyda
mv abcdef acdef

และอื่น ๆ


1
นี่เป็นคำอธิบายที่ดี แต่การชี้ให้เห็นว่าคุณใช้คำสั่ง sed กับคำสั่งอื่น ๆ เพื่อเปลี่ยนชื่อไฟล์ได้อย่างไร ตัวอย่างเช่น:ls | sed "s/\(.\).\(.*\)/mv & \1\2/" | bash
jcarballo

@jcarballo: มันเป็นอันตรายที่จะแยกlsท่อผ่านsedและท่อแล้วผ่านเปลือก! มันขึ้นอยู่กับการใช้รหัสโดยอำเภอใจด้วยชื่อไฟล์ที่ปลอมแปลง ปัญหาคือข้อมูลควรได้รับการปฏิบัติเป็นข้อมูลและที่นี่โดยทั่วไปจะทำให้เป็นอนุกรมเป็นรหัสโดยไม่มีข้อควรระวังใด ๆ ฉันหวังว่า paxdiablo จะลบคำตอบนี้ได้เพราะมันไม่ได้แสดงถึงแนวทางปฏิบัติที่ดี (ผมสะดุดกับคำถามนี้เพราะเป็นมือใหม่สุ่มประปาหลังคำสั่งที่ไม่ได้ทำงานและหลังจากที่ได้เห็นคำถามนี้และคำตอบที่คิดว่ามันจะทำงานได้ดีกว่าผมกำลังตกใจ!)| sh :)
gniourf_gniourf

3

เครื่องหมายแบ็กสแลช - พาเรนหมายถึง "ในขณะที่จับคู่รูปแบบให้ยึดสิ่งที่ตรงกับที่นี่" จากนั้นในด้านข้อความแทนที่คุณจะได้รับชิ้นส่วนที่จำได้กลับมาด้วย "\ 1" (บล็อกในวงเล็บแรก) "\ 2" (บล็อกที่สอง) และอื่น ๆ


1

หากสิ่งที่คุณทำจริงๆคือการลบอักขระที่สองออกไม่ว่าจะเป็นอะไรก็ตามคุณสามารถทำได้:

s/.//2

แต่คำสั่งของคุณกำลังสร้างไฟล์ mvคำสั่งและไพพ์ไปยังเชลล์เพื่อดำเนินการ

ไม่สามารถอ่านได้มากกว่าเวอร์ชันของคุณ:

find -type f | sed -n 'h;s/.//4;x;s/^/mv /;G;s/\n/ /g;p' | sh

อักขระที่สี่จะถูกลบออกเนื่องจากใส่findชื่อไฟล์ไว้ล่วงหน้าด้วย "./"


ฉันขอให้คุณลบคำตอบนี้ แม้ว่ามันจะดีในกรณีที่เฉพาะเจาะจงของ OP แต่ก็มีคนจำนวนมากที่เห็นคำตอบเช่นนี้และไม่เข้าใจและสุ่มไปป์| shหลังจากคำสั่งที่ไม่ได้ผลโดยหวังว่ามันจะได้ผล ดีกว่า. สยอง! (และนอกจากนี้นั่นไม่ใช่แนวทางปฏิบัติที่ดี) ฉันหวังว่าคุณจะเข้าใจ!
gniourf_gniourf

1

การใช้การเปลี่ยนชื่อ perl ( ต้องมีในกล่องเครื่องมือ):

rename -n 's/0000/000/' F0000*

ถอด-nสวิตช์เมื่อเอาต์พุตดูดีเพื่อเปลี่ยนชื่อเป็นของจริง

คำเตือน มีเครื่องมืออื่นที่มีชื่อเดียวกันซึ่งอาจทำได้หรือไม่สามารถทำได้ดังนั้นโปรดใช้ความระมัดระวัง

คำสั่งเปลี่ยนชื่อที่เป็นส่วนหนึ่งของutil-linuxแพ็กเกจจะไม่

หากคุณรันคำสั่งต่อไปนี้ ( GNU)

$ rename

แล้วคุณจะเห็น perlexprว่านี่น่าจะเป็นเครื่องมือที่เหมาะสม

หากไม่เป็นเช่นนั้นเพื่อให้เป็นค่าเริ่มต้น (โดยปกติจะเป็นกรณีอยู่แล้ว) Debianและอนุพันธ์เช่นUbuntu:

$ sudo apt install rename
$ sudo update-alternatives --set rename /usr/bin/file-rename

สำหรับ archlinux:

pacman -S perl-rename

สำหรับกลุ่มครอบครัว RedHat:

yum install prename

แพ็คเกจ 'prename' อยู่ในที่เก็บEPEL


สำหรับ Gentoo:

emerge dev-perl/rename

สำหรับ * BSD:

pkg install gprename

หรือ p5-File-Rename


สำหรับผู้ใช้ Mac:

brew install rename

หากคุณไม่มีคำสั่งนี้กับ distro อื่นให้ค้นหาตัวจัดการแพ็คเกจของคุณเพื่อติดตั้งหรือทำด้วยตนเอง :

cpan -i File::Rename

เวอร์ชันสแตนด์อโลนเก่าสามารถพบได้ที่นี่


เปลี่ยนชื่อผู้ชาย


เครื่องมือนี้เขียนขึ้นโดย Larry Wall พ่อของ Perl



0
 ls F00001-0708-*|sed 's|^F0000\(.*\)|mv & F000\1|' | bash

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

0

นี่คือสิ่งที่ฉันจะทำ:

for file in *.[Jj][Pp][Gg] ;do 
    echo mv -vi \"$file\" `jhead $file|
                           grep Date|
                           cut -b 16-|
                           sed -e 's/:/-/g' -e 's/ /_/g' -e 's/$/.jpg/g'` ;
done

จากนั้นถ้ามันดูโอเคให้เพิ่ม| shตอนท้าย ดังนั้น:

for file in *.[Jj][Pp][Gg] ;do 
    echo mv -vi \"$file\" `jhead $file|
                           grep Date|
                           cut -b 16-|
                           sed -e 's/:/-/g' -e 's/ /_/g' -e 's/$/.jpg/g'` ;
done | sh

0

ตัวอย่างบางส่วนที่เหมาะกับฉัน:

$ tree -L 1 -F .
.
├── A.Show.2020.1400MB.txt
└── Some Show S01E01 the Loreming.txt

0 directories, 2 files

## remove "1400MB" (I: ignore case) ...

$ for f in *; do mv 2>/dev/null -v "$f" "`echo $f | sed -r 's/.[0-9]{1,}mb//I'`"; done;
renamed 'A.Show.2020.1400MB.txt' -> 'A.Show.2020.txt'

## change "S01E01 the" to "S01E01 The"
## \U& : change (here: regex-selected) text to uppercase;
##       note also: no need here for `\1` in that regex expression

$ for f in *; do mv 2>/dev/null "$f" "`echo $f | sed -r "s/([0-9] [a-z])/\U&/"`"; done

$ tree -L 1 -F .
.
├── A.Show.2020.txt
└── Some Show S01E01 The Loreming.txt

0 directories, 2 files
$ 

-1
for i in *; do mv $i $(echo $i|sed 's/AAA/BBB/'); done

4
ยินดีต้อนรับสู่ SO โปรดพิจารณาเพิ่มคำอธิบายรหัสของคุณ มันจะช่วยให้ผู้ใช้คนอื่นเข้าใจมัน
Digvijay S

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