Bash'deki yol dizesinden dosya sonekini ve yol bölümünü nasıl kaldırabilirim?


396

Gibi bir dize dosyası yolu göz önüne alındığında , söz konusu dizenin /foo/fizzbuzz.barsadece bir fizzbuzzkısmını ayıklamak için bash'ı nasıl kullanırım ?


Bilgileri size bulabileceğiniz Bash kılavuzda için görünüm ${parameter%word}ve ${parameter%%word}porsiyon eşleştirme bölümüne sondaki içinde.
1ac0

Yanıtlar:


574

Bash içindeki # ve% operatörleriyle nasıl yapılacağı aşağıda açıklanmıştır.

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

${x%.bar}Ayrıca olabilir ${x%.*}bir nokta sonra her şeyi kaldırmak veya ${x%%.*}ilk noktasından sonraki her şeyi kaldırın.

Misal:

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

Belgeleri Bash kılavuzunda bulabilirsiniz . Bak ${parameter%word}ve ${parameter%%word}kısım uygun bölümü arka.


En esnek ve bunu yapmak istedim başka benzer şeyler vardı bu bir kullanarak sona erdi bu güzel yaptı.
Lawrence Johnston

Bu muhtemelen gönderilen tüm cevapların en esnektir, ancak bence basename ve dirname komutlarını öneren cevaplar da bazı dikkatleri hak ediyor. Başka süslü desen eşleşmesine ihtiyacınız yoksa bunlar sadece hile olabilir.
mgadda

7
Buna $ {x% .bar} denir? Bununla ilgili daha fazla bilgi edinmek istiyorum.
Fesleğen

14
@Basil: Parametre Genişletme. Bir konsol türü "adam bash" ve ardından "/ parametre genişleme" On
Zan Lynx

1
Sanırım 'man bash' açıklaması zaten ne yaptığını biliyorsanız veya kendiniz zor yoldan denediyseniz mantıklı. Git referansı kadar kötü. Bunun yerine sadece google.
triplebig

226

basename komutuna bakın:

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

11
Muhtemelen şu anda sunulan tüm çözümlerin en basiti ... backticks yerine $ (...) kullanacak olsam da.
Michael Johnson

6
En basit ama bir bağımlılık ekler (çok büyük veya garip değil, itiraf ediyorum). Ayrıca soneki bilmek gerekir.
Vinko Vrsalovic

Ve sondan bir şey kaldırmak için kullanılabilir, temelde sadece bir dize kaldırma yapar.
Smar

2
Sorun zaman isabeti. Ben basename kullanarak 800 dosyaları işlemek için neredeyse 5 dakika almak bash izledikten sonra bu tartışma için soruyu araştırdım. Yukarıdaki regex yöntemi kullanılarak, süre yaklaşık 7 saniyeye düşürüldü. Bu cevap programcı için daha kolay olsa da, isabet süresi çok fazladır. İçinde birkaç bin dosya bulunan bir klasör düşünün! Bazı klasörlerim var.
xizdaqrian

@xizdaqrian Bu kesinlikle yanlış. Bu, geri dönmesi yarım saniye sürmemesi gereken basit bir programdır. Az önce time / home / me / dev -name "* .py" .py -exec basename {} \; ve toplamda 1 saniyede 1500 dosya için uzantı ve dizini çıkardı.
Laszlo Treszkai

40

