โซลูชันเหล่านี้ที่คุณเชื่อมโยงไปนั้นดีมาก คำตอบบางคำอาจขาดคำอธิบายดังนั้นลองเรียงลำดับออกมาเพิ่มอีกหน่อย
สายของคุณนี้
for file in *.txt
บ่งชี้ว่าส่วนขยายเป็นที่รู้จักกันล่วงหน้า (หมายเหตุ: สภาพแวดล้อมที่สอดคล้องกับ POSIX เป็นกรณี ๆ ไป *.txt
จะไม่ตรงกัน FOO.TXT
) ในกรณีเช่นนี้
basename -s .txt "$file"
ควรคืนชื่อโดยไม่มีนามสกุล ( basename
ยังลบเส้นทางไดเรกทอรี: /directory/path/filename
& amp; rightarrow; filename
; ในกรณีของคุณมันไม่สำคัญเพราะ $file
ไม่มีเส้นทางดังกล่าว) ในการใช้เครื่องมือในรหัสของคุณคุณต้องทดแทนคำสั่งที่มีลักษณะดังนี้: $(some_command)
. การทดแทนคำสั่งใช้เอาต์พุตของ some_command
ถือว่าเป็นสตริงและวางไว้ที่ใด $(…)
คือ. การเปลี่ยนเส้นทางเฉพาะของคุณจะเป็น
… > "./$(basename -s .txt "$file")_sorted.txt"
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the output of basename will replace this
คำพูดซ้อนกันอยู่ที่นี่เพราะ Bash ฉลาดพอที่จะรู้คำพูดได้ $(…)
ถูกจับคู่เข้าด้วยกัน
สิ่งนี้สามารถปรับปรุงได้ บันทึก basename
เป็นไฟล์ปฏิบัติการแยกต่างหากไม่ใช่เชลล์ในตัว (ใน Bash run type basename
, เปรียบเทียบกับ type cd
) การวางกระบวนการพิเศษใด ๆ เป็นค่าใช้จ่ายมันต้องใช้ทรัพยากรและเวลา การวางไข่แบบวนซ้ำมักจะทำงานได้ไม่ดี ดังนั้นคุณควรใช้อะไรก็ตามที่เชลล์เสนอให้คุณเพื่อหลีกเลี่ยงกระบวนการพิเศษ ในกรณีนี้การแก้ปัญหาคือ:
… > "./${file%.txt}_sorted.txt"
ไวยากรณ์อธิบายไว้ด้านล่างสำหรับกรณีทั่วไปที่มากกว่า
ในกรณีที่คุณไม่รู้จักส่วนขยาย:
… > "./${file%.*}_sorted.${file##*.}"
ไวยากรณ์อธิบาย:
${file#*.}
- $file
แต่การจับคู่สตริงที่สั้นที่สุด *.
ถูกลบออกจากด้านหน้า
${file##*.}
- $file
แต่การจับคู่สตริงที่ยาวที่สุด *.
ถูกลบออกจากด้านหน้า ใช้มันเพื่อรับส่วนขยาย
${file%.*}
- $file
แต่การจับคู่สตริงที่สั้นที่สุด .*
จะถูกลบออกจากจุดสิ้นสุด; ใช้มันเพื่อรับทุกอย่างยกเว้นการขยาย
${file%%.*}
- $file
แต่ด้วยการจับคู่สตริงที่ยาวที่สุด .*
จะถูกลบออกจากจุดสิ้นสุด;
การจับคู่รูปแบบเหมือนกลมไม่ใช่ regex ซึ่งหมายความว่า *
เป็นสัญลักษณ์แทนสำหรับศูนย์หรือมากกว่าตัวอักษร ?
เป็นอักขระตัวแทนสำหรับอักขระหนึ่งตัว (เราไม่ต้องการ ?
ในกรณีของคุณแม้ว่า) เมื่อคุณวิงวอน ls *.txt
หรือ for file in *.txt;
คุณกำลังใช้กลไกการจับคู่รูปแบบเดียวกัน อนุญาตให้ใช้รูปแบบที่ไม่มีอักขระแทน เราได้ใช้ไปแล้ว ${file%.txt}
ที่ไหน .txt
เป็นรูปแบบ
ตัวอย่าง:
$ file=name.name2.name3.ext
$ echo "${file#*.}"
name2.name3.ext
$ echo "${file##*.}"
ext
$ echo "${file%.*}"
name.name2.name3
$ echo "${file%%.*}"
name
แต่ระวัง:
$ file=extensionless
$ echo "${file#*.}"
extensionless
$ echo "${file##*.}"
extensionless
$ echo "${file%.*}"
extensionless
$ echo "${file%%.*}"
extensionless
ด้วยเหตุนี้การคุมกำเนิดต่อไปนี้ อาจ มีประโยชน์ (แต่ไม่ใช่คำอธิบายด้านล่าง):
${file#${file%.*}}
มันทำงานได้โดยระบุทุกอย่างยกเว้นส่วนขยาย ( ${file%.*}
) จากนั้นลบสิ่งนี้ออกจากสตริงทั้งหมด ผลลัพธ์เป็นดังนี้:
$ file=name.name2.name3.ext
$ echo "${file#${file%.*}}"
.ext
$ file=extensionless
$ echo "${file#${file%.*}}"
$ # empty output above
หมายเหตุ .
รวมอยู่ในเวลานี้ คุณอาจได้รับผลลัพธ์ที่ไม่คาดคิดถ้า $file
มีตัวอักษร *
หรือ ?
; แต่ Windows (ในกรณีที่ส่วนขยายสำคัญ) ไม่อนุญาต ตัวละครเหล่านี้ในชื่อไฟล์อย่างไรก็ตามคุณอาจไม่สนใจ อย่างไรก็ตาม […]
หรือ {…}
หากมีอยู่อาจเปิดใช้รูปแบบการจับคู่รูปแบบของตนเองและทำลายโซลูชัน!
การเปลี่ยนเส้นทาง "ที่ดีขึ้น" ของคุณจะเป็น:
… > "./${file%.*}_sorted${file#${file%.*}}"
ควรสนับสนุนชื่อไฟล์ที่มีหรือไม่มีนามสกุลแม้ว่าจะไม่ใช่วงเล็บเหลี่ยมหรือหยิกน่าเสียดาย ค่อนข้างน่าละอาย ในการแก้ไขคุณต้องใส่เครื่องหมายอัญประกาศคู่ภายใน
การเปลี่ยนเส้นทางที่ดีขึ้นจริงๆ:
… > "./${file%.*}_sorted${file#"${file%.*}"}"
การอ้างอิงสองครั้งทำให้ ${file%.*}
ไม่ทำหน้าที่เป็นรูปแบบ! Bash นั้นฉลาดพอที่จะบอกราคาจากภายในและภายนอกได้เพราะราคาด้านในฝังอยู่ด้านนอก ${…}
วากยสัมพันธ์ ฉันคิดว่านี่เป็นวิธีที่ถูกต้อง .
อีกโซลูชัน (ไม่สมบูรณ์) ลองวิเคราะห์ด้วยเหตุผลทางการศึกษา:
${file/./_sorted.}
มันแทนที่ก่อน .
กับ _sorted.
. มันจะทำงานได้ดีถ้าคุณมีจุดมากที่สุดหนึ่งจุด $file
. มีไวยากรณ์ที่คล้ายกัน ${file//./_sorted.}
ที่แทนที่ทุกจุด เท่าที่ฉันรู้ไม่มีตัวแปรที่จะแทนที่ สุดท้าย จุดเท่านั้น
ยังคงเป็นโซลูชั่นเริ่มต้นสำหรับไฟล์ด้วย .
ดูแข็งแกร่ง ทางออกสำหรับการขยาย $file
ไม่สำคัญ: ${file}_sorted
. ตอนนี้สิ่งที่เราต้องการคือวิธีบอกสองกรณี นี่มันคือ:
[[ "$file" == *?.* ]]
จะส่งกลับสถานะการออก 0 (จริง) ถ้าหากเนื้อหาของ $file
ตัวแปรที่ตรงกับรูปแบบด้านขวามือ รูปแบบกล่าวว่า "มีจุดหลังจากตัวละครอย่างน้อยหนึ่งตัว" หรือเทียบเท่า "มีจุดที่ไม่ใช่จุดเริ่มต้น" ประเด็นคือการจัดการกับไฟล์ที่ซ่อนอยู่ของ Linux (เช่น .bashrc
) เป็นส่วนขยายเว้นแต่จะมี อื่น จุดที่ใดที่หนึ่ง
หมายเหตุที่เราต้องการ [[
ที่นี่ไม่ [
. อดีตมีพลังมากขึ้น แต่น่าเสียดาย ไม่พกพา ; หลังมีขนาดเล็ก แต่ จำกัด สำหรับเรา
ตรรกะตอนนี้จะเป็นดังนี้:
[[ "$file" == *?.* ]] && file1="./${file%.*}_sorted.${file##*.}" || file1="${file}_sorted"
หลังจากนี้, $file1
มีชื่อที่ต้องการดังนั้นการเปลี่ยนเส้นทางของคุณควรเป็น
… > "./$file1"
และข้อมูลโค้ดทั้งหมด ( *.txt
แทนที่ด้วย *
เพื่อระบุว่าเราทำงานกับส่วนขยายใด ๆ หรือไม่มีส่วนขยาย):
for file in *;
do
printf 'Processing %s\n' "$file"
[[ "$file" == *?.* ]] && file1="./${file%.*}_sorted.${file##*.}" || file1="${file}_sorted"
LC_ALL=C sort -u "$file" > "./$file1"
done
นี่จะพยายามประมวลผลไดเรกทอรี (ถ้ามี) เช่นกัน คุณรู้อยู่แล้วว่า สิ่งที่ต้องทำ เพื่อแก้ไข
… > "./${file%.txt}_sorted.txt"
"หลีกเลี่ยงกระบวนการพิเศษ" - เป็นเพราะเราใช้ basename ใน$file
ตัวแปรภายนอกfor
วนที่นี่:basename -s .txt "$file"
... หรือฉันเข้าใจผิด?