แบทช์เปลี่ยนชื่อไฟล์ด้วย Bash


101

Bash จะเปลี่ยนชื่อชุดแพ็กเกจเพื่อลบหมายเลขเวอร์ชันได้อย่างไร ฉันเคยเล่นกับทั้งสองexprและ%%ไม่มีประโยชน์

ตัวอย่าง:

Xft2-2.1.13.pkg กลายเป็น Xft2.pkg

jasper-1.900.1.pkg กลายเป็น jasper.pkg

xorg-libXrandr-1.2.3.pkg กลายเป็น xorg-libXrandr.pkg


1
ฉันตั้งใจจะใช้สิ่งนี้เป็นประจำเป็นสคริปต์ที่ใช้ครั้งเดียวได้มาก ระบบใด ๆ ที่ฉันจะใช้จะมีการทุบตีดังนั้นฉันไม่กลัวการทุบตีที่มีประโยชน์มาก
Jeremy L

ดูเพิ่มเติมได้ที่stackoverflow.com/questions/28725333/…
tripleee

คำตอบ:


191

คุณสามารถใช้คุณสมบัติการขยายพารามิเตอร์ของ bash

for i in ./*.pkg ; do mv "$i" "${i/-[0-9.]*.pkg/.pkg}" ; done

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


1
ฉันชอบมันเหมือนกัน แต่มันใช้คุณสมบัติเฉพาะของ bash ... โดยปกติฉันไม่ได้ใช้มันเพื่อสนับสนุน sh "standard"
Diego Sevilla

2
สำหรับสิ่งที่โต้ตอบหนึ่งบรรทัดฉันมักจะใช้สิ่งนี้แทน sed-fu ถ้ามันเป็นสคริปต์ใช่หลีกเลี่ยงการทุบตี
richq

ปัญหาหนึ่งที่ฉันพบกับรหัส: จะเกิดอะไรขึ้นหากไฟล์ถูกเปลี่ยนชื่อไปแล้วและฉันเรียกใช้อีกครั้ง ฉันได้รับคำเตือนเกี่ยวกับการพยายามย้ายไปยังไดเรกทอรีย่อยของตัวเอง
Jeremy L

1
เพื่อหลีกเลี่ยงข้อผิดพลาดในการรันซ้ำคุณสามารถใช้รูปแบบเดียวกันในส่วน " .pkg" เช่น "สำหรับ i in * - [0-9.] .pkg; do mv $ i $ {i / - [0-9 .] *. pkg / .pkg}; เสร็จแล้ว ". แต่ข้อผิดพลาดนั้นไม่มีอันตรายเพียงพอ (ย้ายไปยังไฟล์เดียวกัน)
richq

3
ไม่ใช่วิธีแก้ปัญหา bash แต่ถ้าคุณติดตั้ง perl มันจะมีคำสั่งเปลี่ยนชื่อที่สะดวกสำหรับการเปลี่ยนชื่อแบทช์เพียงแค่ทำrename "s/-[0-9.]*//" *.pkg
Rufus

33

หากไฟล์ทั้งหมดอยู่ในไดเร็กทอรีเดียวกันลำดับ

ls | 
sed -n 's/\(.*\)\(-[0-9.]*\.pkg\)/mv "\1\2" "\1.pkg"/p' | 
sh

จะทำงานของคุณ sedคำสั่งจะสร้างลำดับของmvคำสั่งที่คุณสามารถแล้วท่อเข้าไปในเปลือก วิธีแรกที่ดีที่สุดคือรันไปป์ไลน์โดยไม่ต้องต่อท้าย| shเพื่อตรวจสอบว่าคำสั่งทำในสิ่งที่คุณต้องการ

ในการเรียกคืนผ่านหลายไดเรกทอรีให้ใช้สิ่งที่ต้องการ

find . -type f |
sed -n 's/\(.*\)\(-[0-9.]*\.pkg\)/mv "\1\2" "\1.pkg"/p' |
sh

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


ยกโทษให้กับความไร้เดียงสาของฉัน แต่จะไม่รอดพ้นจากวงเล็บที่มีแบ็กสแลชทำให้ถือว่าเป็นอักขระตามตัวอักษร?
devios1

