Trích xuất tên tệp và phần mở rộng trong Bash


2108

Tôi muốn lấy tên tệp (không có phần mở rộng) và phần mở rộng riêng.

Giải pháp tốt nhất tôi tìm thấy cho đến nay là:

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

Điều này là sai vì nó không hoạt động nếu tên tệp chứa nhiều .ký tự. Nếu, giả sử, tôi có a.b.js, nó sẽ xem xét ab.js, thay vì a.bjs.

Nó có thể dễ dàng thực hiện trong Python với

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

nhưng tôi không muốn kích hoạt trình thông dịch Python chỉ vì điều này, nếu có thể.

Còn ý tưởng nào hay hơn không?


Câu hỏi này giải thích kỹ thuật bash này và một số liên quan khác.
jjclarkson

28
Khi áp dụng các câu trả lời tuyệt vời dưới đây, đừng chỉ dán vào biến của bạn như tôi hiển thị ở đây Sai: extension="{$filename##*.}" như tôi đã làm trong một thời gian! Di chuyển ra $bên ngoài các lọn tóc: Phải: extension="${filename##*.}"
Chris K

4
Đây rõ ràng là một vấn đề không hề nhỏ và đối với tôi thật khó để biết liệu các câu trả lời dưới đây có hoàn toàn chính xác hay không. Thật đáng ngạc nhiên đây không phải là một hoạt động tích hợp trong (ba) sh (câu trả lời dường như thực hiện chức năng bằng cách sử dụng khớp mẫu). Tôi quyết định sử dụng Python os.path.splitextnhư trên thay vì ...
Peter Gibson

1
phần mở rộng phải thể hiện bản chất của một tệp, có một lệnh ma thuật kiểm tra tệp để phân biệt bản chất của nó và mở rộng tiêu chuẩn . xem câu trả lời của tôi
F. Hauri

2
Câu hỏi đặt ra là có vấn đề ngay từ đầu bởi vì .. Từ góc độ của hệ điều hành và hệ thống tệp unix nói chung, không có thứ gọi là phần mở rộng tệp. Sử dụng một "." tách các bộ phận là một quy ước của con người , nó chỉ hoạt động miễn là con người đồng ý tuân theo nó. Ví dụ: với chương trình 'tar', có thể đã quyết định đặt tên các tệp đầu ra bằng "tar." tiền tố thay vì hậu tố ".tar" - Đưa ra "tar.somedir" thay vì "somedir.tar". Không có giải pháp "chung, luôn hoạt động" vì điều này - bạn phải viết mã phù hợp với nhu cầu cụ thể và tên tệp dự kiến ​​của bạn.
CM

Câu trả lời:


3503

Đầu tiên, lấy tên tệp mà không có đường dẫn:

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

Ngoài ra, bạn có thể tập trung vào '/' cuối cùng của đường dẫn thay vì '.' sẽ hoạt động ngay cả khi bạn có phần mở rộng tệp không thể đoán trước:

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

Bạn có thể muốn kiểm tra tài liệu:


85
Hãy xem gnu.org/software/bash/manual/html_node/ Khăn để biết bộ tính năng đầy đủ.
D.Shawley

24
Thêm một số trích dẫn vào "$ fullfile", hoặc bạn sẽ có nguy cơ phá vỡ tên tệp.
lhunath

47
Heck, bạn thậm chí có thể viết filename = "$ {fullfile ## * /}" và tránh gọi thêmbasename
ephemient

45
"Giải pháp" này không hoạt động nếu tệp không có phần mở rộng - thay vào đó, toàn bộ tên tệp là đầu ra, điều này khá tệ khi xem xét rằng các tệp không có phần mở rộng có ở khắp nơi.
nccc

43
Khắc phục để xử lý tên tệp mà không cần gia hạn : extension=$([[ "$filename" = *.* ]] && echo ".${filename##*.}" || echo ''). Lưu ý rằng nếu một tiện ích mở rộng mặt, nó sẽ được trả về bao gồm cả phần mở đầu ., ví dụ : .txt.
mkuity0

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

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

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

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

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

Để biết thêm chi tiết, xem phần mở rộng tham số shell trong hướng dẫn Bash.


