ฉันจะลบคำต่อท้ายไฟล์และส่วนเส้นทางจากสตริงเส้นทางใน Bash ได้อย่างไร


396

เมื่อกำหนดเส้นทางของไฟล์สตริงเช่น/foo/fizzbuzz.barฉันจะใช้ bash เพื่อแยกfizzbuzzส่วนของสตริงดังกล่าวได้อย่างไร


ข้อมูลที่คุณสามารถพบได้ในคู่มือ Bashค้นหา${parameter%word}และ${parameter%%word}ในส่วนการจับคู่ส่วนต่อท้าย
1ac0

คำตอบ:


574

ต่อไปนี้เป็นวิธีดำเนินการกับตัวดำเนินการ # และ% ใน Bash

$ x="/foo/fizzbuzz.bar"
$ y=${x%.bar}
$ echo ${y##*/}
fizzbuzz

${x%.bar}อาจเป็นการ${x%.*}ลบทุกอย่างหลังจากจุดหรือ${x%%.*}ลบทุกอย่างหลังจากจุดแรก

ตัวอย่าง:

$ x="/foo/fizzbuzz.bar.quux"
$ y=${x%.*}
$ echo $y
/foo/fizzbuzz.bar
$ y=${x%%.*}
$ echo $y
/foo/fizzbuzz

เอกสารที่สามารถพบได้ในคู่มือการทุบตี ค้นหา${parameter%word}และ${parameter%%word}ต่อท้ายส่วนการจับคู่ส่วน


ฉันลงเอยด้วยการใช้อันนี้เพราะมันยืดหยุ่นที่สุดและมีอีกสองอย่างที่คล้ายกันที่ฉันอยากทำ
Lawrence Johnston

นี่อาจเป็นคำตอบที่ยืดหยุ่นที่สุด แต่ฉันคิดว่าคำตอบที่แนะนำคำสั่ง basename และ dirname ควรได้รับความสนใจเช่นกัน พวกเขาอาจเป็นเพียงเคล็ดลับถ้าคุณไม่ต้องการการจับคู่รูปแบบแฟนซีอื่น ๆ
mgadda

7
สิ่งนี้เรียกว่า $ {x% .bar} ฉันต้องการเรียนรู้เพิ่มเติมเกี่ยวกับเรื่องนี้
เพรา

14
@Basil: การขยายพารามิเตอร์ บนคอนโซลประเภท "man bash" จากนั้นพิมพ์ "/ พารามิเตอร์ส่วนขยาย"
Zan Lynx

1
ฉันเดาว่าคำอธิบาย 'man bash' นั้นสมเหตุสมผลถ้าคุณรู้อยู่แล้วว่ามันทำอะไรหรือถ้าคุณลองด้วยตัวเองอย่างหนัก มันเกือบจะไม่ดีเท่ากับการอ้างอิงคอมไพล์ ฉันแค่ google มันแทน
triplebig

226

ดูที่คำสั่ง basename:

NAME="$(basename /foo/fizzbuzz.bar .bar)"

11
อาจเป็นวิธีที่ง่ายที่สุดของโซลูชันที่เสนอในขณะนี้ทั้งหมด ... ถึงแม้ว่าฉันจะใช้ $ (... ) แทน backticks
Michael Johnson

6
เรียบง่าย แต่เพิ่มการพึ่งพา (ไม่ใช่คนที่ยิ่งใหญ่หรือแปลกประหลาดฉันยอมรับ) นอกจากนี้ยังจำเป็นต้องรู้คำต่อท้าย
Vinko Vrsalovic

และสามารถใช้เพื่อลบสิ่งใด ๆ ออกจากจุดสิ้นสุดโดยทั่วไปมันเป็นเพียงการลบสตริงออกจากปลาย
มาร์ค

2
ปัญหาคือเวลาที่โดน ฉันค้นหาคำถามสำหรับการสนทนานี้หลังจากดูการทุบตีใช้เวลาเกือบ 5 นาทีในการประมวลผลไฟล์ 800 ไฟล์โดยใช้ชื่อไฟล์พื้นฐาน เมื่อใช้วิธีการ regex ข้างต้นเวลาจะลดลงเหลือประมาณ 7 วินาที แม้ว่าคำตอบนี้จะทำได้ง่ายกว่าสำหรับโปรแกรมเมอร์ แต่เวลาที่ได้รับนั้นมากเกินไป ลองนึกภาพโฟลเดอร์ที่มีไฟล์อยู่สองสามพันไฟล์! ฉันมีโฟลเดอร์บางอย่าง
xizdaqrian

@xizdaqrian นี่เป็นเรื่องจริงอย่างแน่นอน นี่เป็นโปรแกรมง่ายๆที่ไม่ควรใช้เวลาครึ่งวินาทีในการส่งคืน ฉันเพิ่งรันเวลาค้นหา / home / me / dev -name "* .py" .py -exec basename {} \; และจะแยกส่วนขยายและไดเรกทอรีสำหรับไฟล์ 1500 ไฟล์ใน 1 วินาที
Laszlo Treszkai

40

Pure ทุบตีทำในสองการดำเนินการแยก:

  1. ลบเส้นทางออกจาก path-string:

    path=/foo/bar/bim/baz/file.gif
    
    file=${path##*/}  
    #$file is now 'file.gif'
  2. ลบนามสกุลออกจาก path-string:

    base=${file%.*}
    #${base} is now 'file'.

18

ใช้ basename ฉันใช้ต่อไปนี้เพื่อให้บรรลุนี้

for file in *; do
    ext=${file##*.}
    fname=`basename $file $ext`

    # Do things with $fname
done;

สิ่งนี้ไม่จำเป็นต้องมีความรู้เบื้องต้นเกี่ยวกับนามสกุลไฟล์และใช้งานได้แม้ว่าคุณจะมีชื่อไฟล์ที่มีจุดอยู่ในชื่อไฟล์ (หน้านามสกุลของมัน) มันต้องการโปรแกรมbasenameแต่นี่เป็นส่วนหนึ่งของ coreutils ของ GNU ดังนั้นจึงควรจัดส่งด้วย distro ใด ๆ


1
คำตอบที่ยอดเยี่ยม! ลบส่วนขยายด้วยวิธีที่สะอาดมาก แต่ไม่ได้ลบออก ในตอนท้ายของชื่อไฟล์
metrix

3
@metrix เพียงเพิ่ม "." ก่อน $ ext เช่น: fname=`basename $file .$ext`
Carlos Troncoso

13

วิธีทุบตีบริสุทธิ์:

~$ x="/foo/bar/fizzbuzz.bar.quux.zoom"; 
~$ y=${x/\/*\//}; 
~$ echo ${y/.*/}; 
fizzbuzz

ฟังก์ชั่นนี้มีการอธิบายเกี่ยวกับการทุบตีคนภายใต้ "การขยายพารามิเตอร์" วิธีการทุบตีที่ไม่อุดมสมบูรณ์: awk, perl, sed และอื่น ๆ

แก้ไข: ทำงานร่วมกับจุดในคำต่อท้ายไฟล์และไม่จำเป็นต้องรู้คำต่อท้าย (ส่วนขยาย) แต่ไม่ได้ทำงานกับจุดในชื่อตัวเอง


11

ฟังก์ชัน basename และ dirname คือสิ่งที่คุณต้องการ:

mystring=/foo/fizzbuzz.bar
echo basename: $(basename "${mystring}")
echo basename + remove .bar: $(basename "${mystring}" .bar)
echo dirname: $(dirname "${mystring}")

มีเอาต์พุต:

basename: fizzbuzz.bar
basename + remove .bar: fizzbuzz
dirname: /foo

1
มันจะมีประโยชน์ในการแก้ไขข้อความที่นี่ - อาจเรียกใช้ผ่านshellcheck.netด้วยmystring=$1แทนที่จะเป็นค่าคงที่ในปัจจุบัน (ซึ่งจะระงับคำเตือนหลายคำ พบ?
Charles Duffy

ฉันได้ทำการเปลี่ยนแปลงที่เหมาะสมเพื่อรองรับเครื่องหมายคำพูดใน $ mystring เอ้ยเป็นเวลานานแล้วที่ฉันได้เขียนสิ่งนี้ :)
23919

จะเป็นการปรับปรุงเพิ่มเติมเพื่อเสนอราคาผลลัพธ์: echo "basename: $(basename "$mystring")"- หากmystring='/foo/*'คุณไม่ได้รับการ*แทนที่ด้วยรายการไฟล์ในไดเรกทอรีปัจจุบันหลังจาก basenameเสร็จสิ้น
Charles Duffy

6

การใช้ basenameจะถือว่าคุณรู้ว่าส่วนขยายของไฟล์คืออะไรใช่ไหม

และฉันเชื่อว่าคำแนะนำการแสดงออกปกติต่าง ๆ ไม่ได้รับมือกับชื่อไฟล์ที่มีมากกว่าหนึ่ง "."

ต่อไปนี้ดูเหมือนว่าจะรับมือกับจุดสองจุด โอ้และชื่อไฟล์ที่มี "/" ตัวเอง (เพียงเพื่อความบันเทิง)

ในการถอดความภาษาปาสคาล "ขออภัยสคริปต์นี้ยาวมากฉันไม่มีเวลาที่จะทำให้สั้นลง"


  #!/usr/bin/perl
  $fullname = $ARGV[0];
  ($path,$name) = $fullname =~ /^(.*[^\\]\/)*(.*)$/;
  ($basename,$extension) = $name =~ /^(.*)(\.[^.]*)$/;
  print $basename . "\n";
 

1
นี่เป็นสิ่งที่ดีและมีประสิทธิภาพ
Gaurav Jain

4

นอกจากนี้ยังมีไวยากรณ์ที่สอดคล้องกับ POSIXใช้ในคำตอบนี้ ,

basename string [suffix]

เช่นเดียวกับใน

basename /foo/fizzbuzz.bar .bar

GNUbasenameรองรับไวยากรณ์อื่น:

basename -s .bar /foo/fizzbuzz.bar

ด้วยผลลัพธ์เดียวกัน ความแตกต่างและความได้เปรียบคือเป็น-sนัย-aซึ่งรองรับหลายอาร์กิวเมนต์:

$ basename -s .bar /foo/fizzbuzz.bar /baz/foobar.bar
fizzbuzz
foobar

สิ่งนี้สามารถสร้างชื่อไฟล์ได้อย่างปลอดภัยโดยแยกเอาท์พุทด้วย NUL ไบต์โดยใช้-zตัวเลือกตัวอย่างเช่นไฟล์เหล่านี้ที่มีช่องว่าง, บรรทัดใหม่และตัวอักษรกลม (อ้างอิงโดยls):

$ ls has*
'has'$'\n''newline.bar'  'has space.bar'  'has*.bar'

การอ่านอาเรย์:

$ readarray -d $'\0' arr < <(basename -zs .bar has*)
$ declare -p arr
declare -a arr=([0]=$'has\nnewline' [1]="has space" [2]="has*")

readarray -dต้องการ Bash 4.4 หรือใหม่กว่า สำหรับเวอร์ชั่นเก่าเราต้องวนซ้ำ:

while IFS= read -r -d '' fname; do arr+=("$fname"); done < <(basename -zs .bar has*)

นอกจากนี้คำต่อท้ายที่ระบุจะถูกลบออกในเอาต์พุตหากมีอยู่ (และละเว้นเป็นอย่างอื่น)
aksh1618


3

หากคุณไม่สามารถใช้ basename ตามที่แนะนำในโพสต์อื่น ๆ คุณสามารถใช้ sed ได้ตลอดเวลา นี่คือตัวอย่าง (น่าเกลียด) มันไม่ใช่สิ่งที่ยิ่งใหญ่ที่สุด แต่ทำงานได้โดยการแยกสตริงที่ต้องการและแทนที่อินพุตด้วยสตริงที่ต้องการ

echo '/foo/fizzbuzz.bar' | sed 's|.*\/\([^\.]*\)\(\..*\)$|\1|g'

ซึ่งคุณจะได้รับผลลัพธ์

FizzBuzz


แม้ว่านี่จะเป็นคำตอบสำหรับคำถามต้นฉบับคำสั่งนี้มีประโยชน์เมื่อฉันมีบรรทัดของพา ธ ในไฟล์เพื่อแยกชื่อฐานเพื่อพิมพ์ออกมาบนหน้าจอ
Sangcheol Choi

2

ระวังวิธีการแก้ปัญหา perl ที่แนะนำ: มันจะลบสิ่งใดหลังจากจุดแรก

$ echo some.file.with.dots | perl -pe 's/\..*$//;s{^.*/}{}'
some

หากคุณต้องการทำด้วย perl งานนี้:

$ echo some.file.with.dots | perl -pe 's/(.*)\..*$/$1/;s{^.*/}{}'
some.file.with

แต่ถ้าคุณใช้ Bash โซลูชันที่มีy=${x%.*}(หรือbasename "$x" .extถ้าคุณรู้ว่าส่วนขยาย) นั้นง่ายกว่ามาก


1

ชื่อไฟล์ทำเช่นนั้นลบเส้นทาง นอกจากนี้ยังจะลบคำต่อท้ายหากได้รับและหากตรงกับคำต่อท้ายของไฟล์ แต่คุณจะต้องรู้คำต่อท้ายเพื่อให้คำสั่ง มิฉะนั้นคุณสามารถใช้ mv และค้นหาว่าชื่อใหม่ควรมีวิธีอื่นอย่างไร


1

การรวมคำตอบที่ติดอันดับสูงสุดเข้ากับคำตอบที่ติดอันดับสองเพื่อให้ได้ชื่อไฟล์โดยไม่มีเส้นทางแบบเต็ม:

$ x="/foo/fizzbuzz.bar.quux"
$ y=(`basename ${x%%.*}`)
$ echo $y
fizzbuzz

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