Bagaimana cara menghapus ekstensi nama file di skrip shell?

162

Apa yang salah dengan kode berikut?

name='$filename | cut -f1 -d'.''

Sebagaimana adanya, saya mendapatkan string literal $filename | cut -f1 -d'.', tetapi jika saya menghapus tanda kutip, saya tidak mendapatkan apa pun. Sementara itu, mengetik

"test.exe" | cut -f1 -d'.'

dalam shell memberi saya output yang saya inginkan , test. Saya sudah tahu $filenametelah diberi nilai yang benar. Yang ingin saya lakukan adalah menetapkan nama file ke variabel tanpa ekstensi.

mimicocotopus
sumber
6
basename $filename .exeakan melakukan hal yang sama. Itu dengan asumsi Anda selalu tahu ekstensi apa yang ingin Anda hapus.
mpe
7
@pe, maksudmu basename "$filename" .exe. Jika tidak, nama file dengan spasi akan menjadi berita buruk.
Charles Duffy

Jawaban:

132

Anda harus menggunakan sintaks substitusi perintah$(command) saat Anda ingin menjalankan perintah dalam skrip / perintah.

Jadi baris Anda akan menjadi

name=$(echo "$filename" | cut -f 1 -d '.')

Penjelasan kode:

  1. echodapatkan nilai variabel $filenamedan kirimkan ke keluaran standar
  2. Kami kemudian mengambil output dan menyalurkannya ke cutperintah
  3. The cutakan menggunakan. sebagai pembatas (juga dikenal sebagai pemisah) untuk memotong string menjadi segmen dan dengan -fkita memilih segmen mana yang ingin kita keluaran
  4. Kemudian $()substitusi perintah akan mendapatkan keluaran dan mengembalikan nilainya
  5. Nilai yang dikembalikan akan diberikan ke variabel bernama name

Perhatikan bahwa ini memberikan porsi variabel hingga periode pertama .:

$ filename=hello.world
$ echo "$filename" | cut -f 1 -d '.'
hello
$ filename=hello.hello.hello
$ echo "$filename" | cut -f 1 -d '.'
hello
$ filename=hello
$ echo "$filename" | cut -f 1 -d '.'
hello
Rohan
sumber
19
Backticks tidak digunakan lagi oleh POSIX, $()lebih disukai.
jordanm
4
Percabangan dan perpipaan untuk mendapatkan beberapa karakter adalah tentang solusi terburuk yang bisa dibayangkan.
Jens
39
Masalah dengan jawaban ini adalah menganggap string input HANYA memiliki satu titik ... @chepner di bawah ini memiliki solusi yang jauh lebih baik ... name = $ {filename%. *}
Scott Stensland
7
Jawaban ini adalah karakteristik pemula dan tidak boleh disebarluaskan. Gunakan mekanisme bawaan seperti yang dijelaskan oleh jawaban chepner
neric
2
Gagal ./ihavenoextension, seperti jawaban lainnya.
jpmc26
293

Anda juga dapat menggunakan perluasan parameter:

$ filename=foo.txt
$ echo "${filename%.*}"
foo

Perlu diketahui bahwa jika tidak ada ekstensi file, ekstensi akan mencari titik di belakang, misalnya

  • Jika nama file hanya dimulai dengan titik (misalnya .bashrc) itu akan menghapus seluruh nama file.
  • Jika ada titik hanya di jalur (misalnya path.to/myfileatau ./myfile), maka itu akan memotong di dalam jalur.
chepner
sumber
6
berikut penjelasan perintahnya: gnu.org/software/bash/manual/html_node/…
Carlos Robles
1
Dan di sini saya akan menggunakan echo -n "This.File.Has.Periods.In.It.txt" | awk -F. '{$NF=""; print $0}' | tr ' ' '.' | rev | cut -c 2- | rev. Terima kasih.
pengguna208145
1
Apakah ini berfungsi dengan file dengan beberapa ekstensi seperti image.png.gz?
Hawker65
11
%.*hanya akan menghapus ekstensi terakhir; jika Anda ingin menghapus semua ekstensi, gunakan %%.*.
chepner
3
Berhati-hatilah karena ini akan menghapus seluruh nama jika tidak ada ekstensi dan jalurnya relatif. Misalnya filename=./ihavenoextension,.
jpmc26
126