22
Bạn (có lẽ vô tình) đưa ra câu hỏi tuyệt vời phải làm gì nếu phần "phần mở rộng" của tên tệp có 2 dấu chấm, như trong .tar.gz ... Tôi chưa bao giờ xem xét vấn đề đó và tôi nghi ngờ đó là vấn đề không thể giải quyết mà không biết tất cả các phần mở rộng tập tin hợp lệ có thể lên phía trước.
rmeador

8
Tại sao không thể giải quyết? Trong ví dụ của tôi, cần xem xét rằng tệp chứa hai phần mở rộng, không phải phần mở rộng có hai dấu chấm. Bạn xử lý cả hai phần mở rộng riêng biệt.
Juliano

22
Không thể giải quyết trên cơ sở từ vựng, bạn sẽ cần kiểm tra loại tệp. Hãy xem xét nếu bạn có một trò chơi được gọi dinosaurs.in.tarvà bạn đã nén nó vào dinosaurs.in.tar.gz:)
porges

11
Điều này trở nên phức tạp hơn nếu bạn đi qua các đường dẫn đầy đủ. Một trong số tôi đã có một '.' trong một thư mục ở giữa đường dẫn, nhưng không có trong tên tệp. Ví dụ "a / bc / d / e / filename" sẽ kết thúc ".c / d / e / tên tệp"
Walt Sellers

6
rõ ràng không có x.tar.gzphần mở rộng nào gzvà tên tệp x.tarlà nó. Không có những thứ như phần mở rộng kép. Tôi khá chắc chắn boost :: filesystem xử lý theo cách đó. (đường dẫn phân chia, change_extension ...) và hành vi của nó dựa trên python nếu tôi không nhầm.
v.oddou

430

Thông thường bạn đã biết tiện ích mở rộng, vì vậy bạn có thể muốn sử dụng:

basename filename .extension

ví dụ:

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

và chúng tôi nhận được

filename

60
Đối số thứ hai đó basenamelà khá đơn giản, thưa ngài / madam :)
akaIDIOT

10
Và làm thế nào để giải nén phần mở rộng, sử dụng kỹ thuật này? ;) Ồ, đợi đã! Chúng tôi thực sự không biết nó trả trước.
Tomasz Gandor

3
Giả sử bạn có một thư mục được nén bằng hoặc kết thúc bằng .ziphoặc .ZIP. Có cách nào bạn có thể làm một cái gì đó như basename $file {.zip,.ZIP}?
Dennis

8
Mặc dù điều này chỉ trả lời một phần của câu hỏi OP, nhưng nó trả lời câu hỏi tôi đã nhập vào google. :-) Rất lắt léo!
sudo thực hiện cài đặt

1
dễ dàng và tuân thủ POSIX
gpanda

147

Bạn có thể sử dụng phép thuật mở rộng tham số POSIX:

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

Có một lưu ý rằng nếu tên tệp của bạn có dạng ./somefile.tar.gzthì echo ${FILENAME%%.*}sẽ tham lam loại bỏ kết quả khớp dài nhất với .và bạn sẽ có chuỗi trống.

(Bạn có thể làm việc xung quanh đó với một biến tạm thời:

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

)


Trang web này giải thích thêm.