2
ลำดับการจัดกลุ่มนิพจน์ทั่วไปคือ (แทนที่จะเป็นวงเล็บเดี่ยว
Diomidis Spinellis

ไม่ได้ผลสำหรับฉันยินดีที่จะได้รับความช่วยเหลือจากคุณ .... คำต่อท้ายของไฟล์ FROM ต่อท้ายไฟล์ TO: $ find - ประเภท d | sed -n 's /(.*)\/(.*) anysoftkeyboard (. *) / mv "\ 1 \ / \ 2anysoftkeyboard \ 3" "\ 1 \ / \ 2effectedkeyboard \ 3" / p' | sh >> >>>>> OUTPUT >>>> mv: เปลี่ยนชื่อ ./base/src/main/java/com/anysoftkeyboard/base/dictionaries เป็น ./base/src/main/java/com/effectedkeyboard/base/dictionaries/dictionaries : ไม่มีไฟล์หรือไดเรกทอรีดังกล่าว
Vitali Pom

ดูเหมือนว่าคุณจะไม่มีเครื่องหมายแบ็กสแลชในวงเล็บ
Diomidis Spinellis

1
อย่าใช้lsในสคริปต์ ความหลากหลายแรกสามารถทำได้printf '%s\n' * | sed ...และด้วยความคิดสร้างสรรค์บางอย่างคุณก็สามารถกำจัดsedมันได้เช่นกัน Bash printfเสนอ'%q'รหัสรูปแบบซึ่งอาจเป็นประโยชน์หากคุณมีชื่อไฟล์ที่มีตัวอักษรเชลล์ตามตัวอักษร
tripleee

10

ฉันจะทำสิ่งนี้:

for file in *.pkg ; do
    mv $file $(echo $file | rev | cut -f2- -d- | rev).pkg
done

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

แก้ไข : นอกจากนี้เวอร์ชันนี้ไม่ได้ใช้คุณสมบัติเฉพาะของ bash เหมือนรุ่นอื่น ๆ ข้างต้นซึ่งจะทำให้คุณพกพาได้มากขึ้น


ทั้งหมดอยู่ในไดเร็กทอรีเดียวกัน คุณคิดอย่างไรกับคำตอบของ rq?
Jeremy L

ฉันไม่ชอบที่จะใช้คุณสมบัติเฉพาะของ bash แต่เป็นมาตรฐานมากกว่า
Diego Sevilla

1
ฉันคิดว่านี่เป็นการเปลี่ยนชื่ออย่างรวดเร็วไม่ใช่สำหรับการเขียนข้ามแพลตฟอร์มรุ่นต่อไปแอปพลิเคชันองค์กรแบบพกพา ;-)
richq

1
ฮ่าฮ่ายุติธรรมพอแล้ว ... ออกจากคนพกพาอย่างฉัน :)
Diego Sevilla

อันนี้ไม่ได้อธิบายถึงตัวอย่างที่สาม: xorg-libXrandr (ยัติภังค์ที่ใช้ในชื่อ)
Jeremy L

5

นี่คือ POSIX ใกล้เทียบเท่าของคำตอบที่ได้รับการยอมรับในขณะนี้ สิ่งนี้ซื้อขายการ${variable/substring/replacement}ขยายพารามิเตอร์Bash อย่างเดียวสำหรับหนึ่งที่มีอยู่ในเชลล์ที่เข้ากันได้กับ Bourne