Jika Anda mengetahui ekstensinya, Anda dapat menggunakan nama dasar

$ basename /home/jsmith/base.wiki .wiki
base
Steven Penny
sumber
1
Bagaimana cara menghapus just .wikiand end up /home/jsmith/base?
Gabriel Staples
Pembaruan: jawaban: Lihat stackoverflow.com/a/32584935/4561887 . Bekerja dengan sempurna!
Gabriel Staples
22
file1=/tmp/main.one.two.sh
t=$(basename "$file1")                        # output is main.one.two.sh
name=$(echo "$file1" | sed -e 's/\.[^.]*$//') # output is /tmp/main.one.two
name=$(echo "$t" | sed -e 's/\.[^.]*$//')     # output is main.one.two

gunakan mana saja yang Anda inginkan. Di sini saya berasumsi bahwa .(titik) terakhir yang diikuti oleh teks adalah ekstensi.

Raghwendra
sumber
Apa yang terjadi kapan file1=/tmp.d/mainonetwosh? Ekspresi sed harus diganti dengan's/\.[^./]*$//'
FedonKadifeli
21

Jika nama file Anda berisi titik (selain salah satu ekstensi), gunakan ini:

echo $filename | rev | cut -f 2- -d '.' | rev
manish_s
sumber
1
Saya lupa putaran tengahnya, tapi begitu saya melihatnya, ini luar biasa!
Pooba tertinggi
Bahkan lebih baik dengan -sopsi yang diberikan cut, sehingga ia mengembalikan string kosong setiap kali nama file tidak mengandung titik.
Hibou57
2
Ini harus menjadi jawaban yang diterima menurut saya, karena berfungsi di jalur dengan titik-titik di dalamnya, dengan file tersembunyi yang dimulai dengan titik, atau bahkan dengan file dengan banyak ekstensi.
Tim Krief
Apa yang terjadi dengan file seperti ini filename=/tmp.d/foo:?
FedonKadifeli
@FedonKadifeli, ini berfungsi hanya untuk nama file, bukan untuk jalur lengkap. Tapi Anda bisa mendapatkan nama file terlebih dahulu menggunakan pendekatan serupa dengan menggunakan /sebagai pembatas.
manish_s
6
#!/bin/bash
file=/tmp/foo.bar.gz
echo $file ${file%.*}

keluaran:

/tmp/foo.bar.gz /tmp/foo.bar

Perhatikan bahwa hanya ekstensi terakhir yang dihapus.

C. Paul Bond
sumber
Apa yang terjadi dengan file seperti ini file=/tmp.d/foo:?
FedonKadifeli
3

Rekomendasi saya adalah menggunakan basename.
Ini secara default di Ubuntu, kode secara visual sederhana dan menangani sebagian besar kasus.

Berikut adalah beberapa sub-kasus untuk menangani spasi dan multi-titik / sub-ekstensi:

