Bagaimana cara menghapus akhiran file dan bagian jalur dari string jalur di Bash?

396

Diberikan jalur file string seperti /foo/fizzbuzz.bar, bagaimana saya menggunakan bash untuk mengekstrak hanya fizzbuzzbagian dari string tersebut?

Lawrence Johnston
sumber
Informasi yang dapat Anda temukan di Bash manual , cari ${parameter%word}dan ${parameter%%word}di bagian pencocokan porsi.
1ac0

Jawaban:

574

Inilah cara melakukannya dengan operator # dan% di Bash.

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

${x%.bar}bisa juga ${x%.*}untuk menghapus semuanya setelah titik atau ${x%%.*}untuk menghapus semuanya setelah titik pertama.

Contoh:

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

Dokumentasi dapat ditemukan di manual Bash . Cari ${parameter%word}dan ${parameter%%word}ikuti bagian pencocokan porsi.

Zan Lynx
sumber
Saya akhirnya menggunakan yang ini karena itu yang paling fleksibel dan ada beberapa hal serupa lainnya yang ingin saya lakukan juga bahwa ini bekerja dengan baik.
Lawrence Johnston
Ini mungkin yang paling fleksibel dari semua jawaban yang diposting, tapi saya pikir jawaban yang menyarankan perintah basename dan dirname layak mendapat perhatian juga. Mereka mungkin hanya trik jika Anda tidak memerlukan pencocokan pola mewah lainnya.
mgadda
7
Apa ini yang disebut $ {x% .bar}? Saya ingin belajar lebih banyak tentang itu.
Basil
14
@ Basil: Ekspansi Parameter. Pada konsol ketik "man bash" dan kemudian ketik "/ ekspansi parameter"
Zan Lynx
1
Saya kira penjelasan 'man bash' masuk akal jika Anda sudah tahu apa fungsinya atau jika Anda mencobanya sendiri dengan cara yang sulit. Ini hampir seburuk referensi git. Saya hanya akan google saja.
triplebig
227

lihat perintah basename:

NAME="$(basename /foo/fizzbuzz.bar .bar)"
zigdon
sumber
11
Mungkin yang paling sederhana dari semua solusi yang ditawarkan saat ini ... walaupun saya akan menggunakan $ (...) daripada backticks.
Michael Johnson
6
Sederhana tapi menambah ketergantungan (bukan yang besar atau aneh, saya akui). Itu juga perlu tahu sufiks.
Vinko Vrsalovic
Dan dapat digunakan untuk menghapus apa pun dari ujung, pada dasarnya itu hanya penghapusan string dari ujung.
Smar
2
Masalahnya adalah waktu yang tepat. Saya baru saja mencari pertanyaan untuk diskusi ini setelah menonton bash mengambil hampir 5 menit untuk memproses 800 file, menggunakan nama file. Menggunakan metode regex di atas, waktu dikurangi menjadi sekitar 7 detik. Meskipun jawaban ini lebih mudah dilakukan untuk programmer, waktu yang dihabiskan terlalu banyak. Bayangkan sebuah folder dengan beberapa ribu file di dalamnya! Saya punya beberapa folder seperti itu.
xizdaqrian
@ xizdaqrian Ini benar-benar salah. Ini adalah program sederhana, yang seharusnya tidak butuh setengah detik untuk kembali. Saya baru saja mengeksekusi find / home / me / dev -name "* .py" .py -exec basename {} \; dan itu menghapus ekstensi dan direktori untuk 1500 file dalam 1 detik total.
Laszlo Treszkai
40

Bash murni, dilakukan dalam dua operasi terpisah:

  1. Hapus path dari path-string:

    path=/foo/bar/bim/baz/file.gif
    
    file=${path##*/}  
    #$file is now 'file.gif'
  2. Hapus ekstensi dari path-string:

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

Menggunakan nama bas saya menggunakan yang berikut untuk mencapai ini:

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

    # Do things with $fname
done;

Ini tidak memerlukan pengetahuan apriori tentang ekstensi file dan berfungsi bahkan ketika Anda memiliki nama file yang memiliki titik-titik dalam nama file itu (di depan ekstensi itu); itu memang membutuhkan program basename, tetapi ini adalah bagian dari GNU coreutils sehingga harus dikirimkan dengan distro apa pun.

mikrofon
sumber
1
Jawaban yang sangat bagus! menghapus ekstensi dengan cara yang sangat bersih, tetapi ekstensi tidak dihapus. di akhir nama file.
metrix
3
@metrix cukup tambahkan "." sebelum $ ext, yaitu: fname=`basename $file .$ext`
Carlos Troncoso
13

Cara bash murni:

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

Fungsi ini dijelaskan pada man bash di bawah "Ekspansi Parameter". Cara non bash berlimpah: awk, perl, sed dan sebagainya.

EDIT: Bekerja dengan titik-titik di akhiran file dan tidak perlu tahu akhiran (ekstensi), tetapi tidak bekerja dengan titik-titik dalam nama itu sendiri.

Vinko Vrsalovic
sumber
11

Fungsi nama dasar dan dirname adalah yang Anda cari:

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

Memiliki output:

basename: fizzbuzz.bar
basename + remove .bar: fizzbuzz
dirname: /foo
Jerub
sumber
1
Akan sangat membantu untuk memperbaiki kutipan di sini - mungkin jalankan ini melalui shellcheck.net dengan mystring=$1daripada nilai konstan saat ini (yang akan menekan beberapa peringatan, memastikan tidak mengandung spasi / karakter glob / dll), dan mengatasi masalah itu menemukan?
Charles Duffy
Yah, saya membuat beberapa perubahan yang sesuai untuk mendukung tanda kutip dalam $ mystring. Astaga, ini sudah lama sekali saya menulis ini :)
Jerub
Akan ada peningkatan lebih lanjut untuk mengutip hasil: echo "basename: $(basename "$mystring")"- dengan cara itu jika mystring='/foo/*'Anda tidak mendapatkan *diganti dengan daftar file di direktori saat ini setelah basename selesai.
Charles Duffy
6