${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
Đơn giản hơn nhiều so với câu trả lời của Joachim nhưng tôi luôn phải tìm kiếm sự thay thế biến POSIX. Ngoài ra, điều này chạy trên Max OSX nơi cutkhông có --complementsedkhông có -r.
jwadsack 18/07/14

72

Điều đó dường như không hoạt động nếu tệp không có phần mở rộng hoặc không có tên tệp. Đây là những gì tôi đang sử dụng; nó chỉ sử dụng các nội trang và xử lý nhiều hơn (nhưng không phải tất cả) tên tệp bệnh lý.

#!/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

Và đây là một số thử nghiệm:

$ 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 / nhà / tôi / ...
/:
    thư mục = "/"
    cơ sở = ""
    máy lẻ = ""
/ nhà / tôi /:
    dir = "/ nhà / tôi /"
    cơ sở = ""
    máy lẻ = ""
/ nhà / tôi / tập tin:
    dir = "/ nhà / tôi /"
    cơ sở = "tập tin"
    máy lẻ = ""
/home/me/file.tar:
    dir = "/ nhà / tôi /"
    cơ sở = "tập tin"
    ext = "hắc ín"
/home/me/file.tar.gz:
    dir = "/ nhà / tôi /"
    cơ sở = "file.tar"
    ext = "gz"
/home/me/.hidden:
    dir = "/ nhà / tôi /"
    cơ sở = ".hidden"
    máy lẻ = ""
/home/me/.hidden.tar:
    dir = "/ nhà / tôi /"
    cơ sở = ".hidden"
    ext = "hắc ín"
/ nhà / tôi / ..:
    dir = "/ nhà / tôi /"
    cơ sở = ".."
    máy lẻ = ""
.:
    thư mục = ""
    cơ sở = "."
    máy lẻ = ""

2
Thay vì dir="${fullpath:0:${#fullpath} - ${#filename}}"tôi thường thấy dir="${fullpath%$filename}". Nó đơn giản hơn để viết. Không chắc chắn nếu có bất kỳ sự khác biệt tốc độ thực sự hoặc gotchas.
dubiousjim

2
Điều này sử dụng #! / Bin / bash gần như luôn luôn sai. Thích #! / Bin / sh nếu có thể hoặc #! / Usr / bin / env bash nếu không.
Người tốt

@ Người tốt: Tôi không biết làm thế nào hầu như luôn luôn sai: which bash-> /bin/bash; có lẽ đó là distro của bạn?
vol7ron

2
@ vol7ron - trên nhiều distro bash nằm trong / usr / local / bin / bash. Trên OSX, nhiều người cài đặt bash cập nhật trong / opt / local / bin / bash. Như vậy / bin / bash là sai và người ta nên sử dụng env để tìm nó. Thậm chí tốt hơn là sử dụng / bin / sh và cấu trúc POSIX. Ngoại trừ trên solaris đây là vỏ POSIX.
Người tốt

2
@ GoodPerson nhưng nếu bạn cảm thấy thoải mái hơn với bash, tại sao nên sử dụng sh? Không phải như nói, tại sao lại sử dụng Perl khi bạn có thể sử dụng sh?
vol7ron

46

Bạn có thể sử dụng basename.

Thí dụ:

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

Bạn cần cung cấp basename với phần mở rộng đó sẽ được gỡ bỏ, tuy nhiên nếu bạn luôn được thực hiện tarvới -zsau đó bạn biết phần mở rộng sẽ .tar.gz.

Điều này sẽ làm những gì bạn muốn:

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

2
Tôi cho rằng cd $(basename $1 .tar.gz)hoạt động cho các tập tin .gz. Nhưng trong câu hỏi anh ấy đã đề cậpArchive files have several extensions: tar.gz, tat.xz, tar.bz2
SS Hegde

Tomi Po đăng những điều tương tự 2 năm trước.
phil294

Xin chào Blauhirn, đây là một câu hỏi cũ. Tôi nghĩ rằng một cái gì đó đã xảy ra với ngày. Tôi nhớ rõ việc trả lời câu hỏi ngay sau khi được hỏi, và ở đó chỉ có một vài câu trả lời khác. Có thể là câu hỏi đã được hợp nhất với một câu hỏi khác, SO có làm điều đó không?
Bjarke Freund-Hansen

Yep tôi nhớ chính xác. Ban đầu tôi trả lời câu hỏi này stackoverflow.com/questions/14703318/ vào cùng ngày nó được hỏi, 2 năm sau nó được sáp nhập vào câu hỏi này. Tôi khó có thể đổ lỗi cho một câu trả lời trùng lặp khi câu trả lời của tôi được di chuyển theo cách này.
Bjarke Freund-Hansen

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

hoạt động tốt, vì vậy bạn chỉ có thể sử dụng:

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

Các lệnh, nhân tiện, làm việc như sau.

Lệnh NAMEthay thế một "."ký tự theo sau bởi bất kỳ số lượng "."ký tự không nào cho đến cuối dòng, không có gì (nghĩa là, nó loại bỏ mọi thứ từ cuối cùng "."đến cuối dòng, bao gồm). Đây về cơ bản là một sự thay thế không tham lam bằng cách sử dụng thủ thuật regex.

Lệnh EXTENSIONthay thế một số lượng ký tự bất kỳ được theo sau bởi một "."ký tự ở đầu dòng, không có gì (nghĩa là, nó loại bỏ mọi thứ từ đầu dòng đến dấu chấm cuối cùng, bao gồm). Đây là một sự thay thế tham lam là hành động mặc định.


Sự phá vỡ này đối với các tệp không có phần mở rộng vì nó sẽ in giống nhau cho tên và phần mở rộng. Vì vậy, tôi sử dụng sed 's,\.[^\.]*$,,'cho tên và sed 's,.*\.,., ;t ;g'cho phần mở rộng (sử dụng các lệnh không điển hình testgetlệnh, cùng với substitutelệnh điển hình ).
hIpPy

32

Mellen viết trong một bình luận trên một bài đăng trên blog:

Sử dụng Bash, cũng ${file%.*}có được tên tệp mà không có phần mở rộng và ${file##*.}chỉ có phần mở rộng. Đó là,

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

Đầu ra:

filename: thisfile
extension: txt


29

Không cần phải bận tâm với awkhoặc sedhoặc thậm chí perlcho nhiệm vụ đơn giản này. Có một os.path.splitext()giải pháp tương thích thuần Bash, chỉ sử dụng các mở rộng tham số.

Thực hiện tham khảo

Tài liệu về os.path.splitext(path):

Chia đường dẫn tên đường dẫn thành một cặp (root, ext)sao cho root + ext == pathext trống hoặc bắt đầu bằng một dấu chấm và chứa nhiều nhất một khoảng thời gian. Các giai đoạn hàng đầu trên tên cơ sở được bỏ qua; splitext('.cshrc')trả lại ('.cshrc', '').

Mã Python:

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

Thực hiện Bash

Tôn vinh thời kỳ hàng đầu

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

Bỏ qua các giai đoạn hàng đầu

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

Xét nghiệm

Dưới đây là các trường hợp thử nghiệm để thực hiện Bỏ qua các giai đoạn hàng đầu , phù hợp với triển khai tham chiếu Python trên mỗi đầu vào.

|---------------|-----------|-------|
|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' |
|---------------|-----------|-------|

Kết quả kiểm tra

Tất cả các bài kiểm tra đã qua.


2
không, tên tệp cơ sở text.tar.gznên là textvà phần mở rộng là.tar.gz
frederick99

2
@ frederick99 Như tôi đã nói giải pháp ở đây phù hợp với việc triển khai os.path.splitexttrong Python. Liệu việc thực hiện đó có lành mạnh cho các đầu vào có thể gây tranh cãi hay không là một chủ đề khác.
Cyker

Làm thế nào để các trích dẫn trong mẫu ( "$root") hoạt động? Điều gì có thể xảy ra nếu chúng bị bỏ qua? (Tôi không thể tìm thấy bất kỳ tài liệu nào về vấn đề này.) Ngoài ra, cách này xử lý tên tệp có *hoặc ?trong chúng?
ymett

Ok, kiểm tra cho tôi thấy rằng các trích dẫn làm cho mẫu theo nghĩa đen, nghĩa là *?không đặc biệt. Vì vậy, hai phần câu hỏi của tôi trả lời cho nhau. Tôi có đúng rằng điều này không được ghi nhận? Hay điều này được cho là được hiểu từ thực tế là trích dẫn vô hiệu hóa việc mở rộng toàn cầu nói chung?
ymett

Câu trả lời rực rỡ! Tôi sẽ chỉ đề xuất một biến thể đơn giản hơn một chút để tính toán root: root="${path#?}";root="${path::1}${root%.*}"- sau đó tiến hành tương tự để trích xuất phần mở rộng.
Maëlan

26

Bạn có thể sử dụng cutlệnh để xóa hai phần mở rộng cuối cùng ( ".tar.gz"phần):

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

Theo ghi nhận của Clayton Hughes trong một bình luận, điều này sẽ không hoạt động cho ví dụ thực tế trong câu hỏi. Vì vậy, như một cách thay thế tôi đề xuất sử dụng sedvới các biểu thức chính quy mở rộng, như thế này:

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

Nó hoạt động bằng cách loại bỏ hai phần mở rộng (số alpha) cuối cùng một cách vô điều kiện.

[Cập nhật lại sau khi nhận xét từ Anders Lindahl]


4
Điều này chỉ hoạt động trong trường hợp tên tệp / đường dẫn không chứa bất kỳ dấu chấm nào khác: echo "mpc-1.0.1.tar.gz" | cắt -d '.' --compuity -f2- tạo ra "mpc-1" (chỉ 2 trường đầu tiên sau khi phân định bằng.)
Clayton Hughes

@ClaytonHughes Bạn đã đúng, và tôi nên kiểm tra nó tốt hơn. Thêm một giải pháp khác.
Một số lập trình viên anh chàng

Các biểu thức sed nên sử dụng $để kiểm tra xem phần mở rộng phù hợp có ở cuối tên tệp không. Nếu không, một tên tệp như i.like.tar.gz.files.tar.bz2có thể tạo ra kết quả bất ngờ.
Anders Lindahl

@AndersLindahl Nó vẫn sẽ, nếu thứ tự của các phần mở rộng là đảo ngược của sedthứ tự chuỗi. Ngay cả khi $ở cuối, một tên tệp như mpc-1.0.1.tar.bz2.tar.gzsẽ loại bỏ cả hai .tar.gzvà sau đó .tar.bz2.
Một số lập trình viên anh chàng

$ echo "foo.tar.gz" | cắt -d '.' -f2- KHÔNG CÓ - phần bổ sung sẽ đưa mục phân chia thứ 2 đến cuối chuỗi $ echo "foo.tar.gz" | cắt -d '.' -f2- tar.gz
Gene Đen

23

Dưới đây là một số đề xuất thay thế (chủ yếu là trong awk), bao gồm một số trường hợp sử dụng nâng cao, như trích xuất số phiên bản cho các gói phần mềm.

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 '^[/]\|^~/'

Tất cả các trường hợp sử dụng đang sử dụng đường dẫn đầy đủ ban đầu làm đầu vào, mà không phụ thuộc vào kết quả trung gian.


20

Các câu trả lời được chấp nhận hoạt động tốt trong điển hình trường hợp , nhưng thất bại trong cạnh trường hợp , cụ thể là:

  • Đối với tên tệp không có phần mở rộng (được gọi là hậu tố trong phần còn lại của câu trả lời này), extension=${filename##*.}trả về tên tệp đầu vào thay vì một chuỗi trống.
  • extension=${filename##*.}không bao gồm ban đầu ., trái với quy ước.
    • Chuẩn bị mù quáng .sẽ không hoạt động cho tên tệp mà không có hậu tố.
  • filename="${filename%.*}"sẽ là chuỗi rỗng, nếu tên tệp đầu vào bắt đầu bằng .và không chứa thêm .ký tự nào (ví dụ .bash_profile:) trái với quy ước.

---------

Do đó, sự phức tạp của một giải pháp mạnh mẽ bao gồm tất cả các trường hợp cạnh yêu cầu một hàm - xem định nghĩa của nó bên dưới; nó có thể trả về tất cả các thành phần của một đường dẫn .

Cuộc gọi ví dụ:

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

Lưu ý rằng các đối số sau đường dẫn đầu vào được tự do chọn, tên biến vị trí .
Để bỏ qua các biến không quan tâm đến trước các biến đó, chỉ định _(sử dụng biến ném đi $_) hoặc ''; ví dụ, để trích xuất tên tập tin gốc và phần mở rộng, sử dụng 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

Mã kiểm tra thực hiện chức năng:

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

Đầu ra dự kiến ​​- lưu ý các trường hợp cạnh:

  • một tên tệp không có hậu tố
  • một tên tệp bắt đầu bằng .( không được coi là bắt đầu của hậu tố)
  • một đường dẫn đầu vào kết thúc bằng /(trailing /bị bỏ qua)
  • một đường dẫn đầu vào chỉ là một tên tệp ( .được trả về làm đường dẫn cha)
  • một tên tệp có nhiều hơn .mã thông báo -prefixed (chỉ cuối cùng được coi là hậu tố):
----- /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

Giải pháp nhỏ nhất và đơn giản nhất (trong một dòng) là:

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

Đó là một cách sử dụng vô dụngecho . Nói chung, echo $(command)được viết tốt hơn đơn giản commandtrừ khi bạn đặc biệt yêu cầu trình bao thực hiện mã thông báo khoảng trắng và mở rộng ký tự đại diện trên đầu ra từ commandtrước khi hiển thị kết quả. Trắc nghiệm: đầu ra của cái gì echo $(echo '*')(và nếu đó là những gì bạn thực sự muốn, bạn thực sự thực sự muốn chỉ echo *).
tripleee

@triplee Tôi không sử dụng echolệnh nào cả. Tôi chỉ sử dụng nó để chứng minh kết quả fooxuất hiện trong dòng thứ 3 là kết quả của dòng thứ 2.
Ron

Nhưng chỉ cần basename "${file%.*}"làm như vậy; bạn đang sử dụng một lệnh thay thế để nắm bắt đầu ra của nó, chỉ với echocùng một đầu ra đó ngay lập tức. (Không có trích dẫn, kết quả là khác nhau về mặt danh nghĩa; nhưng điều đó hầu như không liên quan, ít hơn một tính năng ở đây.)
tripleee

Cũng basename "$file" .txttránh sự phức tạp của sự thay thế tham số.
tripleee

1
@Ron Đọc bình luận đầu tiên của anh ấy trước khi buộc tội anh ấy lãng phí thời gian của chúng tôi.
frederick99

14

Tôi nghĩ rằng nếu bạn chỉ cần tên của tệp, bạn có thể thử điều này:

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"

Và đó là tất cả = D.


Chỉ muốn BASEDIRECTORY :) Cảm ơn!
Carlos Ricardo

12

Bạn có thể buộc cắt để hiển thị tất cả các trường và những trường tiếp theo thêm -vào số trường.

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

Vì vậy, nếu FILE là eth0.pcap.gz, EXTENSION sẽ làpcap.gz

Sử dụng cùng logic, bạn cũng có thể tìm nạp tên tệp bằng cách sử dụng '-' với cách cắt như sau:

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

Điều này hoạt động ngay cả đối với tên tệp không có bất kỳ phần mở rộng.


8

Nhận dạng tập tin ma thuật

Ngoài rất nhiều câu trả lời hay cho câu hỏi Stack Overflow này, tôi muốn thêm vào:

Trong Linux và unixen khác, có một lệnh ma thuật được đặt tên file, đó là phát hiện filetype bằng cách phân tích một số byte đầu tiên của tệp. Đây là một công cụ rất cũ, ban đầu được sử dụng cho các máy chủ in (nếu không được tạo cho ... Tôi không chắc về điều đó).

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

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

Các phần mở rộng tiêu chuẩn có thể được tìm thấy trong /etc/mime.types(trên máy tính để bàn Debian GNU / Linux của tôi . Xem man fileman mime.types. Có lẽ bạn phải cài đặt filetiện ích và mime-supportgói):

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

Bạn có thể tạo một chức năng xác định mở rộng đúng. Có một mẫu nhỏ (không hoàn hảo):

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##*[/.-]}
}

Hàm này có thể đặt biến Bash có thể được sử dụng sau:

(Điều này được lấy cảm hứng từ câu trả lời đúng @Petesh):

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

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

8

Ok, nếu tôi hiểu chính xác, vấn đề ở đây là làm thế nào để có được tên và phần mở rộng đầy đủ của một tệp có nhiều phần mở rộng, ví dụ : stuff.tar.gz.

Điều này làm việc cho tôi:

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

Điều này sẽ cung cấp cho bạn stuffnhư tên tệp và .tar.gznhư phần mở rộng. Nó hoạt động cho bất kỳ số lượng tiện ích mở rộng nào, bao gồm 0. Hy vọng điều này sẽ giúp cho bất kỳ ai có cùng vấn đề =)


Kết quả chính xác (theo os.path.splitext, đó là những gì OP muốn) ('stuff.tar', '.gz').
Cyker

6

Tôi sử dụng đoạn script sau

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

Điều này không hiệu quả chút nào. Để giả mạo quá nhiều lần, điều này không cần thiết vì thao tác này có thể được thực hiện trong Bash thuần mà không cần bất kỳ lệnh bên ngoài và forking nào.
codeforester

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

Điều này phục vụ cho nhiều dấu chấm và khoảng trắng trong một tên tệp, tuy nhiên nếu không có phần mở rộng thì nó sẽ trả về tên tệp đó. Dễ dàng kiểm tra mặc dù; chỉ cần kiểm tra tên tệp và phần mở rộng là như nhau.

Đương nhiên phương pháp này không hoạt động đối với các tệp .tar.gz. Tuy nhiên, điều đó có thể được xử lý trong một quá trình hai bước. Nếu phần mở rộng là gz thì hãy kiểm tra lại xem có phần mở rộng tar không.


5

Cách trích xuất tên tệp và phần mở rộng trong :

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

Hãy cẩn thận: Chia tách trên dấu chấm cuối cùng, hoạt động tốt cho tên tệp có dấu chấm trong đó, nhưng không tốt cho phần mở rộng có dấu chấm trong đó. Xem ví dụ dưới đây.

Sử dụng:

$ 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.

Có lẽ có nhiều cách tốt hơn để làm điều này. Hãy chỉnh sửa câu trả lời của tôi để cải thiện nó.


Nếu có một bộ tiện ích mở rộng giới hạn mà bạn sẽ xử lý và bạn biết tất cả các tiện ích mở rộng, hãy thử điều này:

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

Điều này không có sự cảnh báo như ví dụ đầu tiên, nhưng bạn phải xử lý mọi trường hợp để nó có thể tẻ nhạt hơn tùy thuộc vào số lượng tiện ích mở rộng bạn có thể mong đợi.


4

Đây là mã với AWK . Nó có thể được thực hiện đơn giản hơn. Nhưng tôi không giỏi trong 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

Bạn không cần câu lệnh awk đầu tiên trong ví dụ cuối, phải không?
BHSPitMonkey

Bạn có thể tránh đường ống Awk đến Awk bằng cách làm khác split(). awk -F / '{ n=split($2, a, "."); print a[n] }' uses / `là dấu phân cách cấp cao nhất nhưng sau đó phân tách các trường thứ hai trên .và in phần tử cuối cùng từ mảng mới.
tripleee

4

Đơn giản chỉ cần sử dụng ${parameter%word}

Trong trường hợp của bạn:

${FILE%.*}

Nếu bạn muốn kiểm tra nó, tất cả các công việc sau đây và chỉ cần xóa tiện ích mở rộng:

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

2
Tại sao các downvote? Nó vẫn hữu ích, mặc dù không nên có khoảng trống xung quanh các =dấu hiệu.
SilverWolf - Phục hồi Monica

1
Điều này hoạt động tốt. Cảm ơn bạn! (bây giờ nó không có khoảng trắng xung quanh các dấu bằng, nếu đó là lý do tại sao nó bị hạ cấp)
Alex. S.

3

Xây dựng từ câu trả lời của Petesh , nếu chỉ cần tên tệp, cả đường dẫn và phần mở rộng có thể bị tước trong một dòng,

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

Không hoạt động với tôi: "basename: thiếu toán hạng Hãy thử 'basename --help' để biết thêm thông tin."
helmy

Lạ thật, bạn có chắc chắn đang sử dụng Bash? Trong trường hợp của tôi, với cả hai phiên bản 3.2.25 (CentOS cũ) và 4.3.30 (Debian Jessie), nó hoạt động hoàn hảo.
cvr

Có lẽ có một khoảng trống trong tên tệp? Hãy thử sử dụngfilename="$(basename "${fullname%.*}")"
Adrian

Đối số thứ hai basenamelà tùy chọn, nhưng chỉ định phần mở rộng để loại bỏ. Sự thay thế có thể vẫn hữu ích nhưng có lẽ basenamethực sự là không, vì bạn thực sự có thể thực hiện tất cả những sự thay thế này bằng các phần tử shell.
tripleee

3

Dựa chủ yếu vào sự xuất sắc của @ mkuity0, và đầy ắp những bashism ngẫu nhiên, hữu ích - cũng như những câu trả lời khác cho câu hỏi này / những câu hỏi khác / "cái internet chết tiệt đó" ... Tôi gói gọn lại một chút, dễ hiểu hơn một chút, chức năng có thể sử dụng lại cho tôi (hoặc của bạn) .bash_profile, người quan tâm đến những gì (tôi cho là) ​​sẽ là phiên bản mạnh mẽ hơn của dirname/ basename/ những gì có bạn ..

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
}     

Ví dụ sử dụng ...

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
Hoàn thành tốt; một vài gợi ý: - Bạn dường như hoàn toàn không dựa vào $IFS(và nếu là bạn, bạn có thể sử dụng localđể bản địa hóa hiệu quả của việc thiết lập nó). - Tốt hơn để sử dụng localcác biến. - Thông báo lỗi của bạn phải được xuất ra stderr, chứ không phải stdout(sử dụng 1>&2) và bạn sẽ trả về mã thoát khác không. - Tốt hơn để đổi tên fullnamethành basename(trước đây gợi ý một đường dẫn với các thành phần dir). - namenối vô điều kiện một .(thời gian), ngay cả khi bản gốc không có. Bạn chỉ có thể sử dụng basenametiện ích, nhưng lưu ý rằng nó bỏ qua việc chấm dứt /.
mkuity0

2

Một câu trả lời đơn giản:

Để mở rộng câu trả lời cho các biến POSIX , lưu ý rằng bạn có thể thực hiện các mẫu thú vị hơn. Vì vậy, đối với trường hợp chi tiết ở đây, bạn chỉ cần làm điều này:

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

Điều đó sẽ cắt đứt sự xuất hiện cuối cùng của .tar. <cái gì đó> .

Tổng quát hơn, nếu bạn muốn loại bỏ sự xuất hiện cuối cùng của. <cái gì đó> . <cái gì đó khác> sau đó

${1.*.*}

nên làm việc tốt

Liên kết câu trả lời trên dường như đã chết. Đây là một lời giải thích tuyệt vời về một loạt các thao tác chuỗi mà bạn có thể thực hiện trực tiếp trong Bash, từ TLDP .


Có cách nào để làm cho trận đấu không nhạy cảm?
tonix

2

Nếu bạn cũng muốn cho phép các tiện ích mở rộng trống , đây là lần ngắn nhất tôi có thể nghĩ ra:

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

Dòng thứ 1 giải thích: Nó khớp với PATH.EXT hoặc BẤT CỨ NÀO và thay thế nó bằng EXT. Nếu MỌI THỨ đã được khớp, nhóm ext sẽ không bị bắt.


2

Đây là người duy nhất làm việc cho tôi:

path='folder/other_folder/file.js'

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

>> file

Điều này cũng có thể được sử dụng trong nội suy chuỗi, nhưng thật không may, bạn phải đặt basetrước.


1

Đây là thuật toán tôi đã sử dụng để tìm tên và phần mở rộng của tệp khi tôi viết tập lệnh Bash để làm cho tên duy nhất khi tên xung đột với vỏ.

#! /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 

Việc chạy thử.

$ 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: Chương trình chuyển ngữ hoàn chỉnh và nhiều trường hợp kiểm tra có thể được tìm thấy ở đây: https://www.dropbox.com/s/4c6m0f2e28a1vxf/avoid-clashes-code.zip?dl=0


Từ tất cả các giải pháp, đây là giải pháp duy nhất trả về một chuỗi trống khi tệp không có phần mở rộng với:extension=$([[ "$theFileName" == *.* ]] && echo ".${theFileName##*.}" || echo '')
f0nzie

1

Sử dụng tệp ví dụ /Users/Jonathan/Scripts/bash/MyScript.sh, mã này:

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

sẽ cho kết quả ${ME}MyScript${MY_EXT}.sh:


Kịch bản:

#!/bin/bash
set -e

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

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

Một số xét nghiệm:

$ ./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
Không chắc chắn tại sao điều này có quá nhiều lượt tải xuống - nó thực sự hiệu quả hơn câu trả lời được chấp nhận. (Như sau này, nó cũng ngắt với tên tệp đầu vào mà không có phần mở rộng). Sử dụng một đường dẫn rõ ràng basenamelà, có lẽ, quá mức cần thiết.
mkuity0

1

Từ các câu trả lời ở trên, oneliner ngắn nhất để bắt chước Python

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

giả sử tập tin của bạn thực sự có một phần mở rộng, là

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

Tôi đã nhận được thông tin về điều này. Tôi đang xem xét để loại bỏ câu trả lời, mọi người bằng cách nào đó không thích nó.
phổ biến

tên cơ sở không loại bỏ phần mở rộng, chỉ đường dẫn.
David Cullen

Đã quá lâu kể từ khi tôi nhìn vào trang người đàn ông, tôi quên mất tùy chọn SUFFIX.
David Cullen

Bạn phải biết phần mở rộng nào bạn muốn loại bỏ trước khi bạn biết nên đặt cái gì để EXTđây là con rùa. (Ngoài ra, bạn nên tránh tất cả chữ hoa cho tên biến riêng tư của mình; chúng được dành riêng cho biến hệ thống.)
tripleee
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.