pathfile="../space fld/space -file.tar.gz"
echo ${pathfile//+(*\/|.*)}

Biasanya menghilangkan ekstensi dari yang pertama ., tetapi gagal di ..jalur kita

echo **"$(basename "${pathfile%.*}")"**  
space -file.tar     # I believe we needed exatly that

Ini catatan penting:

Saya menggunakan tanda kutip ganda di dalam tanda kutip ganda untuk menangani spasi. Kutipan tunggal tidak akan lolos karena mengirim $. Bash tidak biasa dan membaca "kedua" pertama "kutipan" karena perluasan.

Namun, Anda tetap perlu memikirkannya .hidden_files

hidden="~/.bashrc"
echo "$(basename "${hidden%.*}")"  # will produce "~" !!!  

bukan hasil yang "" diharapkan. Untuk mewujudkannya gunakan $HOMEatau /home/user_path/
karena sekali lagi bash "tidak biasa" dan jangan perluas "~" (cari bash BashPitfalls)

hidden2="$HOME/.bashrc" ;  echo '$(basename "${pathfile%.*}")'
Alexey K.
sumber
2

Hanya menggunakan built-in POSIX:

#!/usr/bin/env sh
path=this.path/with.dots/in.path.name/filename.tar.gz

# Get the basedir without external command
# by stripping out shortest trailing match of / followed by anything
dirname=${path%/*}

# Get the basename without external command
# by stripping out longest leading match of anything followed by /
basename=${path##*/}

# Strip uptmost trailing extension only
# by stripping out shortest trailing match of dot followed by anything
oneextless=${basename%.*}; echo "$noext" 

# Strip all extensions
# by stripping out longest trailing match of dot followed by anything
noext=${basename%%.*}; echo "$noext"

# Printout demo
printf %s\\n "$path" "$dirname" "$basename" "$oneextless" "$noext"

Demo cetakan:

this.path/with.dots/in.path.name/filename.tar.gz
this.path/with.dots/in.path.name
filename.tar.gz
filename.tar
filename
Léa Gris
sumber
1
#!/bin/bash
filename=program.c
name=$(basename "$filename" .c)
echo "$name"

keluaran:

program
booboo
sumber
1
Bagaimana ini berbeda dengan jawaban yang diberikan oleh Steven Penny 3 tahun lalu?
gniourf_gniourf
@gniourf_gniourf upvoting karena Anda telah membuatnya menjadi jawaban yang berguna dengan menunjukkan cara menggunakannya dengan benar dengan variabel.
mwfearnley
1

Seperti yang ditunjukkan oleh Hawker65 dalam komentar jawaban chepner, solusi yang paling banyak dipilih tidak menangani beberapa ekstensi (seperti namafile.tar.gz), atau titik-titik di jalur lainnya (seperti this.path / with .dots / in.path.name). Solusi yang mungkin adalah:

a=this.path/with.dots/in.path.name/filename.tar.gz
echo $(dirname $a)/$(basename $a | cut -d. -f1)
MadMage
sumber
Yang satu ini menghapus "tar.gz" dengan memilih karakter sebelum titik pertama dalam nama file tidak menghitung jalur. Seseorang mungkin tidak ingin menghapus ekstensi seperti itu.
Frotz
Jika Anda tahu itu berakhir .tar.gz, Anda bisa menggunakan $(basename "$a" .tar.gz). Selain itu, pastikan untuk membungkus variabel Anda dalam tanda kutip di mana-mana jika ada kemungkinan variabel itu akan berisi spasi atau karakter aneh lainnya!
mwfearnley
0

Dua masalah dengan kode Anda:

  1. Anda menggunakan '(centang) alih-alih' (centang kembali) untuk mengapit perintah yang menghasilkan string yang ingin Anda simpan dalam variabel.
  2. Anda tidak melakukan "echo" variabel "$ filename" ke pipa ke dalam perintah "cut".

Saya akan mengubah kode Anda menjadi "name = ʻecho $ filename | cut -f 1 -d '.' `", seperti yang ditunjukkan di bawah ini (sekali lagi, perhatikan tanda centang di sekitar definisi variabel nama):

$> filename=foo.txt
$> echo $filename
foo.txt
$> name=`echo $filename | cut -f1 -d'.'`
$> echo $name
foo
$> 
FanDeLaU
sumber
0

Jawaban yang diberikan sebelumnya memiliki masalah dengan jalur yang berisi titik. Beberapa contoh:

/xyz.dir/file.ext
./file.ext
/a.b.c/x.ddd.txt

Saya lebih suka menggunakan |sed -e 's/\.[^./]*$//'. Sebagai contoh:

$ echo "/xyz.dir/file.ext" | sed -e 's/\.[^./]*$//'
/xyz.dir/file
$ echo "./file.ext" | sed -e 's/\.[^./]*$//'
./file
$ echo "/a.b.c/x.ddd.txt" | sed -e 's/\.[^./]*$//'
/a.b.c/x.ddd

Catatan: Jika Anda ingin menghapus beberapa ekstensi (seperti pada contoh terakhir), gunakan |sed -e 's/\.[^/]*$//':

$ echo "/a.b.c/x.ddd.txt" | sed -e 's/\.[^/]*$//'
/a.b.c/x

Namun, metode ini akan gagal di "dot-files" tanpa ekstensi:

$ echo "/a.b.c/.profile" | sed -e 's/\.[^./]*$//'
/a.b.c/

Untuk menutupi juga kasus seperti itu, Anda dapat menggunakan:

$ echo "/a.b.c/.profile" | sed -re 's/(^.*[^/])\.[^./]*$/\1/'
/a.b.c/.profile
FedonKadifeli
sumber