Menggunakan basenameasumsi bahwa Anda tahu apa ekstensi file itu, bukan?

Dan saya percaya bahwa berbagai saran ekspresi reguler tidak mengatasi nama file yang mengandung lebih dari satu "."

Berikut ini tampaknya mengatasi titik ganda. Oh, dan nama file yang berisi "/" sendiri (hanya untuk iseng)

Mengutip Pascal, "Maaf skrip ini terlalu panjang. Saya tidak punya waktu untuk membuatnya lebih pendek"


  #!/usr/bin/perl
  $fullname = $ARGV[0];
  ($path,$name) = $fullname =~ /^(.*[^\\]\/)*(.*)$/;
  ($basename,$extension) = $name =~ /^(.*)(\.[^.]*)$/;
  print $basename . "\n";
 
Andrew Edgecombe
sumber
1
Ini bagus dan kuat
Gaurav Jain
4

Selain sintaks konforman POSIX yang digunakan dalam jawaban ini ,

basename string [suffix]

seperti dalam

basename /foo/fizzbuzz.bar .bar

GNUbasename mendukung sintaks lain:

basename -s .bar /foo/fizzbuzz.bar

dengan hasil yang sama. Perbedaan dan keuntungannya adalah yang -smenyiratkan -a, yang mendukung banyak argumen:

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

Ini bahkan dapat membuat nama file aman dengan memisahkan output dengan NUL byte menggunakan -zopsi, misalnya untuk file-file ini yang mengandung karakter kosong, baris baru dan gumpalan (dikutip oleh ls):

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

Membaca ke dalam array:

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

readarray -dmembutuhkan Bash 4.4 atau yang lebih baru. Untuk versi yang lebih lama, kita harus mengulang:

while IFS= read -r -d '' fname; do arr+=("$fname"); done < <(basename -zs .bar has*)
Benjamin W.
sumber
Juga, akhiran yang ditentukan dihapus dalam output jika ada (dan diabaikan sebaliknya).
aksh1618
3
perl -pe 's/\..*$//;s{^.*/}{}'
mopoke
sumber
3

Jika Anda tidak dapat menggunakan nama bas seperti yang disarankan dalam posting lain, Anda selalu dapat menggunakan sed. Berikut ini adalah contoh (jelek). Ini bukan yang terbaik, tetapi bekerja dengan mengekstraksi string yang diinginkan dan mengganti input dengan string yang diinginkan.

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

Yang akan memberi Anda output

fizzbuzz

nymacro
sumber
Meskipun ini adalah jawaban untuk pertanyaan awal, perintah ini berguna ketika saya memiliki garis path dalam file untuk mengekstrak nama dasar untuk mencetaknya ke layar.
Sangcheol Choi
2

Waspadalah terhadap solusi perl yang disarankan: ia menghilangkan apa pun setelah titik pertama.

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

Jika Anda ingin melakukannya dengan perl, ini berfungsi:

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

Tetapi jika Anda menggunakan Bash, solusi dengan y=${x%.*}(atau basename "$x" .extjika Anda tahu ekstensi) jauh lebih sederhana.

mivk
sumber
1

Nama dasarnya melakukan itu, menghilangkan path. Ini juga akan menghapus akhiran jika diberikan dan jika cocok dengan akhiran file tetapi Anda harus tahu akhiran untuk diberikan kepada perintah. Kalau tidak, Anda dapat menggunakan mv dan mencari tahu apa nama baru itu dengan cara lain.

waynecolvin
sumber
1

Menggabungkan jawaban berperingkat teratas dengan jawaban peringkat kedua untuk mendapatkan nama file tanpa path lengkap:

$ x="/foo/fizzbuzz.bar.quux"
$ y=(`basename ${x%%.*}`)
$ echo $y
fizzbuzz
c.gutierrez
sumber
Mengapa Anda menggunakan array di sini? Juga, mengapa menggunakan nama samasekali?
codeforester