Làm cách nào để xóa hậu tố tệp và phần đường dẫn khỏi chuỗi đường dẫn trong Bash?


396

Đưa ra một đường dẫn tệp chuỗi như /foo/fizzbuzz.bar, làm thế nào tôi có thể sử dụng bash để trích xuất chỉ một fizzbuzzphần của chuỗi đã nói?


Thông tin bạn có thể tìm thấy trong hướng dẫn Bash , tìm kiếm ${parameter%word}${parameter%%word}trong phần khớp phần.
1ac0

Câu trả lời:


574

Đây là cách thực hiện với các toán tử # và% trong Bash.

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

${x%.bar}cũng có thể ${x%.*}xóa mọi thứ sau một dấu chấm hoặc ${x%%.*}xóa mọi thứ sau dấu chấm đầu tiên.

Thí dụ:

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

Tài liệu có thể được tìm thấy trong hướng dẫn Bash . Tìm kiếm ${parameter%word}${parameter%%word}phần phù hợp với phần.


Tôi đã kết thúc việc sử dụng cái này bởi vì nó linh hoạt nhất và có một vài điều tương tự khác tôi muốn làm cũng như điều này đã làm tốt.
Lawrence Johnston

Đây có lẽ là linh hoạt nhất trong tất cả các câu trả lời được đăng, nhưng tôi nghĩ rằng các câu trả lời gợi ý các lệnh basename và dirname cũng đáng được chú ý. Chúng có thể chỉ là mánh khóe nếu bạn không cần bất kỳ mẫu phù hợp nào khác.
mgadda

7
Cái này được gọi là $ {x% .bar} là gì? Tôi muốn tìm hiểu thêm về nó.
Basil

14
@Basil: Mở rộng tham số. Trên bảng điều khiển, gõ "man bash" và sau đó nhập "/ mở rộng tham số"
Zan Lynx

1
Tôi đoán lời giải thích 'man bash' có ý nghĩa nếu bạn đã biết nó làm gì hoặc nếu bạn tự thử nó một cách khó khăn. Nó gần như là xấu như tham chiếu git. Tôi chỉ cần google nó thay thế.
triplebig

226

nhìn vào lệnh basename:

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

11
Có lẽ là đơn giản nhất trong tất cả các giải pháp hiện được cung cấp ... mặc dù tôi sẽ sử dụng $ (...) thay vì backticks.
Michael Johnson

6
Đơn giản nhất nhưng thêm một phụ thuộc (không phải là lớn hay lạ, tôi thừa nhận). Nó cũng cần phải biết hậu tố.
Vinko Vrsalovic

Và có thể được sử dụng để loại bỏ bất cứ thứ gì từ cuối, về cơ bản nó chỉ là loại bỏ chuỗi từ cuối.
Smar

2
Vấn đề là thời gian trúng. Tôi vừa tìm kiếm câu hỏi cho cuộc thảo luận này sau khi xem bash mất gần 5 phút để xử lý 800 tệp, sử dụng tên cơ sở. Sử dụng phương pháp regex ở trên, thời gian đã giảm xuống còn khoảng 7 giây. Mặc dù câu trả lời này dễ thực hiện hơn cho lập trình viên, nhưng thời gian đạt được là quá nhiều. Hãy tưởng tượng một thư mục có vài nghìn tệp trong đó! Tôi có một số thư mục như vậy.
xizdaqrian

@xizdaqrian Điều này hoàn toàn sai. Đây là một chương trình đơn giản, không cần mất nửa giây để quay lại. Tôi vừa thực hiện thời gian find / home / me / dev -name "* .py" .py -exec tên cơ sở {} \; và nó tước phần mở rộng và thư mục cho 1500 tệp trong tổng số 1 giây.
Laszlo Treszkai

40

Bash thuần, được thực hiện trong hai hoạt động riêng biệt:

  1. Xóa đường dẫn khỏi chuỗi đường dẫn:

    path=/foo/bar/bim/baz/file.gif
    
    file=${path##*/}  
    #$file is now 'file.gif'
  2. Xóa phần mở rộng khỏi chuỗi đường dẫn:

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

18

Sử dụng tên cơ sở tôi đã sử dụng như sau để đạt được điều này:

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

    # Do things with $fname
done;

Điều này không đòi hỏi kiến ​​thức chuyên môn về phần mở rộng tệp và hoạt động ngay cả khi bạn có tên tệp có dấu chấm trong tên tệp của nó (phía trước phần mở rộng của nó); basenamemặc dù nó không yêu cầu chương trình , nhưng đây là một phần của lõi GNU nên nó sẽ được gửi cùng với bất kỳ bản phân phối nào.


1
Câu trả lời tuyệt vời! loại bỏ phần mở rộng một cách rất sạch sẽ, nhưng nó không xóa phần mở rộng. ở cuối tên tập tin
metrix

3
@metrix chỉ cần thêm "." trước $ ext, tức là: fname=`basename $file .$ext`
Carlos Troncoso

13

Cách bash tinh khiết:

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

Chức năng này được giải thích trên man bash trong phần "Mở rộng tham số". Non bash cách rất nhiều: awk, perl, sed và như vậy.

EDIT: Hoạt động với các dấu chấm trong hậu tố tập tin và không cần biết hậu tố (phần mở rộng), nhưng không hoạt động với các dấu chấm trong chính tên .


11

Các hàm basename và dirname là những gì bạn đang theo đuổi:

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

Có đầu ra:

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

1
Nó sẽ rất hữu ích để sửa chữa các trích dẫn ở đây - có thể chạy qua shellcheck.net với mystring=$1chứ không phải là giá trị không đổi hiện tại (mà sẽ ngăn chặn một vài cảnh báo, là nhất định không chứa dấu cách / nhân vật glob / etc), và địa chỉ các vấn đề đó tìm thấy?
Charles Duffy

Chà, tôi đã thực hiện một số thay đổi thích hợp để hỗ trợ dấu ngoặc kép trong $ mystring. Trời ạ, đã lâu lắm rồi tôi mới viết cái này :)
Jerub