for i in ./*.pkg; do
    mv "$i" "${i%-[0-9.]*.pkg}.pkg"
done

การขยายพารามิเตอร์${variable%pattern}จะสร้างค่าของvariableคำต่อท้ายที่ตรงกันซึ่งpatternถูกลบออก (นอกจากนี้ยังมี${variable#pattern}การลบคำนำหน้า)

ฉันเก็บรูปแบบย่อยไว้-[0-9.]*จากคำตอบที่ยอมรับแม้ว่าอาจทำให้เข้าใจผิดได้ ไม่ใช่นิพจน์ทั่วไป แต่เป็นรูปแบบวงกลม ดังนั้นจึงไม่ได้หมายถึง "เครื่องหมายขีดตามด้วยตัวเลขหรือจุดศูนย์ขึ้นไป" แต่จะหมายถึง "เครื่องหมายขีดตามด้วยตัวเลขหรือจุดตามด้วยอะไรก็ได้" "อะไรก็ได้" จะเป็นการจับคู่ที่สั้นที่สุดไม่ใช่สิ่งที่ยาวที่สุด (Bash เสนอ##และ%%สำหรับการตัดคำนำหน้าหรือคำต่อท้ายที่ยาวที่สุดที่เป็นไปได้แทนที่จะสั้นที่สุด)


5

เราสามารถสันนิษฐานได้ว่าsedมีอยู่ใน * nix ใด ๆ แต่เราไม่แน่ใจว่าจะรองรับsed -nการสร้างคำสั่ง mv ( หมายเหตุ: GNU เท่านั้นที่sedทำสิ่งนี้ได้)

ถึงกระนั้นก็ตาม bash builtins และ sed เราสามารถแส้ฟังก์ชันเชลล์เพื่อทำสิ่งนี้ได้อย่างรวดเร็ว

sedrename() {
  if [ $# -gt 1 ]; then
    sed_pattern=$1
    shift
    for file in $(ls $@); do
      mv -v "$file" "$(sed $sed_pattern <<< $file)"
    done
  else
    echo "usage: $0 sed_pattern files..."
  fi
}

การใช้งาน

sedrename 's|\(.*\)\(-[0-9.]*\.pkg\)|\1\2|' *.pkg

before:

./Xft2-2.1.13.pkg
./jasper-1.900.1.pkg
./xorg-libXrandr-1.2.3.pkg

after:

./Xft2.pkg
./jasper.pkg
./xorg-libXrandr.pkg

การสร้างโฟลเดอร์เป้าหมาย:

เนื่องจากmvไม่ได้สร้างโฟลเดอร์เป้าหมายโดยอัตโนมัติเราจึงไม่สามารถใช้เวอร์ชันเริ่มต้นของเราsedrenameได้

เป็นการเปลี่ยนแปลงที่ค่อนข้างเล็กดังนั้นจึงเป็นการดีที่จะรวมคุณลักษณะดังกล่าว:

เราจะต้องมีฟังก์ชั่นยูทิลิตี้abspath(หรือพา ธ สัมบูรณ์) เนื่องจาก bash ไม่มีบิลด์นี้

abspath () { case "$1" in
               /*)printf "%s\n" "$1";;
               *)printf "%s\n" "$PWD/$1";;
             esac; }

เมื่อเรามีแล้วเราสามารถสร้างโฟลเดอร์เป้าหมายสำหรับรูปแบบ sed / เปลี่ยนชื่อซึ่งรวมถึงโครงสร้างโฟลเดอร์ใหม่

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

# generate the rename target
target="$(sed $sed_pattern <<< $file)"

# Use absolute path of the rename target to make target folder structure
mkdir -p "$(dirname $(abspath $target))"

# finally move the file to the target name/folders
mv -v "$file" "$target"

นี่คือสคริปต์รับรู้โฟลเดอร์แบบเต็ม ...

sedrename() {
  if [ $# -gt 1 ]; then
    sed_pattern=$1
    shift
    for file in $(ls $@); do
      target="$(sed $sed_pattern <<< $file)"
      mkdir -p "$(dirname $(abspath $target))"
      mv -v "$file" "$target"
    done
  else
    echo "usage: $0 sed_pattern files..."
  fi
}

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

หากเราต้องการใส่เพลงทั้งหมดลงในโฟลเดอร์./Beethoven/เราสามารถทำได้:

การใช้งาน

sedrename 's|Beethoven - |Beethoven/|g' *.mp3

before:

./Beethoven - Fur Elise.mp3
./Beethoven - Moonlight Sonata.mp3
./Beethoven - Ode to Joy.mp3
./Beethoven - Rage Over the Lost Penny.mp3

after:

./Beethoven/Fur Elise.mp3
./Beethoven/Moonlight Sonata.mp3
./Beethoven/Ode to Joy.mp3
./Beethoven/Rage Over the Lost Penny.mp3

รอบโบนัส ...

การใช้สคริปต์นี้เพื่อย้ายไฟล์จากโฟลเดอร์ไปยังโฟลเดอร์เดียว:

สมมติว่าเราต้องการรวบรวมไฟล์ทั้งหมดที่ตรงกันและวางไว้ในโฟลเดอร์ปัจจุบันเราสามารถทำได้:

sedrename 's|.*/||' **/*.mp3

before:

./Beethoven/Fur Elise.mp3
./Beethoven/Moonlight Sonata.mp3
./Beethoven/Ode to Joy.mp3
./Beethoven/Rage Over the Lost Penny.mp3

after:

./Beethoven/ # (now empty)
./Fur Elise.mp3
./Moonlight Sonata.mp3
./Ode to Joy.mp3
./Rage Over the Lost Penny.mp3

หมายเหตุเกี่ยวกับรูปแบบ sed regex

กฎรูปแบบ sed ปกติใช้ในสคริปต์นี้รูปแบบเหล่านี้ไม่ใช่ PCRE (Perl Compatible Regular Expressions) คุณสามารถขยายไวยากรณ์นิพจน์ทั่วไปได้โดยใช้อย่างใดอย่างหนึ่งsed -rหรือsed -E ขึ้นอยู่กับแพลตฟอร์มของคุณ

ดูที่สอดคล้องกับ POSIX man re_formatสำหรับคำอธิบายที่สมบูรณ์ของรูปแบบ regexp พื้นฐานและส่วนขยาย sed


