แยกชื่อไฟล์และนามสกุลใน Bash


2105

ฉันต้องการรับชื่อไฟล์ (ไม่มีส่วนขยาย) และส่วนขยายแยกต่างหาก

ทางออกที่ดีที่สุดที่ฉันพบคือ:

NAME=`echo "$FILE" | cut -d'.' -f1`
EXTENSION=`echo "$FILE" | cut -d'.' -f2`

สิ่งนี้ผิดเพราะใช้งานไม่ได้หากชื่อไฟล์มี.อักขระหลายตัว ถ้าสมมติว่าผมมีa.b.jsก็จะพิจารณาaและb.jsแทนและa.bjs

สามารถทำได้อย่างง่ายดายใน Python ด้วย

file, ext = os.path.splitext(path)

แต่ฉันไม่ต้องการที่จะไล่ล่าล่ามงูใหญ่สำหรับเรื่องนี้ถ้าเป็นไปได้

มีความคิดที่ดีกว่านี้ไหม?


คำถามนี้อธิบายเทคนิคการทุบตีนี้และอีกหลายข้อที่เกี่ยวข้อง
jjclarkson

28
เมื่อใช้คำตอบที่ดีด้านล่างอย่าวางในตัวแปรของคุณอย่างที่ฉันแสดงที่นี่ผิด: extension="{$filename##*.}"เหมือนฉันทำไปสักพัก! เลื่อน$ออกไปข้างนอกการโค้ง: ใช่: extension="${filename##*.}"
Chris K

4
เห็นได้ชัดว่าเป็นปัญหาที่ไม่สำคัญและสำหรับฉันมันยากที่จะบอกได้ว่าคำตอบด้านล่างนั้นถูกต้องทั้งหมดหรือไม่ มันน่าทึ่งที่นี่ไม่ใช่การทำงานในตัวใน (ba) sh (คำตอบดูเหมือนจะใช้ฟังก์ชันโดยใช้การจับคู่รูปแบบ) ฉันตัดสินใจที่จะใช้ Python os.path.splitextแทนข้างบน ...
Peter Gibson

1
ในฐานะที่เป็นส่วนขยายจะต้องเป็นตัวแทนของธรรมชาติของไฟล์มีเวทมนตร์คำสั่งแฟ้มตรวจสอบเพื่อทำนายธรรมชาติและ offert ของเขาขยายมาตรฐาน ดูคำตอบของฉัน
F. Hauri

2
คำถามนี้มีปัญหาในตอนแรกเพราะ .. จากมุมมองของระบบปฏิบัติการและระบบไฟล์ยูนิกซ์โดยทั่วไปไม่มีสิ่งใดเป็นนามสกุลไฟล์ ใช้ "." การแยกชิ้นส่วนเป็นแบบแผนของมนุษย์ซึ่งทำงานตราบเท่าที่มนุษย์ตกลงที่จะปฏิบัติตาม ตัวอย่างเช่นด้วยโปรแกรม 'tar' อาจมีการตัดสินใจตั้งชื่อไฟล์เอาต์พุตด้วย "tar" คำนำหน้าแทนคำต่อท้าย ". tar" - การให้ "tar.somedir" แทน "somedir.tar" ไม่มีวิธีแก้ปัญหา "ทั่วไปใช้งานได้เสมอ" เนื่องจากคุณต้องเขียนโค้ดที่ตรงกับความต้องการเฉพาะของคุณและชื่อไฟล์ที่ต้องการ
CM

คำตอบ:


3499

ก่อนอื่นรับชื่อไฟล์โดยไม่มีพา ธ :

filename=$(basename -- "$fullfile")
extension="${filename##*.}"
filename="${filename%.*}"

หรือคุณสามารถมุ่งเน้นไปที่ '/' สุดท้ายของเส้นทางแทนที่จะเป็น '.' ซึ่งควรทำงานแม้ว่าคุณจะมีนามสกุลไฟล์ที่ไม่แน่นอน:

filename="${fullfile##*/}"

คุณอาจต้องการตรวจสอบเอกสาร:


85
ลองดูที่gnu.org/software/bash/manual/html_node/…สำหรับชุดคุณสมบัติแบบเต็ม
D.Shawley

24
เพิ่มเครื่องหมายคำพูดลงใน "$ fullfile" มิฉะนั้นคุณอาจเสี่ยงต่อการแตกชื่อไฟล์
lhunath

47
Heck คุณสามารถเขียน filename = "$ {fullfile ## * /}" และหลีกเลี่ยงการโทรพิเศษbasename
ephemient

45
"การแก้ปัญหา" นี้ไม่ทำงานหากไฟล์ไม่มีส่วนขยาย - แทนชื่อไฟล์ทั้งหมดเป็นผลลัพธ์ซึ่งค่อนข้างไม่ดีเมื่อพิจารณาว่าไฟล์ที่ไม่มีนามสกุลนั้นอยู่ทั่วไปทุกหนทุกแห่ง
nccc

43
extension=$([[ "$filename" = *.* ]] && echo ".${filename##*.}" || echo '')แก้ไขการจัดการกับชื่อไฟล์โดยไม่มีนามสกุล: โปรดทราบว่าถ้านามสกุลเป็นปัจจุบันก็จะถูกส่งกลับรวมทั้งเริ่มต้นเช่น. .txt
mklement0

683
~% FILE="example.tar.gz"

~% echo "${FILE%%.*}"
example

~% echo "${FILE%.*}"
example.tar

~% echo "${FILE#*.}"
tar.gz

~% echo "${FILE##*.}"
gz

สำหรับรายละเอียดเพิ่มเติมดูการขยายพารามิเตอร์เชลล์ในคู่มือ Bash


22
คุณ (อาจตั้งใจ) ถามคำถามที่ยอดเยี่ยมว่าจะทำอย่างไรถ้าส่วน "นามสกุล" ของชื่อไฟล์มี 2 จุดอยู่ในนั้นเช่นเดียวกับใน. tar.gz ... ฉันไม่เคยพิจารณาปัญหานั้นและฉันสงสัยว่ามันเป็น ไม่สามารถแก้ไขได้โดยไม่ทราบนามสกุลไฟล์ที่ถูกต้องที่เป็นไปได้ทั้งหมด
rmeador

8
ทำไมไม่แก้ไขได้? ในตัวอย่างของฉันควรพิจารณาว่าไฟล์มีสองส่วนขยายไม่ใช่นามสกุลที่มีสองจุด คุณจัดการกับส่วนขยายทั้งสองแยกจากกัน
Juliano

22
มันไม่สามารถแก้ไขได้ด้วยคำศัพท์คุณจะต้องตรวจสอบประเภทของไฟล์ พิจารณาถ้าคุณมีเกมที่เรียกว่าdinosaurs.in.tarและคุณ gzipped มันdinosaurs.in.tar.gz:)
Porges

11
สิ่งนี้จะซับซ้อนมากขึ้นหากคุณผ่านเส้นทางแบบเต็ม หนึ่งในของฉันมี '.' ในไดเรกทอรีที่อยู่ตรงกลางของพา ธ แต่ไม่มีในชื่อไฟล์ ตัวอย่าง "a / bc / d / e / filename" จะปิดท้าย ".c / d / e / filename"
Walt Sellers

