Bashのパス文字列からファイルサフィックスとパス部分を削除するにはどうすればよいですか?


回答:


574

Bashで#および%演算子を使用してこれを行う方法を次に示します。

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

${x%.bar}また${x%.*}、ドットの後のすべてを削除することも${x%%.*}、最初のドットの後のすべてを削除することもできます。

例:

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

ドキュメントはBashのマニュアルにあります。探し${parameter%word}${parameter%%word}部分マッチング部末尾。


これは最も柔軟性があり、他にも同様にやりたいことがいくつかあったので、これを使用してしまいました。
ローレンスジョンストン

これはおそらく投稿されたすべての回答の中で最も柔軟性がありますが、basenameコマンドとdirnameコマンドを示唆する回答にも注意が必要だと思います。他の派手なパターンマッチングが必要ない場合は、これらの方法でうまくいくかもしれません。
mgadda

7
これは$ {x%.bar}と呼ばれますか?それについてもっと知りたいです。
バジル

14
@Basil:パラメータ拡張。コンソールで「man bash」と入力し、次に「/ parameter Expansion」と入力します
Zan Lynx

1
「man bash」の説明は、それがすでに何をしているのかを知っている場合、または自分で試してみた場合に理にかなっていると思います。gitリファレンスと同じくらい悪いです。代わりにググってみます。
triplebig 2016

226

basenameコマンドを見てください。

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

11
おそらく現在提供されているすべてのソリューションの中で最も単純なものですが...バッククォートの代わりに$(...)を使用します。
マイケルジョンソン

6
最も単純ですが、依存関係を追加します(巨大でも奇妙なものでもない、私は認めます)。また、サフィックスも知っている必要があります。
Vinko Vrsalovic 2008

そして、最後から何かを削除するために使用できます。基本的には、最後から文字列を削除するだけです。
Smar 2011

2
問題はヒット時間です。bashがbasenameを使用して800ファイルを処理するのに約5分かかるのを見た後、このディスカッションの質問を検索しました。上記の正規表現方法を使用すると、時間は約7秒に短縮されました。この答えは、プログラマにとっては実行が簡単ですが、時間のヒットが多すぎます。数千のファイルが入ったフォルダを想像してみてください!そのようなフォルダがいくつかあります。
xizdaqrian

@xizdaqrianこれは間違いです。これは簡単なプログラムで、戻るのに0.5秒かかることはありません。時間を実行しましたfind / home / me / dev -name "* .py" .py -exec basename {} \; そして、合計1秒で1500ファイルの拡張子とディレクトリを取り除きました。
Laszlo Treszkai

40

純粋なbash、2つの別々の操作で行われます:

  1. パス文字列からパスを削除します。

    path=/foo/bar/bim/baz/file.gif
    
    file=${path##*/}  
    #$file is now 'file.gif'
  2. パス文字列から拡張子を削除します。

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

18

basenameを使用して、これを実現するために以下を使用しました。

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

    # Do things with $fname
done;

これは、ファイル拡張子についての事前の知識を必要とせず、ファイル名にファイル名(拡張子の前)にドットが含まれている場合でも機能します。basenameただし、プログラムは必要ですが、これはGNU coreutilsの一部であるため、ディストリビューションに付属しています。


1
正解です。非常にクリーンな方法で拡張機能を削除しますが、は削除しません。ファイル名の最後に。
metrix 14

3
@metrixは「。」を追加するだけです $ extの前、つまり: fname=`basename $file .$ext`
Carlos Troncoso 2016年

13

純粋なbashの方法:

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

この機能は、man bashの「Parameter Expansion」で説明されています。bash以外の方法はたくさんあります:awk、perl、sedなど。

編集:ファイル接尾辞のドットで機能し、接尾辞(拡張子)を知る必要はありませんが、名前自体のドットでは機能しません


11

basename関数とdirname関数はあなたが求めているものです:

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

出力あり:

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

1
ここで引用を修正することは役に立ちます-多分これを現在の定数値(これはいくつかの警告を抑制し、スペース/グロブ文字などを含まないことが確実です)ではなくshellcheck.netを介して実行しmystring=$1、問題に対処します見つける?
チャールズダフィー

まあ、私は$ mystringの引用符をサポートするためにいくつかの適切な変更を加えました。これはかなり前のことですが、私はこれを書きました:)
Jerub

結果を引用するとさらに改善されます:echo "basename: $(basename "$mystring")"- 完了後に現在のディレクトリあるファイルのリストで置き換えられmystring='/foo/*'ない場合。* basename
Charles Duffy

6

を使用するbasenameと、ファイルの拡張子が何であるかがわかっていると思いますか?

また、さまざまな正規表現の提案では、複数の「。」を含むファイル名には対応できないと思います。

以下はダブルドットに対応しているようです。ああ、それ自体が「/」を含むファイル名(キックのみ)

Pascalを言い換えると、「申し訳ありませんが、このスクリプトは長すぎます。短くする時間はありませんでした」


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

1
これは素晴らしく堅牢です
Gaurav Jain 2013

4

この回答で使用されているPOSIX準拠の構文に加えて、

basename string [suffix]

のように

basename /foo/fizzbuzz.bar .bar

GNUbasenameは別の構文をサポートしています。

basename -s .bar /foo/fizzbuzz.bar

同じ結果。違いと利点は、複数の引数をサポートするを-s意味すること-aです。

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

これは、-zオプションを使用して出力をNULバイトで分離することにより、ファイル名を安全にすることもできます。たとえば、空白、改行、グロブ文字(で引用ls)を含むこれらのファイルの場合:

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

配列への読み込み:

$ 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以降が必要です。古いバージョンの場合、ループする必要があります。

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

また、指定されたサフィックスは存在する場合は出力から削除されます(それ以外の場合は無視されます)。
aksh1618


3

他の投稿で提案されているようにbasenameを使用できない場合は、常にsedを使用できます。これは(醜い)例です。これは最高ではありませんが、必要な文字列を抽出し、入力を必要な文字列に置き換えることで機能します。

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

あなたは出力を得るでしょう

フィズバズ


これは元の質問に対する回答ですが、このコマンドは、ファイルにパスの行があり、ベース名を抽出して画面に出力する場合に役立ちます。
Sangcheol Choi 2013

2

提案されたperlソリューションに注意してください。最初のドット以降は削除されます。

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

あなたがperlでそれをしたいなら、これはうまくいきます:

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

しかし、Bashを使用している場合はy=${x%.*}(またはbasename "$x" .ext拡張機能を知っている場合)のソリューションははるかに簡単です。


1

ベース名はそれを行い、パスを削除します。また、指定されている場合、およびファイルのサフィックスと一致する場合は、サフィックスを削除しますが、コマンドに与えるサフィックスを知っている必要があります。それ以外の場合は、mvを使用して、他の方法で新しい名前を指定する必要があります。


1

最高評価の回答を2番目に評価の高い回答と組み合わせて、フルパスなしでファイル名を取得します。

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

なぜここで配列を使用しているのですか?また、なぜベース名を使用するのですか?
codeforester 2018年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.