İki ayrı işlemle yapılan saf bash:

  1. Yolu yol dizesinden kaldırın:

    path=/foo/bar/bim/baz/file.gif
    
    file=${path##*/}  
    #$file is now 'file.gif'
  2. Uzantıyı bir yol dizesinden kaldırın:

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

18

Basename kullanarak bunu başarmak için aşağıdakileri kullandım:

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

    # Do things with $fname
done;

Bu, dosya uzantısı hakkında önceden bilgi gerektirmez ve dosya adında (uzantısının önünde) noktalara sahip bir dosya adınız olsa bile çalışır; basenameYine de program gerektiriyor , ancak bu GNU coreutils'in bir parçası olduğu için herhangi bir dağıtımla birlikte gönderilmelidir.


1
Mükemmel cevap! uzantıyı çok temiz bir şekilde kaldırır, ancak. dosya adının sonunda.
metrix

3
@metrix sadece "." $ ext'den önce, yani: fname=`basename $file .$ext`
Carlos Troncoso

13

Saf bash yolu:

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

Bu işlevsellik insan bashında "Parameter Expansion" altında açıklanmaktadır. Bash olmayan yollar boldur: awk, perl, sed vb.

DÜZENLEME: Dosya soneklerindeki noktalarla çalışır ve soneki (uzantı) bilmesine gerek yoktur , ancak adın kendisindeki noktalarla çalışmaz .


11

Taban adı ve dizin adı işlevleri peşinde olduğunuz şeydir:

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

Çıktı var:

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

1
Burada alıntı düzeltmek için yararlı olacaktır - belki aracılığıyla bu çalıştırmak shellcheck.net ile mystring=$1ve adres sorunları o (boşluk / glob karakterleri / vs içeren değil belli olan birkaç uyarı bastırmak olacaktır) yerine geçerli sabit değer bulur?
Charles Duffy

$ Mystring'deki tırnak işaretlerini desteklemek için bazı uygun değişiklikler yaptım. Tanrım bu çok uzun zaman önce yazdım :)
Jerub

: Sonuçları alıntı başka gelişme olurdu echo "basename: $(basename "$mystring")"eğer bu şekilde - mystring='/foo/*'sen alamadım *geçerli dizinde dosyaların listesini ile değiştirilir sonra basename bittikten.
Charles Duffy

6

Kullanılması basenameo, dosya uzantısı olduğunu biliyorum gelmez varsayar?

Ve çeşitli düzenli ifade önerilerinin birden fazla "" içeren bir dosya adıyla baş etmediğine inanıyorum.

Aşağıdaki çift nokta ile başa çıkmak gibi görünüyor. Oh ve kendileri "/" içeren dosya adları (sadece tekmeler için)

Pascal'ı açıklamak için, "Üzgünüm, bu senaryo çok uzun. Daha kısa yapmak için zamanım yoktu"


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

1
Bu güzel ve sağlam
Gaurav Jain

4

Bu cevapta kullanılan POSIX uyumlu sözdizimine ek olarak ,

basename string [suffix]

de olduğu gibi

basename /foo/fizzbuzz.bar .bar

GNUbasename başka bir sözdizimini destekler:

basename -s .bar /foo/fizzbuzz.bar

aynı sonucu verir. Fark ve avantaj, birden fazla argümanı destekleyen şu -sanlama gelir -a:

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

Bu, -zörneğin boşluklar, yeni satırlar ve glob karakterleri (alıntılanan ls) içeren dosyalar için , seçeneği kullanarak çıktıyı NUL baytlarıyla ayırarak dosya adı açısından güvenli hale getirilebilir :

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

Bir diziye okuma:

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

readarray -dBash 4.4 veya daha yenisini gerektirir. Daha eski sürümler için:

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

Ayrıca, belirtilen sonek varsa çıktıdan kaldırılır (ve aksi takdirde yoksayılır).
aksh1618


3

Basename'i diğer yayınlarda önerildiği gibi kullanamıyorsanız, her zaman sed kullanabilirsiniz. İşte (çirkin) bir örnek. En büyük değil, ancak istenen dizeyi ayıklayarak ve girdiyi istenen dizeyle değiştirerek çalışır.

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

Hangi çıktıyı alacak

fizzbuzz


Bu orijinal sorunun cevabı olmasına rağmen, bu komut, bir dosyada ekrana yazdırmak için temel adları ayıklamak için yol satırları olduğunda kullanışlıdır.
Sangcheol Choi

2

Önerilen perl çözümüne dikkat edin: ilk noktadan sonra her şeyi kaldırır.

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

Perl ile yapmak istiyorsanız, bu işe yarar:

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

Ancak Bash kullanıyorsanız, y=${x%.*}(veya basename "$x" .extuzantıyı biliyorsanız) ile çözümler çok daha basittir.


1

Taban adı bunu yapar, yolu kaldırır. Ayrıca, sonek verilirse ve dosyanın sonekiyle eşleşirse kaldırır, ancak komuta vermek için son eki bilmeniz gerekir. Aksi takdirde mv'yi kullanabilir ve yeni adın başka ne olması gerektiğini anlayabilirsiniz.


1

Tam yolu kullanmadan dosya adını almak için en yüksek puanlı cevabı ikinci en yüksek puanlı cevapla birleştirmek:

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

Neden burada bir dizi kullanıyorsunuz? Ayrıca, neden basename kullanıyorsunuz?
codeforester
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.