6
เห็นได้ชัดว่าไม่มีx.tar.gzนามสกุลgzและชื่อไฟล์ก็x.tarคือ ไม่มีสิ่งเช่นนามสกุลสอง ฉันค่อนข้างแน่ใจว่า boost :: ระบบไฟล์จัดการกับมันอย่างนั้น (เส้นทางแยก, change_extension ... ) และพฤติกรรมของมันขึ้นอยู่กับหลามถ้าฉันไม่ผิด
v.oddou

430

โดยปกติแล้วคุณจะรู้จักส่วนขยายดังนั้นคุณอาจต้องการใช้:

basename filename .extension

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

basename /path/to/dir/filename.txt .txt

และเราได้รับ

filename

60
อาร์กิวเมนต์ที่สองbasenameนั้นค่อนข้างเปิดตา ty ใจดีครับ / ท่านผู้หญิง :)
akaIDIOT

10
และวิธีแยกส่วนขยายโดยใช้เทคนิคนี้ ;) โอ้รอ! เราไม่รู้จริง ๆ ล่วงหน้า
Tomasz Gandor

3
สมมติว่าคุณมีไดเรกทอรีซิปที่ปลายทั้งที่มีหรือ.zip .ZIPมีวิธีที่คุณสามารถทำอะไรบางอย่างเช่นbasename $file {.zip,.ZIP}?
Dennis

8
แม้ว่านี่จะตอบคำถาม OPs เพียงบางส่วน แต่จะตอบคำถามที่ฉันพิมพ์ลงใน google :-) เนียนมาก!
sudo ทำการติดตั้ง

1
ใช้งานง่ายและ POSIX
gpanda

146

คุณสามารถใช้ความมหัศจรรย์ของการขยายพารามิเตอร์ POSIX:

bash-3.2$ FILENAME=somefile.tar.gz
bash-3.2$ echo "${FILENAME%%.*}"
somefile
bash-3.2$ echo "${FILENAME%.*}"
somefile.tar

มีข้อแม้ว่าถ้าชื่อไฟล์ของคุณอยู่ในรูปแบบ./somefile.tar.gzแล้วecho ${FILENAME%%.*}ละโมบจะลบการจับคู่ที่ยาวที่สุดไปยัง.และคุณจะมีสตริงว่าง