Sẽ được cải thiện hơn nữa để trích dẫn kết quả: echo "basename: $(basename "$mystring")"- theo cách đó nếu mystring='/foo/*'bạn không được *thay thế bằng danh sách các tệp trong thư mục hiện tại sau khi basename kết thúc.
Charles Duffy

6

Sử dụng basenamegiả định rằng bạn biết phần mở rộng tập tin là gì, phải không?

Và tôi tin rằng các đề xuất biểu thức chính quy khác nhau không đối phó với tên tệp chứa nhiều hơn một "."

Sau đây dường như để đối phó với dấu chấm đôi. Ồ, và tên tệp có chứa "/" (chỉ dành cho cú đá)

Để diễn giải Pascal, "Xin lỗi kịch bản này quá dài. Tôi không có thời gian để làm cho nó ngắn hơn"


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

1
Điều này thật tuyệt vời và mạnh mẽ
Gaurav Jain

4

Ngoài cú pháp tuân thủ POSIX được sử dụng trong câu trả lời này ,

basename string [suffix]

như trong

basename /foo/fizzbuzz.bar .bar

GNUbasename hỗ trợ cú pháp khác:

basename -s .bar /foo/fizzbuzz.bar

với kết quả tương tự. Sự khác biệt và lợi thế là -sngụ ý -a, hỗ trợ nhiều đối số:

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

Điều này thậm chí có thể được tạo tên tệp an toàn bằng cách tách đầu ra với các byte NUL bằng cách sử dụng -ztùy chọn, ví dụ: đối với các tệp này chứa khoảng trắng, dòng mới và ký tự toàn cầu (được trích dẫn bởi ls):

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

Đọc thành một mảng:

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

readarray -dyêu cầu Bash 4.4 hoặc mới hơn. Đối với các phiên bản cũ hơn, chúng tôi phải lặp:

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

Ngoài ra, hậu tố được chỉ định sẽ bị xóa trong đầu ra nếu có (và bỏ qua khác).
aksh1618


3

Nếu bạn không thể sử dụng tên cơ sở như được đề xuất trong các bài đăng khác, bạn luôn có thể sử dụng sed. Đây là một ví dụ (xấu xí). Nó không phải là tốt nhất, nhưng nó hoạt động bằng cách trích xuất chuỗi mong muốn và thay thế đầu vào bằng chuỗi mong muốn.

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

Điều này sẽ giúp bạn có đầu ra

fizzbuzz


Mặc dù đây là câu trả lời cho câu hỏi ban đầu, lệnh này rất hữu ích khi tôi có các dòng đường dẫn trong một tệp để trích xuất tên cơ sở để in chúng ra màn hình.
Sangcheol Choi

2

Cẩn thận với giải pháp perl được đề xuất: nó loại bỏ bất cứ thứ gì sau dấu chấm đầu tiên.

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

Nếu bạn muốn làm điều đó với perl, điều này hoạt động:

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

Nhưng nếu bạn đang sử dụng Bash, các giải pháp với y=${x%.*}(hoặc basename "$x" .extnếu bạn biết phần mở rộng) đơn giản hơn nhiều.


1

Tên cơ sở làm điều đó, loại bỏ đường dẫn. Nó cũng sẽ loại bỏ hậu tố nếu được cung cấp và nếu nó phù hợp với hậu tố của tệp nhưng bạn sẽ cần phải biết hậu tố để cung cấp cho lệnh. Nếu không, bạn có thể sử dụng mv và tìm ra tên mới nên là một cách khác.


1

Kết hợp câu trả lời được xếp hạng cao nhất với câu trả lời được xếp hạng thứ hai để có được tên tệp mà không có đường dẫn đầy đủ:

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

Tại sao bạn sử dụng một mảng ở đây? Ngoài ra, tại sao lại sử dụng tên cơ sở?
codeforester
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.