4

ฉันพบว่าการเปลี่ยนชื่อเป็นเครื่องมือที่ตรงไปตรงมากว่ามากที่จะใช้กับสิ่งนี้ ฉันพบมันใน Homebrew สำหรับ OSX

สำหรับตัวอย่างของคุณฉันจะทำ:

rename 's/\d*?\.\d*?\.\d*?//' *.pkg

's' หมายถึงสิ่งทดแทน แบบฟอร์มคือ s / searchPattern / replacement / files_to_apply คุณต้องใช้ regex สำหรับสิ่งนี้ซึ่งใช้เวลาศึกษาเล็กน้อย แต่ก็คุ้มค่ากับความพยายาม


ฉันเห็นด้วย. สำหรับบางครั้งการใช้งานforและsedอื่น ๆ ก็ใช้ได้ แต่ใช้งานง่ายและเร็วกว่ามากrenameหากคุณพบว่าตัวเองทำสิ่งนี้บ่อยๆ
argentum2f

คุณกำลังหลบหนีช่วงเวลาหรือไม่?
Big Money

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

3

ใช้ดีกว่าsedสำหรับสิ่งนี้สิ่งที่ต้องการ:

find . -type f -name "*.pkg" |
 sed -e 's/((.*)-[0-9.]*\.pkg)/\1 \2.pkg/g' |
 while read nameA nameB; do
    mv $nameA $nameB;
 done

การหานิพจน์ทั่วไปจะถูกปล่อยให้เป็นแบบฝึกหัด (เช่นเดียวกับการจัดการกับชื่อไฟล์ที่มีช่องว่าง)


ขอบคุณ: Spaces ไม่ได้ใช้ในชื่อ
Jeremy L

1

ดูเหมือนว่าจะทำงานได้โดยสมมติว่า

  • ทุกอย่างลงท้ายด้วย $ pkg
  • เวอร์ชัน # ของคุณจะขึ้นต้นด้วย "-" เสมอ

ถอด. pkg แล้วเปลื้อง - ..

for x in $(ls); do echo $x $(echo $x | sed 's/\.pkg//g' | sed 's/-.*//g').pkg; done

มีบางแพ็กเกจที่มียัติภังค์อยู่ในชื่อ (ดูตัวอย่างที่สาม) สิ่งนี้ส่งผลต่อรหัสของคุณหรือไม่
Jeremy L

1
คุณสามารถเรียกใช้การแสดงออก sed -eหลายคนโดยใช้ เช่นsed -e 's/\.pkg//g' -e 's/-.*//g'.
อังคารที่

อย่าแยกวิเคราะห์lsผลลัพธ์และอ้างตัวแปรของคุณ โปรดดูที่shellcheck.netสำหรับการวินิจฉัยปัญหาทั่วไปและการต่อต้านรูปแบบในเชลล์สคริปต์
tripleee

1

ฉันมี*.txtไฟล์หลายไฟล์ที่ต้องเปลี่ยนชื่อ.sqlในโฟลเดอร์เดียวกัน ด้านล่างทำงานให้ฉัน:

for i in \`ls *.txt | awk -F "." '{print $1}'\` ;do mv $i.txt $i.sql; done

2
คุณสามารถปรับปรุงคำตอบของคุณโดยสรุปเป็นกรณีการใช้งานจริงของ OP
dakab

ปัญหานี้มีหลายปัญหาเช่นเดียวกับข้อผิดพลาดทางไวยากรณ์ที่ชัดเจน ดูshellcheck.netสำหรับการเริ่มต้น
tripleee

0

ขอบคุณสำหรับคำตอบนี้ ฉันยังมีปัญหาบางอย่าง การย้ายไฟล์. nzb.queued ไปยังไฟล์. nzb มันมีช่องว่างและส่วนอื่น ๆ ในชื่อไฟล์และสิ่งนี้ช่วยแก้ปัญหาของฉัน:

find . -type f -name "*.nzb.queued" |
sed -ne "s/^\(\(.*\).nzb.queued\)$/mv -v \"\1\" \"\2.nzb\"/p" |
sh

ขึ้นอยู่กับคำตอบของ Diomidis Spinellis

regex สร้างกลุ่มหนึ่งสำหรับชื่อไฟล์ทั้งหมดและอีกกลุ่มหนึ่งสำหรับส่วนก่อน. nzb.queued จากนั้นสร้างคำสั่งย้ายเชลล์ ด้วยสตริงที่ยกมา นอกจากนี้ยังหลีกเลี่ยงการสร้างลูปในเชลล์สคริปต์เนื่องจากสิ่งนี้ทำได้แล้วโดย sed


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