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
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
คำตอบ:
คุณสามารถใช้คุณสมบัติการขยายพารามิเตอร์ของ bash
for i in ./*.pkg ; do mv "$i" "${i/-[0-9.]*.pkg/.pkg}" ; done
จำเป็นต้องมีเครื่องหมายคำพูดสำหรับชื่อไฟล์ที่มีช่องว่าง
rename "s/-[0-9.]*//" *.pkg
หากไฟล์ทั้งหมดอยู่ในไดเร็กทอรีเดียวกันลำดับ
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การแสดงออกปกติการจัดกลุ่มลำดับวงเล็บนำหน้าด้วยเครื่องหมาย, \(และ\)มากกว่าวงเล็บเดียวและ()
lsในสคริปต์ ความหลากหลายแรกสามารถทำได้printf '%s\n' * | sed ...และด้วยความคิดสร้างสรรค์บางอย่างคุณก็สามารถกำจัดsedมันได้เช่นกัน Bash printfเสนอ'%q'รหัสรูปแบบซึ่งอาจเป็นประโยชน์หากคุณมีชื่อไฟล์ที่มีตัวอักษรเชลล์ตามตัวอักษร
ฉันจะทำสิ่งนี้:
for file in *.pkg ; do
mv $file $(echo $file | rev | cut -f2- -d- | rev).pkg
done
ไฟล์ทั้งหมดของคุณควรอยู่ในไดเร็กทอรีปัจจุบัน ถ้าไม่ลองใช้ find ตามที่ Javier แนะนำข้างต้น
แก้ไข : นอกจากนี้เวอร์ชันนี้ไม่ได้ใช้คุณสมบัติเฉพาะของ bash เหมือนรุ่นอื่น ๆ ข้างต้นซึ่งจะทำให้คุณพกพาได้มากขึ้น
นี่คือ 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 เสนอ##และ%%สำหรับการตัดคำนำหน้าหรือคำต่อท้ายที่ยาวที่สุดที่เป็นไปได้แทนที่จะสั้นที่สุด)
เราสามารถสันนิษฐานได้ว่า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 ปกติใช้ในสคริปต์นี้รูปแบบเหล่านี้ไม่ใช่ PCRE (Perl Compatible Regular Expressions) คุณสามารถขยายไวยากรณ์นิพจน์ทั่วไปได้โดยใช้อย่างใดอย่างหนึ่งsed -rหรือsed -E
ขึ้นอยู่กับแพลตฟอร์มของคุณ
ดูที่สอดคล้องกับ POSIX man re_formatสำหรับคำอธิบายที่สมบูรณ์ของรูปแบบ regexp พื้นฐานและส่วนขยาย sed
ฉันพบว่าการเปลี่ยนชื่อเป็นเครื่องมือที่ตรงไปตรงมากว่ามากที่จะใช้กับสิ่งนี้ ฉันพบมันใน Homebrew สำหรับ OSX
สำหรับตัวอย่างของคุณฉันจะทำ:
rename 's/\d*?\.\d*?\.\d*?//' *.pkg
's' หมายถึงสิ่งทดแทน แบบฟอร์มคือ s / searchPattern / replacement / files_to_apply คุณต้องใช้ regex สำหรับสิ่งนี้ซึ่งใช้เวลาศึกษาเล็กน้อย แต่ก็คุ้มค่ากับความพยายาม
forและsedอื่น ๆ ก็ใช้ได้ แต่ใช้งานง่ายและเร็วกว่ามากrenameหากคุณพบว่าตัวเองทำสิ่งนี้บ่อยๆ
renameคำสั่ง นอกจากนี้บางส่วนจะมีความแตกต่างกัน renameคำสั่งที่มีคุณสมบัติที่แตกต่างกันไวยากรณ์และ / หรือตัวเลือก ดูเหมือนว่า Perl renameซึ่งเป็นที่นิยมพอสมควร แต่คำตอบควรชี้แจงเรื่องนี้จริงๆ
ใช้ดีกว่า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
การหานิพจน์ทั่วไปจะถูกปล่อยให้เป็นแบบฝึกหัด (เช่นเดียวกับการจัดการกับชื่อไฟล์ที่มีช่องว่าง)
ดูเหมือนว่าจะทำงานได้โดยสมมติว่า
ถอด. pkg แล้วเปลื้อง - ..
for x in $(ls); do echo $x $(echo $x | sed 's/\.pkg//g' | sed 's/-.*//g').pkg; done
-eหลายคนโดยใช้ เช่นsed -e 's/\.pkg//g' -e 's/-.*//g'.
lsผลลัพธ์และอ้างตัวแปรของคุณ โปรดดูที่shellcheck.netสำหรับการวินิจฉัยปัญหาทั่วไปและการต่อต้านรูปแบบในเชลล์สคริปต์
ฉันมี*.txtไฟล์หลายไฟล์ที่ต้องเปลี่ยนชื่อ.sqlในโฟลเดอร์เดียวกัน ด้านล่างทำงานให้ฉัน:
for i in \`ls *.txt | awk -F "." '{print $1}'\` ;do mv $i.txt $i.sql; done
ขอบคุณสำหรับคำตอบนี้ ฉันยังมีปัญหาบางอย่าง การย้ายไฟล์. 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
-nตัวเลือกในลักษณะเดียวกัน