(คุณสามารถแก้ไขได้ด้วยตัวแปรชั่วคราว:

FULL_FILENAME=$FILENAME
FILENAME=${FULL_FILENAME##*/}
echo ${FILENAME%%.*}

)


เว็บไซต์นี้อธิบายเพิ่มเติม

${variable%pattern}
  Trim the shortest match from the end
${variable##pattern}
  Trim the longest match from the beginning
${variable%%pattern}
  Trim the longest match from the end
${variable#pattern}
  Trim the shortest match from the beginning

5
ง่ายกว่าคำตอบของโจอาคิม แต่ฉันต้องค้นหาการแทนที่ตัวแปร POSIX เสมอ นอกจากนี้วิ่งต่อ Max OSX ที่cutไม่ได้--complementและไม่ได้sed -r
jwadsack

72

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

#!/bin/bash
for fullpath in "$@"
do
    filename="${fullpath##*/}"                      # Strip longest match of */ from start
    dir="${fullpath:0:${#fullpath} - ${#filename}}" # Substring from 0 thru pos of filename
    base="${filename%.[^.]*}"                       # Strip shortest match of . plus at least one non-dot char from end
    ext="${filename:${#base} + 1}"                  # Substring from len of base thru end
    if [[ -z "$base" && -n "$ext" ]]; then          # If we have an extension and no base, it's really the base
        base=".$ext"
        ext=""
    fi

    echo -e "$fullpath:\n\tdir  = \"$dir\"\n\tbase = \"$base\"\n\text  = \"$ext\""
done

และนี่คือตัวอย่างบางส่วน:

$ basename-and-extension.sh / / home / me / / home / me / file /home/me/file.tar /home/me/file.tar.gz /home/me/.hidden / home / me / .hidden.tar / home / me / ..
/:
    dir = "/"
    base = ""
    ext = ""
/ home / ME /:
    dir = "/ home / me /"
    base = ""
    ext = ""
/ home / ME / ไฟล์:
    dir = "/ home / me /"
    base = "ไฟล์"
    ext = ""
/home/me/file.tar:
    dir = "/ home / me /"
    base = "ไฟล์"
    ext = "tar"
/home/me/file.tar.gz:
    dir = "/ home / me /"
    base = "file.tar"
    ext = "gz"
/home/me/.hidden:
    dir = "/ home / me /"
    base = ".hidden"
    ext = ""
/home/me/.hidden.tar:
    dir = "/ home / me /"
    base = ".hidden"
    ext = "tar"
/ home / ME / .. :
    dir = "/ home / me /"
    base = ".. "
    ext = ""
.:
    dir = ""
    base = "."
    ext = ""

2
แทนการที่ผมเคยเห็นบ่อยdir="${fullpath:0:${#fullpath} - ${#filename}}" dir="${fullpath%$filename}"เขียนง่ายกว่า ไม่แน่ใจว่ามีความแตกต่างของความเร็วจริงหรือ gotchas
dubiousjim

2
สิ่งนี้ใช้ #! / bin / bash ซึ่งเกือบจะผิดเสมอ ต้องการ #! / bin / sh ถ้าเป็นไปได้หรือ #! / usr / bin / env ทุบตีถ้าไม่
คนดี

@ บุคคลที่ดี: ฉันไม่รู้ว่ามันผิดเกือบทุกครั้ง: which bash -> /bin/bash; บางทีมันเป็น distro ของคุณ?
vol7ron

2
@ vol7ron - ในการทุบตี distros จำนวนมากอยู่ใน / usr / local / bin / bash บน OSX หลายคนติดตั้ง bash ที่อัปเดตใน / opt / local / bin / bash เช่น / bin / bash ผิดและควรใช้ env เพื่อค้นหา ยิ่งไปกว่านั้นคือการใช้ / bin / sh และ POSIX สร้าง ยกเว้นบน solaris นี่เป็น POSIX เชลล์
คนดี

2
@ GoodPerson แต่ถ้าคุณพอใจกับการทุบตีทำไมใช้ sh? ไม่เหมือนที่พูดทำไมใช้ Perl เมื่อคุณสามารถใช้ sh?
vol7ron

46

basenameคุณสามารถใช้

ตัวอย่าง:

$ basename foo-bar.tar.gz .tar.gz
foo-bar

คุณไม่จำเป็นต้องให้ basename มีนามสกุลนั้นจะต้องถูกลบออก แต่ถ้าคุณมักจะดำเนินการtarด้วยแล้วคุณจะรู้ส่วนขยายนี้จะ-z.tar.gz

สิ่งนี้ควรทำในสิ่งที่คุณต้องการ:

tar -zxvf $1
cd $(basename $1 .tar.gz)

2
ฉันคิดว่าcd $(basename $1 .tar.gz)ใช้ได้กับไฟล์. gz แต่ในคำถามที่เขาพูดถึงArchive files have several extensions: tar.gz, tat.xz, tar.bz2
SS Hegde

โทมิโปโพสต์สิ่งเดียวกันเมื่อ 2 ปีก่อน
phil294

สวัสดี Blauhirn, wauw นี่เป็นคำถามเก่า ฉันคิดว่ามีบางอย่างเกิดขึ้นกับวันที่ ฉันจำการตอบคำถามได้ไม่นานหลังจากถูกถามและที่นั่นมีคำตอบเพียงไม่กี่ข้อ เป็นไปได้หรือไม่ว่าคำถามนั้นถูกรวมเข้ากับคำถามอื่นทำเช่นนั้นหรือไม่
Bjarke Freund-Hansen

ใช่ฉันจำได้ถูกต้อง ฉันเดิมตอบคำถามนี้stackoverflow.com/questions/14703318/ …ในวันเดียวกันกับที่ถาม 2 ปีต่อมามันรวมเข้ากับอันนี้ ฉันแทบจะไม่ถูกตำหนิสำหรับคำตอบที่ซ้ำกันเมื่อคำตอบของฉันถูกย้ายในลักษณะนี้
Bjarke Freund-Hansen

37
pax> echo a.b.js | sed 's/\.[^.]*$//'
a.b
pax> echo a.b.js | sed 's/^.*\.//'
js

ทำงานได้ดีดังนั้นคุณสามารถใช้:

pax> FILE=a.b.js
pax> NAME=$(echo "$FILE" | sed 's/\.[^.]*$//')
pax> EXTENSION=$(echo "$FILE" | sed 's/^.*\.//')
pax> echo $NAME
a.b
pax> echo $EXTENSION
js

คำสั่งโดยวิธีการทำงานดังต่อไปนี้

คำสั่งสำหรับNAMEแทนที่"."อักขระตามด้วยจำนวน"."อักขระที่ไม่ใช่อักขระใด ๆจนถึงจุดสิ้นสุดของบรรทัดโดยที่ไม่มีสิ่งใด (เช่นจะลบทุกอย่างออกจากรอบชิงชนะเลิศ"."ไปยังจุดสิ้นสุดของบรรทัดรวม) นี่คือการทดแทนที่ไม่โลภโดยใช้กลอุบาย regex

คำสั่งสำหรับEXTENSIONแทนที่จำนวนอักขระใด ๆ ตามด้วย"."อักขระที่จุดเริ่มต้นของบรรทัดโดยไม่มีสิ่งใด (กล่าวคือจะลบทุกอย่างจากจุดเริ่มต้นของบรรทัดไปจนถึงจุดสุดท้ายรวมอยู่ด้วย) นี่เป็นการทดแทนโลภซึ่งเป็นการกระทำเริ่มต้น


ตัวแบ่งนี้สำหรับไฟล์ที่ไม่มีส่วนขยายเนื่องจากจะพิมพ์เหมือนกันสำหรับชื่อและส่วนขยาย ดังนั้นฉันจึงใช้sed 's,\.[^\.]*$,,'ชื่อและsed 's,.*\.,., ;t ;g'ส่วนขยาย (ใช้คำสั่งatypical testและgetคำสั่งพร้อมกับsubstituteคำสั่งทั่วไป)
hIpPy

32

เมลเลนเขียนความคิดเห็นในบล็อกโพสต์:

การใช้ Bash ${file%.*}จะต้องมีชื่อไฟล์โดยไม่มีนามสกุลและ${file##*.}เพื่อให้ได้นามสกุลเท่านั้น นั่นคือ,

file="thisfile.txt"
echo "filename: ${file%.*}"
echo "extension: ${file##*.}"

ขาออก:

filename: thisfile
extension: txt


29

ไม่จำเป็นต้องรำคาญกับawkหรือsedหรือแม้กระทั่งperlสำหรับงานนี้ง่าย มี pure-Bash, - os.path.splitext()โซลูชันที่เข้ากันได้ซึ่งใช้การขยายพารามิเตอร์เท่านั้น

การดำเนินการอ้างอิง

เอกสารของos.path.splitext(path):

แยกพา ธ ชื่อพา ธ เป็นคู่(root, ext)เช่นนั้นroot + ext == pathและextจะว่างเปล่าหรือเริ่มต้นด้วยจุดและมีอย่างน้อยหนึ่งช่วงเวลา ช่วงเวลานำในชื่อฐานจะถูกละเว้น ผลตอบแทนsplitext('.cshrc')('.cshrc', '')

รหัสหลาม:

root, ext = os.path.splitext(path)

การใช้งาน Bash

เคารพช่วงเวลาชั้นนำ

root="${path%.*}"
ext="${path#"$root"}"

ละเว้นช่วงเวลานำ

root="${path#.}";root="${path%"$root"}${root%.*}"
ext="${path#"$root"}"

การทดสอบ

ต่อไปนี้เป็นกรณีทดสอบสำหรับการละเว้นการใช้งานช่วงเวลานำซึ่งควรตรงกับการใช้งานการอ้างอิงของ Python ในทุกอินพุต

|---------------|-----------|-------|
|path           |root       |ext    |
|---------------|-----------|-------|
|' .txt'        |' '        |'.txt' |
|' .txt.txt'    |' .txt'    |'.txt' |
|' txt'         |' txt'     |''     |
|'*.txt.txt'    |'*.txt'    |'.txt' |
|'.cshrc'       |'.cshrc'   |''     |
|'.txt'         |'.txt'     |''     |
|'?.txt.txt'    |'?.txt'    |'.txt' |
|'\n.txt.txt'   |'\n.txt'   |'.txt' |
|'\t.txt.txt'   |'\t.txt'   |'.txt' |
|'a b.txt.txt'  |'a b.txt'  |'.txt' |
|'a*b.txt.txt'  |'a*b.txt'  |'.txt' |
|'a?b.txt.txt'  |'a?b.txt'  |'.txt' |
|'a\nb.txt.txt' |'a\nb.txt' |'.txt' |
|'a\tb.txt.txt' |'a\tb.txt' |'.txt' |
|'txt'          |'txt'      |''     |
|'txt.pdf'      |'txt'      |'.pdf' |
|'txt.tar.gz'   |'txt.tar'  |'.gz'  |
|'txt.txt'      |'txt'      |'.txt' |
|---------------|-----------|-------|

ผลการทดสอบ

การทดสอบทั้งหมดผ่าน


2
ไม่ชื่อไฟล์ฐานสำหรับtext.tar.gzควรเป็นtextและนามสกุลควรเป็น.tar.gz
frederick99

2
@ frederick99 ดังที่ฉันได้กล่าวไปแล้วว่าการแก้ปัญหาตรงกับการนำไปใช้os.path.splitextใน Python การใช้งานนั้นมีเหตุผลสำหรับอินพุตที่อาจขัดแย้งหรือไม่เป็นอีกหัวข้อหนึ่ง
Cyker

เครื่องหมายคำพูดในรูปแบบ ( "$root") ทำงานอย่างไร จะเกิดอะไรขึ้นถ้าพวกเขาถูกละไว้ (ฉันไม่พบเอกสารใด ๆ เกี่ยวกับเรื่องนี้) นอกจากนี้ยังมีชื่อไฟล์ที่จัดการกับ*หรือ?ในพวกเขาอย่างไร
ymett

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

คำตอบที่ยอดเยี่ยม! ฉันจะแนะนำตัวแปรที่เรียบง่ายกว่าเล็กน้อยสำหรับการคำนวณรูท: root="${path#?}";root="${path::1}${root%.*}"- จากนั้นดำเนินการเดียวกันเพื่อแยกส่วนขยาย
Maëlan

26

คุณสามารถใช้cutคำสั่งเพื่อลบสองนามสกุลสุดท้าย ( ".tar.gz"ส่วน):

$ echo "foo.tar.gz" | cut -d'.' --complement -f2-
foo

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

$ echo "mpc-1.0.1.tar.gz" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'
mpc-1.0.1

มันทำงานได้โดยการลบส่วนขยาย (ตัวเลขและตัวอักษร) สองตัวสุดท้ายโดยไม่มีเงื่อนไข

[อัปเดตอีกครั้งหลังจากความคิดเห็นจาก Anders Lindahl]


4
ใช้ได้เฉพาะในกรณีที่ชื่อไฟล์ / พา ธ ไม่มีจุดอื่น ๆ : echo "mpc-1.0.1.tar.gz" | ตัด -d '.' --complement -f2- สร้าง "mpc-1" (เพียงแค่ 2 ฟิลด์แรกหลังจากคั่นด้วย)
Clayton Hughes

@ClaytonHughes คุณถูกต้องและฉันควรทดสอบให้ดีกว่านี้ เพิ่มโซลูชันอื่น
โปรแกรมเมอร์บางคนเพื่อน

นิพจน์ sed ควรใช้$เพื่อตรวจสอบว่าส่วนขยายที่ตรงกันอยู่ที่ส่วนท้ายของชื่อไฟล์ มิฉะนั้นชื่อไฟล์เช่นi.like.tar.gz.files.tar.bz2อาจก่อให้เกิดผลลัพธ์ที่ไม่คาดคิด
Anders Lindahl

@AndersLindahl มันจะยังคงอยู่หากคำสั่งของส่วนขยายนั้นตรงกันข้ามกับsedคำสั่งซื้อในเครือ ถึงแม้จะมี$ในตอนท้ายชื่อไฟล์เช่นmpc-1.0.1.tar.bz2.tar.gzจะเอาทั้งสองแล้ว.tar.gz .tar.bz2
โปรแกรมเมอร์บางคนเพื่อน

$ echo "foo.tar.gz" | ตัด -d '.' -f2- โดยไม่ต้อง - การใช้งานจะได้รับรายการแยก 2 ถึงจุดสิ้นสุดของสตริง $ echo "foo.tar.gz" | ตัด -d '.' -f2- tar.gz
Gene Black

23

ต่อไปนี้เป็นคำแนะนำทางเลือกอื่น ๆ (โดยส่วนใหญ่awk) รวมถึงกรณีการใช้งานขั้นสูงบางอย่างเช่นการแยกหมายเลขเวอร์ชั่นสำหรับแพ็คเกจซอฟต์แวร์

f='/path/to/complex/file.1.0.1.tar.gz'

# Filename : 'file.1.0.x.tar.gz'
    echo "$f" | awk -F'/' '{print $NF}'

# Extension (last): 'gz'
    echo "$f" | awk -F'[.]' '{print $NF}'

# Extension (all) : '1.0.1.tar.gz'
    echo "$f" | awk '{sub(/[^.]*[.]/, "", $0)} 1'

# Extension (last-2): 'tar.gz'
    echo "$f" | awk -F'[.]' '{print $(NF-1)"."$NF}'

# Basename : 'file'
    echo "$f" | awk '{gsub(/.*[/]|[.].*/, "", $0)} 1'

# Basename-extended : 'file.1.0.1.tar'
    echo "$f" | awk '{gsub(/.*[/]|[.]{1}[^.]+$/, "", $0)} 1'

# Path : '/path/to/complex/'
    echo "$f" | awk '{match($0, /.*[/]/, a); print a[0]}'
    # or 
    echo "$f" | grep -Eo '.*[/]'

# Folder (containing the file) : 'complex'
    echo "$f" | awk -F'/' '{$1=""; print $(NF-1)}'

# Version : '1.0.1'
    # Defined as 'number.number' or 'number.number.number'
    echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?'

    # Version - major : '1'
    echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f1

    # Version - minor : '0'
    echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f2

    # Version - patch : '1'
    echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f3

# All Components : "path to complex file 1 0 1 tar gz"
    echo "$f" | awk -F'[/.]' '{$1=""; print $0}'

# Is absolute : True (exit-code : 0)
    # Return true if it is an absolute path (starting with '/' or '~/'
    echo "$f" | grep -q '^[/]\|^~/'

กรณีการใช้งานทั้งหมดกำลังใช้เส้นทางแบบเต็มต้นฉบับเป็นอินพุตโดยไม่ขึ้นอยู่กับผลลัพธ์ระดับกลาง


20

คำตอบที่ได้รับการยอมรับทำงานได้ดีในแบบฉบับกรณีแต่ล้มเหลวในการขอบกรณีกล่าวคือ:

  • สำหรับชื่อไฟล์ที่ไม่มีนามสกุล (เรียกว่าคำต่อท้ายในส่วนที่เหลือของคำตอบนี้) extension=${filename##*.}ส่งคืนชื่อไฟล์อินพุตแทนที่จะเป็นสตริงว่าง
  • extension=${filename##*.}ไม่รวมถึงการเริ่มต้น.ตรงกันข้ามกับการประชุม
    • การเพิ่มระดับสุ่มสี่สุ่มห้า.จะไม่ทำงานกับชื่อไฟล์หากไม่มีคำต่อท้าย
  • filename="${filename%.*}"จะเป็นสตริงว่างเปล่าหากชื่อไฟล์อินพุตเริ่มต้นด้วย.และไม่มี.อักขระเพิ่มเติม(เช่น.bash_profile) - ตรงกันข้ามกับแบบแผน

---------

ดังนั้นความซับซ้อนของการแก้ปัญหาที่แข็งแกร่งซึ่งครอบคลุมทุกกรณีขอบเรียกฟังก์ชั่น - ดูคำนิยามของมันด้านล่าง; มันสามารถกลับทุกชิ้นส่วนของเส้นทาง

ตัวอย่างการโทร:

splitPath '/etc/bash.bashrc' dir fname fnameroot suffix
# -> $dir == '/etc'
# -> $fname == 'bash.bashrc'
# -> $fnameroot == 'bash'
# -> $suffix == '.bashrc'

โปรดทราบว่าข้อโต้แย้งหลังจากเส้นทางการป้อนข้อมูลที่ได้รับการแต่งตั้งอย่างอิสระตัวแปรตำแหน่งชื่อ
ในการข้ามตัวแปรที่ไม่สนใจก่อนหน้านั้นให้ระบุ_(เพื่อใช้ตัวแปรแบบโยนทิ้ง$_) หรือ''; splitPath '/etc/bash.bashrc' _ _ fnameroot extensionเช่นในการสกัดรากชื่อไฟล์และนามสกุลเท่านั้นการใช้งาน


# SYNOPSIS
#   splitPath path varDirname [varBasename [varBasenameRoot [varSuffix]]] 
# DESCRIPTION
#   Splits the specified input path into its components and returns them by assigning
#   them to variables with the specified *names*.
#   Specify '' or throw-away variable _ to skip earlier variables, if necessary.
#   The filename suffix, if any, always starts with '.' - only the *last*
#   '.'-prefixed token is reported as the suffix.
#   As with `dirname`, varDirname will report '.' (current dir) for input paths
#   that are mere filenames, and '/' for the root dir.
#   As with `dirname` and `basename`, a trailing '/' in the input path is ignored.
#   A '.' as the very first char. of a filename is NOT considered the beginning
#   of a filename suffix.
# EXAMPLE
#   splitPath '/home/jdoe/readme.txt' parentpath fname fnameroot suffix
#   echo "$parentpath" # -> '/home/jdoe'
#   echo "$fname" # -> 'readme.txt'
#   echo "$fnameroot" # -> 'readme'
#   echo "$suffix" # -> '.txt'
#   ---
#   splitPath '/home/jdoe/readme.txt' _ _ fnameroot
#   echo "$fnameroot" # -> 'readme'  
splitPath() {
  local _sp_dirname= _sp_basename= _sp_basename_root= _sp_suffix=
    # simple argument validation
  (( $# >= 2 )) || { echo "$FUNCNAME: ERROR: Specify an input path and at least 1 output variable name." >&2; exit 2; }
    # extract dirname (parent path) and basename (filename)
  _sp_dirname=$(dirname "$1")
  _sp_basename=$(basename "$1")
    # determine suffix, if any
  _sp_suffix=$([[ $_sp_basename = *.* ]] && printf %s ".${_sp_basename##*.}" || printf '')
    # determine basename root (filemane w/o suffix)
  if [[ "$_sp_basename" == "$_sp_suffix" ]]; then # does filename start with '.'?
      _sp_basename_root=$_sp_basename
      _sp_suffix=''
  else # strip suffix from filename
    _sp_basename_root=${_sp_basename%$_sp_suffix}
  fi
  # assign to output vars.
  [[ -n $2 ]] && printf -v "$2" "$_sp_dirname"
  [[ -n $3 ]] && printf -v "$3" "$_sp_basename"
  [[ -n $4 ]] && printf -v "$4" "$_sp_basename_root"
  [[ -n $5 ]] && printf -v "$5" "$_sp_suffix"
  return 0
}

test_paths=(
  '/etc/bash.bashrc'
  '/usr/bin/grep'
  '/Users/jdoe/.bash_profile'
  '/Library/Application Support/'
  'readme.new.txt'
)

for p in "${test_paths[@]}"; do
  echo ----- "$p"
  parentpath= fname= fnameroot= suffix=
  splitPath "$p" parentpath fname fnameroot suffix
  for n in parentpath fname fnameroot suffix; do
    echo "$n=${!n}"
  done
done

รหัสทดสอบที่ฝึกใช้งานฟังก์ชั่น:

test_paths=(
  '/etc/bash.bashrc'
  '/usr/bin/grep'
  '/Users/jdoe/.bash_profile'
  '/Library/Application Support/'
  'readme.new.txt'
)

for p in "${test_paths[@]}"; do
  echo ----- "$p"
  parentpath= fname= fnameroot= suffix=
  splitPath "$p" parentpath fname fnameroot suffix
  for n in parentpath fname fnameroot suffix; do
    echo "$n=${!n}"
  done
done

ผลลัพธ์ที่คาดหวัง - บันทึกตัวเรือนขอบ:

  • ชื่อไฟล์ที่ไม่มีคำต่อท้าย
  • ชื่อไฟล์ที่ขึ้นต้นด้วย.( ไม่ถือว่าเป็นจุดเริ่มต้นของคำต่อท้าย)
  • เส้นทางอินพุตที่ลงท้ายด้วย/(ต่อท้าย/ถูกละเว้น)
  • เส้นทางเข้าที่เป็นชื่อไฟล์เท่านั้น ( .จะถูกส่งกลับเป็นเส้นทางหลัก)
  • ชื่อไฟล์ที่มี.โทเค็นที่มีการแก้ไขมากกว่า(เฉพาะอันสุดท้ายถือว่าเป็นคำต่อท้าย):
----- /etc/bash.bashrc
parentpath=/etc
fname=bash.bashrc
fnameroot=bash
suffix=.bashrc
----- /usr/bin/grep
parentpath=/usr/bin
fname=grep
fnameroot=grep
suffix=
----- /Users/jdoe/.bash_profile
parentpath=/Users/jdoe
fname=.bash_profile
fnameroot=.bash_profile
suffix=
----- /Library/Application Support/
parentpath=/Library
fname=Application Support
fnameroot=Application Support
suffix=
----- readme.new.txt
parentpath=.
fname=readme.new.txt
fnameroot=readme.new
suffix=.txt

19

โซลูชันที่เล็กที่สุดและง่ายที่สุด (ในบรรทัดเดียว) คือ:

$ file=/blaabla/bla/blah/foo.txt
echo $(basename ${file%.*}) # foo

นั่นคือการใช้งานที่ไร้ประโยชน์ของ echoโดยทั่วไปแล้วecho $(command)จะเขียนได้ดีกว่าcommandเว้นแต่คุณจะต้องการเปลือกเพื่อดำเนินการโทเค็นช่องว่างและการขยายตัวสัญลักษณ์ในผลลัพธ์จากcommandก่อนที่จะแสดงผล คำถาม: อะไรคือผลลัพธ์ของecho $(echo '*')(และถ้านั่นคือสิ่งที่คุณต้องการจริงๆคุณต้องการจริงๆecho *)
tripleee

@triplee ฉันไม่ได้ใช้ echoคำสั่งเลย ฉันเพิ่งใช้มันเพื่อสาธิตผลลัพธ์fooที่ปรากฏในบรรทัดที่ 3 ซึ่งเป็นผลลัพธ์ของบรรทัดที่ 2
Ron

แต่เพียง basename "${file%.*}"จะทำเช่นเดียวกัน; คุณกำลังใช้การทดแทนคำสั่งเพื่อดักจับเอาต์พุตของมันเฉพาะกับechoเอาต์พุตเดียวกันนั้นทันที (โดยไม่ต้องอ้างผลที่แตกต่างกันในนาม แต่ที่เกี่ยวข้องแทบจะมากน้อยคุณลักษณะที่นี่.)
tripleee

ยังbasename "$file" .txtหลีกเลี่ยงความซับซ้อนของการทดแทนพารามิเตอร์
tripleee

1
@Ron อ่านความคิดเห็นแรกของเขาก่อนที่จะกล่าวหาว่าเขาเสียเวลาของเรา
frederick99

14

ฉันคิดว่าถ้าคุณต้องการชื่อไฟล์คุณสามารถลอง:

FULLPATH=/usr/share/X11/xorg.conf.d/50-synaptics.conf

# Remove all the prefix until the "/" character
FILENAME=${FULLPATH##*/}

# Remove all the prefix until the "." character
FILEEXTENSION=${FILENAME##*.}

# Remove a suffix, in our case, the filename. This will return the name of the directory that contains this file.
BASEDIRECTORY=${FULLPATH%$FILENAME}

echo "path = $FULLPATH"
echo "file name = $FILENAME"
echo "file extension = $FILEEXTENSION"
echo "base directory = $BASEDIRECTORY"

และนั่นคือทั้งหมด = D


แค่ต้องการเบส :) ขอบคุณ!
Carlos Ricardo

12

คุณสามารถบังคับให้ตัดเพื่อแสดงฟิลด์ทั้งหมดและฟิลด์ที่ตามมาเพิ่ม-ลงในหมายเลขฟิลด์

NAME=`basename "$FILE"`
EXTENSION=`echo "$NAME" | cut -d'.' -f2-`

ดังนั้นหากไฟล์คือeth0.pcap.gzส่วนขยายจะเป็นpcap.gz

ด้วยตรรกะเดียวกันคุณสามารถดึงชื่อไฟล์โดยใช้ '-' โดยมีการตัดดังนี้:

NAME=`basename "$FILE" | cut -d'.' -f-1`

วิธีนี้ใช้ได้แม้กับชื่อไฟล์ที่ไม่มีส่วนขยายใด ๆ


8

การรับรู้ไฟล์มายากล

นอกจากคำตอบที่ดีมากมายสำหรับคำถาม Stack Overflow ฉันต้องการเพิ่ม:

ภายใต้ Linux และ unixen อื่น ๆ จะมีคำสั่งเวทชื่อfileซึ่งทำการตรวจจับประเภทไฟล์โดยการวิเคราะห์ไฟล์ไบต์แรก นี่เป็นเครื่องมือที่เก่ามากใช้สำหรับเซิร์ฟเวอร์การพิมพ์เริ่มต้น (หากไม่ได้สร้างไว้สำหรับ ... ฉันไม่แน่ใจเกี่ยวกับเรื่องนี้)

file myfile.txt
myfile.txt: UTF-8 Unicode text

file -b --mime-type myfile.txt
text/plain

ส่วนขยายมาตรฐานสามารถพบได้ใน/etc/mime.types(บนเดสก์ท็อปDebian GNU / Linux ของฉันดูman fileและman mime.typesบางทีคุณต้องติดตั้งfileยูทิลิตี้และmime-supportแพ็คเกจ):

grep $( file -b --mime-type myfile.txt ) </etc/mime.types
text/plain      asc txt text pot brf srt

คุณสามารถสร้าง ฟังก์ชั่นสำหรับการกำหนดนามสกุลที่ถูกต้อง มีตัวอย่างเล็กน้อย (ไม่สมบูรณ์):

file2ext() {
    local _mimetype=$(file -Lb --mime-type "$1") _line _basemimetype
    case ${_mimetype##*[/.-]} in
        gzip | bzip2 | xz | z )
            _mimetype=${_mimetype##*[/.-]}
            _mimetype=${_mimetype//ip}
            _basemimetype=$(file -zLb --mime-type "$1")
            ;;
        stream )
            _mimetype=($(file -Lb "$1"))
            [ "${_mimetype[1]}" = "compressed" ] &&
                _basemimetype=$(file -b --mime-type - < <(
                        ${_mimetype,,} -d <"$1")) ||
                _basemimetype=${_mimetype,,}
            _mimetype=${_mimetype,,}
            ;;
        executable )  _mimetype='' _basemimetype='' ;;
        dosexec )     _mimetype='' _basemimetype='exe' ;;
        shellscript ) _mimetype='' _basemimetype='sh' ;;
        * )
            _basemimetype=$_mimetype
            _mimetype=''
            ;;
    esac
    while read -a _line ;do
        if [ "$_line" == "$_basemimetype" ] ;then
            [ "$_line[1]" ] &&
                _basemimetype=${_line[1]} ||
                _basemimetype=${_basemimetype##*[/.-]}
            break
        fi
        done </etc/mime.types
    case ${_basemimetype##*[/.-]} in
        executable ) _basemimetype='' ;;
        shellscript ) _basemimetype='sh' ;;
        dosexec ) _basemimetype='exe' ;;
        * ) ;;
    esac
    [ "$_mimetype" ] && [ "$_basemimetype" != "$_mimetype" ] &&
      printf ${2+-v} $2 "%s.%s" ${_basemimetype##*[/.-]} ${_mimetype##*[/.-]} ||
      printf ${2+-v} $2 "%s" ${_basemimetype##*[/.-]}
}

ฟังก์ชันนี้สามารถตั้งค่าตัวแปร Bash ที่สามารถใช้ได้ในภายหลัง:

(นี่เป็นแรงบันดาลใจจากคำตอบที่ถูกต้องของ @Petesh):

filename=$(basename "$fullfile")
filename="${filename%.*}"
file2ext "$fullfile" extension

echo "$fullfile -> $filename . $extension"

8

stuff.tar.gzตกลงดังนั้นถ้าผมเข้าใจถูกต้องปัญหาที่นี่คือวิธีการที่จะได้รับชื่อและนามสกุลเต็มรูปแบบของไฟล์ที่มีนามสกุลหลายเช่น

สิ่งนี้ใช้ได้กับฉัน:

fullfile="stuff.tar.gz"
fileExt=${fullfile#*.}
fileName=${fullfile%*.$fileExt}

สิ่งนี้จะให้คุณstuffเป็นชื่อไฟล์และ.tar.gzเป็นส่วนขยาย มันใช้งานได้กับจำนวนส่วนขยายใด ๆ รวมถึง 0 หวังว่านี่จะช่วยได้สำหรับทุกคนที่มีปัญหาเดียวกัน =)


ผลที่ถูกต้อง (ตามos.path.splitextซึ่งเป็นสิ่งที่ต้องการ OP) ('stuff.tar', '.gz')เป็น
Cyker

6

ฉันใช้สคริปต์ต่อไปนี้

$ echo "foo.tar.gz"|rev|cut -d"." -f3-|rev
foo

มันไม่ได้มีประสิทธิภาพเลย ใช้ส้อมหลายครั้งซึ่งไม่จำเป็นเนื่องจากการดำเนินการนี้สามารถทำได้ใน Bash บริสุทธิ์โดยไม่ต้องใช้คำสั่งภายนอกและการฟอร์ก
codeforester

5
$ F = "text file.test.txt"  
$ echo ${F/*./}  
txt  

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

โดยปกติวิธีการนี้ใช้ไม่ได้กับไฟล์. tar.gz อย่างไรก็ตามสามารถจัดการได้ในกระบวนการสองขั้นตอน หากส่วนขยายคือ gz ให้ตรวจสอบอีกครั้งเพื่อดูว่ามีส่วนขยาย tar หรือไม่


5

วิธีการแยกชื่อไฟล์และนามสกุลในปลา :

function split-filename-extension --description "Prints the filename and extension"
  for file in $argv
    if test -f $file
      set --local extension (echo $file | awk -F. '{print $NF}')
      set --local filename (basename $file .$extension)
      echo "$filename $extension"
    else
      echo "$file is not a valid file"
    end
  end
end

Caveats:แยกที่จุดสุดท้ายซึ่งทำงานได้ดีสำหรับชื่อไฟล์ที่มีจุดอยู่ในจุดนั้น แต่ไม่เหมาะสำหรับส่วนขยายที่มีจุดในจุดนั้น ดูตัวอย่างด้านล่าง

การใช้งาน:

$ split-filename-extension foo-0.4.2.zip bar.tar.gz
foo-0.4.2 zip  # Looks good!
bar.tar gz  # Careful, you probably want .tar.gz as the extension.

อาจมีวิธีที่ดีกว่าในการทำเช่นนี้ อย่าลังเลที่จะแก้ไขคำตอบของฉันเพื่อปรับปรุง


หากมีส่วนขยายจำนวน จำกัด ที่คุณจะติดต่อด้วยและคุณรู้ส่วนขยายทั้งหมดให้ลองทำสิ่งนี้:

switch $file
  case *.tar
    echo (basename $file .tar) tar
  case *.tar.bz2
    echo (basename $file .tar.bz2) tar.bz2
  case *.tar.gz
    echo (basename $file .tar.gz) tar.gz
  # and so on
end

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


4

นี่คือรหัสกับAWK สามารถทำได้ง่ายขึ้น แต่ฉันไม่เก่งใน AWK

filename$ ls
abc.a.txt  a.b.c.txt  pp-kk.txt
filename$ find . -type f | awk -F/ '{print $2}' | rev | awk -F"." '{$1="";print}' | rev | awk 'gsub(" ",".") ,sub(".$", "")'
abc.a
a.b.c
pp-kk
filename$ find . -type f | awk -F/ '{print $2}' | awk -F"." '{print $NF}'
txt
txt
txt

คุณไม่จำเป็นต้องมีคำสั่ง awk แรกในตัวอย่างสุดท้ายใช่ไหม
BHSPitMonkey

คุณสามารถหลีกเลี่ยงท่อ Awk จะ Awk split()ด้วยการทำอีก awk -F / '{ n=split($2, a, "."); print a[n] }' uses / `เป็นตัวคั่นระดับบนสุด แต่จากนั้นแยกฟิลด์ที่สองบน.และพิมพ์องค์ประกอบสุดท้ายจากอาร์เรย์ใหม่
tripleee

4

เพียงใช้ ${parameter%word}

ในกรณีของคุณ:

${FILE%.*}

หากคุณต้องการที่จะทดสอบมันทำงานต่อไปนี้ทั้งหมดและเพียงแค่ลบส่วนขยาย:

FILE=abc.xyz; echo ${FILE%.*};
FILE=123.abc.xyz; echo ${FILE%.*};
FILE=abc; echo ${FILE%.*};

2
ทำไมต้องลงคะแนน? มันยังมีประโยชน์แม้ว่าจะไม่ควรมีช่องว่างรอบ ๆ=สัญญาณ
SilverWolf - Reinstate Monica

1
ใช้งานได้ดี ขอบคุณ! (ตอนนี้มันไม่มีช่องว่างรอบเครื่องหมายเท่ากับถ้านั่นคือสาเหตุที่มันถูกลดระดับลง)
อเล็กซ์ S.

3

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

filename=$(basename ${fullname%.*})

ไม่ทำงานสำหรับฉัน: "basename: ตัวถูกดำเนินการที่ขาดหายไปลอง 'basename --help' สำหรับข้อมูลเพิ่มเติม"
helmy

แปลกคุณแน่ใจหรือไม่ว่าคุณใช้ Bash? ในกรณีของฉันทั้งสองเวอร์ชัน 3.2.25 (CentOS เก่า) และ 4.3.30 (Debian Jessie) มันใช้งานได้อย่างไม่มีที่ติ
cvr

อาจมีช่องว่างในชื่อไฟล์หรือไม่ ลองใช้filename="$(basename "${fullname%.*}")"
Adrian

อาร์กิวเมนต์ที่สองbasenameเป็นทางเลือก แต่ระบุส่วนขยายที่จะตัดออก การทดแทนอาจยังคงมีประโยชน์ แต่อาจbasenameไม่จริงเนื่องจากคุณสามารถทำการทดแทนทั้งหมดเหล่านี้ด้วยเชลล์บิลด์ได้
tripleee

3

ส่วนใหญ่มาจาก @ mklement0 ยอดเยี่ยมและเต็มไปด้วยการสุ่มbashisms ที่มีประโยชน์- เช่นเดียวกับคำตอบอื่น ๆ สำหรับคำถามนี้ / คำถามอื่น ๆ / "อินเทอร์เน็ตที่แย่" ... ฉันห่อมันทั้งหมดเล็กน้อยฟังก์ชั่นที่ใช้ซ้ำได้สำหรับ (หรือของคุณ) .bash_profileที่ดูแลสิ่งที่ (ฉันพิจารณา) ควรเป็นรุ่นที่มีประสิทธิภาพมากขึ้นของdirname/ basename/ สิ่งที่คุณมี ..

function path { SAVEIFS=$IFS; IFS=""   # stash IFS for safe-keeping, etc.
    [[ $# != 2 ]] && echo "usage: path <path> <dir|name|fullname|ext>" && return    # demand 2 arguments
    [[ $1 =~ ^(.*/)?(.+)?$ ]] && {     # regex parse the path
        dir=${BASH_REMATCH[1]}
        file=${BASH_REMATCH[2]}
        ext=$([[ $file = *.* ]] && printf %s ${file##*.} || printf '')
        # edge cases for extensionless files and files like ".nesh_profile.coffee"
        [[ $file == $ext ]] && fnr=$file && ext='' || fnr=${file:0:$((${#file}-${#ext}))}
        case "$2" in
             dir) echo      "${dir%/*}"; ;;
            name) echo      "${fnr%.*}"; ;;
        fullname) echo "${fnr%.*}.$ext"; ;;
             ext) echo           "$ext"; ;;
        esac
    }
    IFS=$SAVEIFS
}     

ตัวอย่างการใช้ ...

SOMEPATH=/path/to.some/.random\ file.gzip
path $SOMEPATH dir        # /path/to.some
path $SOMEPATH name       # .random file
path $SOMEPATH ext        # gzip
path $SOMEPATH fullname   # .random file.gzip                     
path gobbledygook         # usage: -bash <path> <dir|name|fullname|ext>

1
ทำได้ดีมาก คำแนะนำเล็ก ๆ น้อย ๆ : - คุณดูเหมือนจะไม่พึ่งพา$IFSเลย (และถ้าคุณเป็นคุณสามารถใช้localเพื่อ จำกัด ผลของการตั้งค่า) - ดีกว่าที่จะใช้localตัวแปร - ข้อความแสดงข้อผิดพลาดของคุณควรถูกส่งไปที่stderrไม่ใช่stdout(ใช้1>&2) และคุณควรส่งคืนรหัสทางออกที่ไม่เป็นศูนย์ - ดีกว่าที่จะเปลี่ยนชื่อfullnameเป็นbasename(อดีตแนะนำเส้นทางที่มีส่วนประกอบ dir) - nameผนวก a .(จุด) อย่างไม่มีเงื่อนไขแม้ว่าต้นฉบับจะไม่มี คุณก็สามารถใช้basenameยูทิลิตี้ /แต่ทราบว่าจะละเว้นยุติ
mklement0

2

คำตอบง่ายๆ:

หากต้องการขยายคำตอบของตัวแปร POSIXโปรดทราบว่าคุณสามารถทำรูปแบบที่น่าสนใจมากขึ้น ดังนั้นสำหรับรายละเอียดที่นี่คุณสามารถทำสิ่งนี้ได้:

tar -zxvf $1
cd ${1%.tar.*}

ที่จะตัดการเกิดขึ้นครั้งสุดท้ายของ. tar <บางสิ่งบางอย่าง>

โดยทั่วไปหากคุณต้องการลบการเกิดขึ้นครั้งสุดท้ายของ <บางสิ่งบางอย่าง> <บางสิ่ง - อื่น ๆ >จากนั้น

${1.*.*}

ควรทำงานได้ดี

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


มีวิธีที่จะทำให้การแข่งขันเป็นกรณี ๆ หรือไม่?
tonix

2

หากคุณต้องการอนุญาตให้มีส่วนขยายที่ว่างเปล่านี่คือระยะทางสั้นที่สุดที่ฉันสามารถทำได้:

echo 'hello.txt' | sed -r 's/.+\.(.+)|.*/\1/' # EXTENSION
echo 'hello.txt' | sed -r 's/(.+)\..+|(.*)/\1\2/' # FILENAME

บรรทัดแรกอธิบาย: มันตรงกับ PATH.EXT หรืออะไรก็ได้และแทนที่ด้วย EXT หากมีสิ่งใดตรงกันกลุ่ม ext จะไม่ถูกจับ


2

นี่เป็นสิ่งเดียวที่ทำงานสำหรับฉัน:

path='folder/other_folder/file.js'

base=${path##*/}
echo ${base%.*}

>> file

สามารถใช้ในการแก้ไขสตริงได้เช่นกัน แต่น่าเสียดายที่คุณต้องตั้งค่าbaseไว้ล่วงหน้า


1

นี่คืออัลกอริทึมที่ฉันใช้ในการค้นหาชื่อและนามสกุลของไฟล์เมื่อฉันเขียนสคริปต์ Bash เพื่อสร้างชื่อที่ไม่ซ้ำกันเมื่อชื่อขัดแย้งกับการใส่ปลอก

#! /bin/bash 

#
# Finds 
# -- name and extension pairs
# -- null extension when there isn't an extension.
# -- Finds name of a hidden file without an extension
# 

declare -a fileNames=(
  '.Montreal' 
  '.Rome.txt' 
  'Loundon.txt' 
  'Paris' 
  'San Diego.txt'
  'San Francisco' 
  )

echo "Script ${0} finding name and extension pairs."
echo 

for theFileName in "${fileNames[@]}"
do
     echo "theFileName=${theFileName}"  

     # Get the proposed name by chopping off the extension
     name="${theFileName%.*}"

     # get extension.  Set to null when there isn't an extension
     # Thanks to mklement0 in a comment above.
     extension=$([[ "$theFileName" == *.* ]] && echo ".${theFileName##*.}" || echo '')

     # a hidden file without extenson?
     if [ "${theFileName}" = "${extension}" ] ; then
         # hidden file without extension.  Fixup.
         name=${theFileName}
         extension=""
     fi

     echo "  name=${name}"
     echo "  extension=${extension}"
done 

การทดสอบการทำงาน

$ config/Name\&Extension.bash 
Script config/Name&Extension.bash finding name and extension pairs.

theFileName=.Montreal
  name=.Montreal
  extension=
theFileName=.Rome.txt
  name=.Rome
  extension=.txt
theFileName=Loundon.txt
  name=Loundon
  extension=.txt
theFileName=Paris
  name=Paris
  extension=
theFileName=San Diego.txt
  name=San Diego
  extension=.txt
theFileName=San Francisco
  name=San Francisco
  extension=
$ 

FYI: โปรแกรมถอดเสียงที่สมบูรณ์และกรณีทดสอบเพิ่มเติมสามารถดูได้ที่นี่: https://www.dropbox.com/s/4c6m0f2e28a1vxf/avoid-clashes-code.zip?dl=0


จากการแก้ปัญหาทั้งหมดนี้เป็นเพียงคนเดียวที่ส่งกลับสตริงที่ว่างเปล่าเมื่อไฟล์ที่ไม่มีส่วนขยายด้วย:extension=$([[ "$theFileName" == *.* ]] && echo ".${theFileName##*.}" || echo '')
f0nzie

1

ใช้ไฟล์ตัวอย่าง/Users/Jonathan/Scripts/bash/MyScript.shรหัสนี้:

MY_EXT=".${0##*.}"
ME=$(/usr/bin/basename "${0}" "${MY_EXT}")

จะมีผลใน${ME}การเป็นMyScriptและ${MY_EXT}เป็น.sh:


สคริปต์:

#!/bin/bash
set -e

MY_EXT=".${0##*.}"
ME=$(/usr/bin/basename "${0}" "${MY_EXT}")

echo "${ME} - ${MY_EXT}"

การทดสอบบางอย่าง:

$ ./MyScript.sh 
MyScript - .sh

$ bash MyScript.sh
MyScript - .sh

$ /Users/Jonathan/Scripts/bash/MyScript.sh
MyScript - .sh

$ bash /Users/Jonathan/Scripts/bash/MyScript.sh
MyScript - .sh

2
ไม่แน่ใจว่าทำไมจึงมี downvotes มากมาย - จริง ๆ แล้วมันมีประสิทธิภาพมากกว่าคำตอบที่ยอมรับ (ตามหลังมันยังแบ่งด้วยชื่อไฟล์อินพุตโดยไม่มีส่วนขยาย) การใช้พา ธ ที่ชัดเจนถึงbasenameคือบางทีอาจมากไป
mklement0

1

จากคำตอบข้างต้นผู้แปลที่สั้นที่สุดถึงเลียนแบบ Python

file, ext = os.path.splitext(path)

สมมติว่าไฟล์ของคุณมีนามสกุลจริง ๆ คือ

EXT="${PATH##*.}"; FILE=$(basename "$PATH" .$EXT)

ฉันลงคะแนนเรื่องนี้แล้ว ฉันกำลังพิจารณาที่จะลบคำตอบบางคนไม่ชอบมัน
commonpike

basename ไม่ได้ลบส่วนขยายเพียงแค่เส้นทาง
David Cullen

มันนานมากแล้วที่ฉันดูหน้าคนที่ฉันลืมเกี่ยวกับตัวเลือก SUFFIX
David Cullen

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