Bashでファイル名と拡張子を抽出する


2109

ファイル名(拡張子なし)と拡張子を別々に取得したい。

これまでに見つけた最良の解決策は次のとおりです。

NAME=`echo "$FILE" | cut -d'.' -f1`
EXTENSION=`echo "$FILE" | cut -d'.' -f2`

ファイル名に複数の.文字が含まれていると機能しないため、これは誤りです。もし私が持っているとしましょうa.b.js、それはaand b.jsではなくand を考慮a.bjsます。

Pythonで簡単に行うことができます

file, ext = os.path.splitext(path)

しかし、可能であれば、この目的のためだけにPythonインタープリターを起動しない方がよいでしょう。

より良いアイデアはありますか?


この質問では、このbashテクニックと他のいくつかの関連するテクニックについて説明します。
jjclarkson 2009年

28
下記の偉大な答えを適用するとき、私はここに示したように、単にあなたの変数に貼り付けられません間違っ: extension="{$filename##*.}"私はしばらくの間行ったように!$カーリーの外側を移動:右: extension="${filename##*.}"
クリスK

4
これは明らかに重要な問題であり、私にとっては、以下の回答が完全に正しいかどうかを判断するのは困難です。これが(ba)shの組み込み操作ではないのは驚くべきことです(回答は、パターンマッチングを使用して関数を実装しているようです)。os.path.splitext代わりに上記のPythonを使用することにしました...
Peter Gibson、

1
拡張表現するために持っている自然のファイルを、そこにある魔法の彼の自然とoffert占うために、ファイルをチェックし、コマンドの標準的な拡張を私の回答を
F. Hauri

2
質問はそもそも問題があります。OSとunixファイルシステムの観点から、一般に、ファイル拡張子などはありません。を使って "。" パーツを分離することは人間の慣習であり、人間がそれに従うことに同意する場合にのみ機能します。たとえば、「tar」プログラムを使用すると、出力ファイルに「tar」という名前を付けることができます。「.tar」サフィックスの代わりにプレフィックス-「somedir.tar」の代わりに「tar.somedir」を与える。このため、「一般的で常に機能する」ソリューションはありません。特定のニーズと予想されるファイル名に一致するコードを記述する必要があります。
CM

回答:


3503

まず、パスなしでファイル名を取得します。

filename=$(basename -- "$fullfile")
extension="${filename##*.}"
filename="${filename%.*}"

または、「。」ではなく、パスの最後の「/」に注目することもできます。これは、予期しないファイル拡張子がある場合でも機能するはずです。

filename="${fullfile##*/}"

あなたはドキュメントをチェックしたいかもしれません:


85
完全な機能セットについては、gnu.org / software / bash / manual / html_node /…を確認してください。
D.Shawley 09年

24
"$ fullfile"に引用符を追加すると、ファイル名が壊れるおそれがあります。
lhunath 2009年

47
ヘック、あなたができたとしても、書き込みファイル名=「$ {FULLFILE ## * /}」と回避余分な呼び出しbasename
ephemient

45
この「解決策」は、ファイルに拡張子がない場合は機能しません。代わりに、ファイル名全体が出力されます。これは、拡張子のないファイルがいたるところに存在することを考えると非常に悪いです。
nccc

43
拡張子のないファイル名を処理するための修正:extension=$([[ "$filename" = *.* ]] && echo ".${filename##*.}" || echo '')。拡張子があればという注意である現在、それは初期含めて返されます.、例えば、.txt
mklement0 2012

684
~% FILE="example.tar.gz"

~% echo "${FILE%%.*}"
example

~% echo "${FILE%.*}"
example.tar

~% echo "${FILE#*.}"
tar.gz

~% echo "${FILE##*.}"
gz

詳細については、Bashマニュアルのシェルパラメータの展開をご覧ください。


22
ファイル名の「拡張子」の部分に.tar.gzのように2つのドットが含まれている場合、どうすればよいかという疑問をあなたは(おそらく意図せずに)提起します。可能なすべての有効なファイル拡張子を事前に知らなければ解決できません。
rmeador 2009年

8
なぜ解決できないのですか?私の例では、ファイルには2つのドットの付いた拡張子ではなく、2つの拡張子が含まれていると考える必要があります。両方の拡張を個別に処理します。
ジュリアーノ

22
字句ベースでは解決できません。ファイルタイプを確認する必要があります。あなたが呼ばれるゲームがあったら考えてみましょうdinosaurs.in.tar、あなたはそれをgzipで圧縮dinosaurs.in.tar.gz:)
porges

11
フルパスで渡す場合、これはさらに複雑になります。私の1人には「。」がありました。パスの途中のディレクトリにありますが、ファイル名にはありません。例 "a / bc / d / e / filename"は ".c / d / e / filename"を巻き上げます
Walt Sellers

6
明らかにno x.tar.gzの拡張子はgzなく、ファイル名x.tarはそれだけです。二重拡張のようなものはありません。boost :: filesystemがそれをそのように処理するのは確かだ。(スプリットパス、change_extension ...)そして私が間違っていない場合、その動作はpythonに基づいています。
v.oddou 2013年

431

通常、拡張機能はすでに知っているので、次のように使用することをお勧めします。

basename filename .extension

例えば:

basename /path/to/dir/filename.txt .txt

そして私たちは

filename

61
その2番目の引数basenameは、非常に
目を見張る

10
そして、この手法を使用して拡張を抽出する方法は?;) あ、待って!私たちは実際にそれを前もって知りません。
Tomasz Gandor

3
.zipまたはで終わる圧縮ディレクトリがあるとします.ZIP。あなたが何かをすることができる方法はありますbasename $file {.zip,.ZIP}か?
Dennis

8
これはOPの質問の一部にしか答えませんが、Googleに入力した質問には答えます。:-)非常に滑らかです!
sudo make install

1
簡単でPOSIX準拠
gpanda

147

POSIXパラメータ拡張の魔法を使うことができます:

bash-3.2$ FILENAME=somefile.tar.gz
bash-3.2$ echo "${FILENAME%%.*}"
somefile
bash-3.2$ echo "${FILENAME%.*}"
somefile.tar

ファイル名が次の形式の./somefile.tar.gz場合echo ${FILENAME%%.*}、最長一致を貪欲に削除し.、空の文字列になることに注意してください。

(一時的な変数でそれを回避することができます:

FULL_FILENAME=$FILENAME
FILENAME=${FULL_FILENAME##*/}
echo ${FILENAME%%.*}


このサイトで詳しく説明しています。

${variable%pattern}
  Trim the shortest match from the end
${variable##pattern}
  Trim the longest match from the beginning
${variable%%pattern}
  Trim the longest match from the end
${variable#pattern}
  Trim the shortest match from the beginning

5
ヨアヒムの答えよりもはるかに簡単ですが、私は常にPOSIX変数置換を調べる必要があります。また、これはcutがない--complementとないMax OSXで実行されsedます-r
jwadsa​​ck 14

72

ファイルに拡張子がない場合やファイル名がない場合は、機能しないようです。これが私が使っているものです。ビルトインのみを使用し、より多くの(すべてではない)病理学的ファイル名を処理します。

#!/bin/bash
for fullpath in "$@"
do
    filename="${fullpath##*/}"                      # Strip longest match of */ from start
    dir="${fullpath:0:${#fullpath} - ${#filename}}" # Substring from 0 thru pos of filename
    base="${filename%.[^.]*}"                       # Strip shortest match of . plus at least one non-dot char from end
    ext="${filename:${#base} + 1}"                  # Substring from len of base thru end
    if [[ -z "$base" && -n "$ext" ]]; then          # If we have an extension and no base, it's really the base
        base=".$ext"
        ext=""
    fi

    echo -e "$fullpath:\n\tdir  = \"$dir\"\n\tbase = \"$base\"\n\text  = \"$ext\""
done

そしてここにいくつかのテストケースがあります:

$ basename-and-extension.sh / / home / me / / home / me / file /home/me/file.tar /home/me/file.tar.gz /home/me/.hidden / home / me / .hidden.tar / home / me / ..。
/:
    dir = "/"
    ベース= ""
    ext = ""
/ home / me /:
    dir = "/ home / me /"
    ベース= ""
    ext = ""
/ home / me / file:
    dir = "/ home / me /"
    base = "ファイル"
    ext = ""
/home/me/file.tar:
    dir = "/ home / me /"
    base = "ファイル"
    ext = "tar"
/home/me/file.tar.gz:
    dir = "/ home / me /"
    base = "file.tar"
    ext = "gz"
/home/me/.hidden:
    dir = "/ home / me /"
    base = ".hidden"
    ext = ""
/home/me/.hidden.tar:
    dir = "/ home / me /"
    base = ".hidden"
    ext = "tar"
/ home / me / ..:
    dir = "/ home / me /"
    ベース= ".."
    ext = ""
。:
    dir = ""
    base = "。"
    ext = ""

2
代わりにdir="${fullpath:0:${#fullpath} - ${#filename}}"私はよく見ましたdir="${fullpath%$filename}"。書く方が簡単です。実際の速度の違いや落とし穴があるかどうかはわかりません。
dubiousjim

2
これは#!/ bin / bashを使用しますが、これはほとんどの場合間違っています。可能であれば#!/ bin / shを、そうでなければ#!/ usr / bin / env bashを優先します。
いい人

@良い人:私はそれがほとんど常に間違っている方法を知りません:which bash-> /bin/bash; おそらくそれはあなたのディストリビューションですか?
vol7ron 2013

2
@ vol7ron-多くのディストリビューションでは、bashは/ usr / local / bin / bashにあります。OSXでは、多くの人が更新されたbashを/ opt / local / bin / bashにインストールします。そのため、/ bin / bashは誤りであり、envを使用して検索する必要があります。/ bin / shとPOSIXコンストラクトを使用するのがさらに良いでしょう。solarisを除いて、これはPOSIXシェルです。
グッドパーソン

2
@GoodPersonですが、bashに慣れているなら、なぜshを使うのですか?shを使用できるのに、なぜPerlを使用するのですか?
vol7ron 2013

46

使用できますbasename

例:

$ basename foo-bar.tar.gz .tar.gz
foo-bar

削除する拡張子をベース名に指定する必要はありますが、常にで実行tarしている-z場合は、拡張子がであることはわかっています.tar.gz

これはあなたが望むことをするはずです:

tar -zxvf $1
cd $(basename $1 .tar.gz)

2
cd $(basename $1 .tar.gz).gzファイルで動作すると思います。しかし問題に彼は言及しましたArchive files have several extensions: tar.gz, tat.xz, tar.bz2
SS Hegde 2013

Tomi Poは2年前に同じものを投稿しました。
phil294 '16 / 09/17

こんにちはBlauhirn、これは古い質問です。日付に何かが起こったと思います。質問された直後に質問に答えたことをはっきりと覚えています。質問が別の質問とマージされたのでしょうか?
Bjarke Freund-Hansen 2017

うん、私は正しく覚えています。私はもともとこの質問に答えましたstackoverflow.com/questions/14703318/…質問されたその日に2年後にこの質問に統合されました。私の答えがこのように動かされたとき、私は重複した答えに対してほとんど非難されることができません。
Bjarke Freund-Hansen 2017

37
pax> echo a.b.js | sed 's/\.[^.]*$//'
a.b
pax> echo a.b.js | sed 's/^.*\.//'
js

うまく機能するので、次のように使用できます:

pax> FILE=a.b.js
pax> NAME=$(echo "$FILE" | sed 's/\.[^.]*$//')
pax> EXTENSION=$(echo "$FILE" | sed 's/^.*\.//')
pax> echo $NAME
a.b
pax> echo $EXTENSION
js

ちなみに、コマンドは次のように機能します。

のコマンドはNAME"."文字の後に"."行末までの任意の数の非文字が続くものを何も置換しません(つまり、最後"."から行末まですべてを削除します)。これは基本的に、正規表現のトリックを使用した貪欲でない置換です。

のコマンドは、行の先頭のEXTENSION任意の数の文字の後に文字を置き、何も置換"."しません(つまり、行の先頭から最後のドットまでをすべて削除します)。これは、デフォルトのアクションである貪欲な置換です。


これは、名前と拡張子が同じように印刷されるため、拡張子のないファイルでは機能しません。したがって、私sed 's,\.[^\.]*$,,'は名前とsed 's,.*\.,., ;t ;g'拡張子に使用します(一般的なコマンドとともに、非定型コマンドtestgetコマンドを使用しますsubstitute)。
hIpPy 2018年

32

メレンはブログの投稿にコメントを書いています:

Bashを使用${file%.*}すると、拡張子なしでファイル名${file##*.}を取得したり、拡張子のみを取得したりすることもできます。あれは、

file="thisfile.txt"
echo "filename: ${file%.*}"
echo "extension: ${file##*.}"

出力:

filename: thisfile
extension: txt


29

気にする必要はありませんawksed偶数かperlこの単純なタスクのために。os.path.splitext()パラメータ拡張のみを使用する、pure-Bash 互換のソリューションがあります。

リファレンス実装

のドキュメントos.path.splitext(path)

ペアにパス名のパスを分割する(root, ext)ようにroot + ext == pathし、extは空であるか、ピリオドで始まり、多くても1回の周期で含まれています。ベース名の先頭のピリオドは無視されます。splitext('.cshrc')を返します('.cshrc', '')

Pythonコード:

root, ext = os.path.splitext(path)

Bashの実装

先行期間を尊重する

root="${path%.*}"
ext="${path#"$root"}"

先行期間を無視する

root="${path#.}";root="${path%"$root"}${root%.*}"
ext="${path#"$root"}"

テスト

以下は、先行期間無視の実装のテストケースです。これは、すべての入力でPythonリファレンス実装と一致する必要があります。

|---------------|-----------|-------|
|path           |root       |ext    |
|---------------|-----------|-------|
|' .txt'        |' '        |'.txt' |
|' .txt.txt'    |' .txt'    |'.txt' |
|' txt'         |' txt'     |''     |
|'*.txt.txt'    |'*.txt'    |'.txt' |
|'.cshrc'       |'.cshrc'   |''     |
|'.txt'         |'.txt'     |''     |
|'?.txt.txt'    |'?.txt'    |'.txt' |
|'\n.txt.txt'   |'\n.txt'   |'.txt' |
|'\t.txt.txt'   |'\t.txt'   |'.txt' |
|'a b.txt.txt'  |'a b.txt'  |'.txt' |
|'a*b.txt.txt'  |'a*b.txt'  |'.txt' |
|'a?b.txt.txt'  |'a?b.txt'  |'.txt' |
|'a\nb.txt.txt' |'a\nb.txt' |'.txt' |
|'a\tb.txt.txt' |'a\tb.txt' |'.txt' |
|'txt'          |'txt'      |''     |
|'txt.pdf'      |'txt'      |'.pdf' |
|'txt.tar.gz'   |'txt.tar'  |'.gz'  |
|'txt.txt'      |'txt'      |'.txt' |
|---------------|-----------|-------|

試験結果

すべてのテストに合格しました。


2
いいえ、のベースファイル名はtext.tar.gzでありtext、拡張子は.tar.gz
frederick99

2
@ frederick99前述のとおり、ここでのソリューションos.path.splitextはPythonのの実装と一致します。論争の的になる可能性のある入力に対してその実装が正気であるかどうかは、別のトピックです。
Cyker、

パターン("$root")内の引用符はどのように機能しますか?省略された場合はどうなりますか?(この件についてのドキュメントは見つかりませんでした。)また、これにより*、ファイル名が含ま?れているファイル名をどのように処理しますか?
ymett

テストでは、引用符がパターンを文字通りにしていること、つまり 特別*?はないことがわかりました。したがって、私の質問の2つの部分は互いに答えます。これが文書化されていないことは正しいですか?それとも、引用がグロブ展開を一般的に無効にするという事実から理解されるはずですか?
ymett

素晴らしい答え!ルートを計算するための少し単純なバリアントを提案します:root="${path#?}";root="${path::1}${root%.*}"—次に、同じようにして拡張を抽出します。
Maëlan

26

cutコマンドを使用して、最後の2つの拡張(".tar.gz"部分)を削除できます。

$ echo "foo.tar.gz" | cut -d'.' --complement -f2-
foo

コメントでクレイトン・ヒューズが述べたように、これは質問の実際の例では機能しません。したがって、代替としてsed、次のような拡張正規表現を使用することを提案します。

$ echo "mpc-1.0.1.tar.gz" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'
mpc-1.0.1

最後の2つの(英数字)拡張を無条件に削除することで機能します。

[Anders Lindahlからのコメントの後に再度更新]


4
これは、ファイル名/パスに他のドットが含まれていない場合にのみ機能します。echo "mpc-1.0.1.tar.gz" | カット-d '。' --complement -f2-は "mpc-1"を生成します(。で区切った後の最初の2フィールドのみ)
Clayton Hughes

@ClaytonHughesあなたは正しい、そして私はそれをよりよくテストすべきだった。別のソリューションを追加しました。
一部のプログラマは

sed式は$、一致する拡張子がファイル名の末尾にあることを確認するために使用する必要があります。そうしないと、などのファイル名でi.like.tar.gz.files.tar.bz2予期しない結果が生じる可能性があります。
Anders Lindahl、

@AndersLindahl拡張の順序がsedチェーンの順序の逆の場合でも、そうなります。でも、と$最後にファイル名などmpc-1.0.1.tar.bz2.tar.gzの両方が削除され.tar.gz、その後.tar.bz2
一部のプログラマは、

$ echo "foo.tar.gz" | カット-d '。' -f2- WITHOUT --complementは、2番目の分割項目を文字列の最後に取得します$ echo "foo.tar.gz" | カット-d '。' -f2- tar.gz
Gene Black

23

awkソフトウェアパッケージのバージョン番号の抽出など、いくつかの高度な使用例を含む、いくつかの代替提案(主に)を次に示します。

f='/path/to/complex/file.1.0.1.tar.gz'

# Filename : 'file.1.0.x.tar.gz'
    echo "$f" | awk -F'/' '{print $NF}'

# Extension (last): 'gz'
    echo "$f" | awk -F'[.]' '{print $NF}'

# Extension (all) : '1.0.1.tar.gz'
    echo "$f" | awk '{sub(/[^.]*[.]/, "", $0)} 1'

# Extension (last-2): 'tar.gz'
    echo "$f" | awk -F'[.]' '{print $(NF-1)"."$NF}'

# Basename : 'file'
    echo "$f" | awk '{gsub(/.*[/]|[.].*/, "", $0)} 1'

# Basename-extended : 'file.1.0.1.tar'
    echo "$f" | awk '{gsub(/.*[/]|[.]{1}[^.]+$/, "", $0)} 1'

# Path : '/path/to/complex/'
    echo "$f" | awk '{match($0, /.*[/]/, a); print a[0]}'
    # or 
    echo "$f" | grep -Eo '.*[/]'

# Folder (containing the file) : 'complex'
    echo "$f" | awk -F'/' '{$1=""; print $(NF-1)}'

# Version : '1.0.1'
    # Defined as 'number.number' or 'number.number.number'
    echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?'

    # Version - major : '1'
    echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f1

    # Version - minor : '0'
    echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f2

    # Version - patch : '1'
    echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f3

# All Components : "path to complex file 1 0 1 tar gz"
    echo "$f" | awk -F'[/.]' '{$1=""; print $0}'

# Is absolute : True (exit-code : 0)
    # Return true if it is an absolute path (starting with '/' or '~/'
    echo "$f" | grep -q '^[/]\|^~/'

すべての使用例では、中間結果に依存することなく、元の完全パスを入力として使用しています。


20

受け入れ答えはでうまく機能典型的なが、中に失敗したエッジの場合、すなわち、:

  • 拡張子のないファイル名(この回答の残りではサフィックスと呼ばれます)のextension=${filename##*.}場合、空の文字列ではなく入力ファイル名を返します。
  • extension=${filename##*.}.慣習に反して、最初のは含まれていません。
    • 盲目的に前に付ける.ことは、サフィックスのないファイル名に対しては機能しませんでした。
  • filename="${filename%.*}"入力ファイル名がで始まり、.それ以上の.文字(例:)を含まない場合、.bash_profile規則に反して、空の文字列になります。

---------

したがって、すべてのエッジケースをカバーする堅牢なソリューションの複雑さは、関数を必要とします -以下の定義を参照してください。パスのすべてのコンポーネントを返すことができます

呼び出し例:

splitPath '/etc/bash.bashrc' dir fname fnameroot suffix
# -> $dir == '/etc'
# -> $fname == 'bash.bashrc'
# -> $fnameroot == 'bash'
# -> $suffix == '.bashrc'

入力パスの後の引数は自由に選択できることに注意してください。位置変数です。
関心のない変数の前にある変数をスキップするには、_(スローアウェイ変数を使用するために$_)または'';を指定します。たとえば、ファイル名のルートと拡張子のみを抽出するには、を使用しますsplitPath '/etc/bash.bashrc' _ _ fnameroot extension


# SYNOPSIS
#   splitPath path varDirname [varBasename [varBasenameRoot [varSuffix]]] 
# DESCRIPTION
#   Splits the specified input path into its components and returns them by assigning
#   them to variables with the specified *names*.
#   Specify '' or throw-away variable _ to skip earlier variables, if necessary.
#   The filename suffix, if any, always starts with '.' - only the *last*
#   '.'-prefixed token is reported as the suffix.
#   As with `dirname`, varDirname will report '.' (current dir) for input paths
#   that are mere filenames, and '/' for the root dir.
#   As with `dirname` and `basename`, a trailing '/' in the input path is ignored.
#   A '.' as the very first char. of a filename is NOT considered the beginning
#   of a filename suffix.
# EXAMPLE
#   splitPath '/home/jdoe/readme.txt' parentpath fname fnameroot suffix
#   echo "$parentpath" # -> '/home/jdoe'
#   echo "$fname" # -> 'readme.txt'
#   echo "$fnameroot" # -> 'readme'
#   echo "$suffix" # -> '.txt'
#   ---
#   splitPath '/home/jdoe/readme.txt' _ _ fnameroot
#   echo "$fnameroot" # -> 'readme'  
splitPath() {
  local _sp_dirname= _sp_basename= _sp_basename_root= _sp_suffix=
    # simple argument validation
  (( $# >= 2 )) || { echo "$FUNCNAME: ERROR: Specify an input path and at least 1 output variable name." >&2; exit 2; }
    # extract dirname (parent path) and basename (filename)
  _sp_dirname=$(dirname "$1")
  _sp_basename=$(basename "$1")
    # determine suffix, if any
  _sp_suffix=$([[ $_sp_basename = *.* ]] && printf %s ".${_sp_basename##*.}" || printf '')
    # determine basename root (filemane w/o suffix)
  if [[ "$_sp_basename" == "$_sp_suffix" ]]; then # does filename start with '.'?
      _sp_basename_root=$_sp_basename
      _sp_suffix=''
  else # strip suffix from filename
    _sp_basename_root=${_sp_basename%$_sp_suffix}
  fi
  # assign to output vars.
  [[ -n $2 ]] && printf -v "$2" "$_sp_dirname"
  [[ -n $3 ]] && printf -v "$3" "$_sp_basename"
  [[ -n $4 ]] && printf -v "$4" "$_sp_basename_root"
  [[ -n $5 ]] && printf -v "$5" "$_sp_suffix"
  return 0
}

test_paths=(
  '/etc/bash.bashrc'
  '/usr/bin/grep'
  '/Users/jdoe/.bash_profile'
  '/Library/Application Support/'
  'readme.new.txt'
)

for p in "${test_paths[@]}"; do
  echo ----- "$p"
  parentpath= fname= fnameroot= suffix=
  splitPath "$p" parentpath fname fnameroot suffix
  for n in parentpath fname fnameroot suffix; do
    echo "$n=${!n}"
  done
done

関数を実行するテストコード:

test_paths=(
  '/etc/bash.bashrc'
  '/usr/bin/grep'
  '/Users/jdoe/.bash_profile'
  '/Library/Application Support/'
  'readme.new.txt'
)

for p in "${test_paths[@]}"; do
  echo ----- "$p"
  parentpath= fname= fnameroot= suffix=
  splitPath "$p" parentpath fname fnameroot suffix
  for n in parentpath fname fnameroot suffix; do
    echo "$n=${!n}"
  done
done

予想される出力-エッジケースに注意してください。

  • 接尾辞のないファイル名
  • で始まるファイル名.(サフィックスの開始とは見なされません
  • で終わる入力パス/(末尾/は無視されます)
  • ファイル名のみの入力パス(.親パスとして返されます)
  • .接頭辞付きのトークンを超えるファイル名(最後のトークンのみがサフィックスと見なされます):
----- /etc/bash.bashrc
parentpath=/etc
fname=bash.bashrc
fnameroot=bash
suffix=.bashrc
----- /usr/bin/grep
parentpath=/usr/bin
fname=grep
fnameroot=grep
suffix=
----- /Users/jdoe/.bash_profile
parentpath=/Users/jdoe
fname=.bash_profile
fnameroot=.bash_profile
suffix=
----- /Library/Application Support/
parentpath=/Library
fname=Application Support
fnameroot=Application Support
suffix=
----- readme.new.txt
parentpath=.
fname=readme.new.txt
fnameroot=readme.new
suffix=.txt

19

(1行で)最小かつ最も単純なソリューションは次のとおりです。

$ file=/blaabla/bla/blah/foo.txt
echo $(basename ${file%.*}) # foo

それはの無駄な使い方ですecho。一般に、結果を表示する前に、シェルからの出力で空白のトークン化とワイルドカード拡張を実行するように特別に要求しない限り、echo $(command)は単純に記述した方がよいcommandでしょうcommand。クイズ:の出力はecho $(echo '*')何ですか(そして、それが本当に必要なものである場合は、本当に本当に本当に欲しいだけですecho *)。
tripleee 2017年

@triplee echoコマンドをまったく使用しませんでした。私fooは、2行目の結果として3行目に表示される結果を示すために使用しました。
Ron

しかしbasename "${file%.*}"、同じことをするだけです。コマンド置換を使用してその出力をキャプチャしますが、echo同じ出力のみをすぐにキャプチャします。(引用なしでは、結果は名目上異なりますが、これはほとんど関連がなく、ここでは機能ではありません。)
tripleee

またbasename "$file" .txt、パラメーター置換の複雑さを回避します。
tripleee

1
@Ron時間を無駄にしたと非難する前に彼の最初のコメントを読んでください。
frederick99

14

ファイルの名前が必要なだけなら、これを試すことができると思います:

FULLPATH=/usr/share/X11/xorg.conf.d/50-synaptics.conf

# Remove all the prefix until the "/" character
FILENAME=${FULLPATH##*/}

# Remove all the prefix until the "." character
FILEEXTENSION=${FILENAME##*.}

# Remove a suffix, in our case, the filename. This will return the name of the directory that contains this file.
BASEDIRECTORY=${FULLPATH%$FILENAME}

echo "path = $FULLPATH"
echo "file name = $FILENAME"
echo "file extension = $FILEEXTENSION"
echo "base directory = $BASEDIRECTORY"

そして、これがすべて= Dです。


ただBASEDIRECTORYが欲しかった:)ありがとう!
Carlos Ricardo

12

すべてのフィールドと-、フィールド番号に追加された後続のフィールドを表示するように強制的にカットできます。

NAME=`basename "$FILE"`
EXTENSION=`echo "$NAME" | cut -d'.' -f2-`

したがって、FILEがのeth0.pcap.gz場合、EXTENSIONはpcap.gz

同じロジックを使用して、次のようにcutで '-'を使用してファイル名をフェッチすることもできます。

NAME=`basename "$FILE" | cut -d'.' -f-1`

これは、拡張子のないファイル名でも機能します。


8

マジックファイル認識

このStack Overflowの質問に対する多くの良い答えに加えて、私は追加したいと思います:

Linuxおよびその他のunixenの下には、という名前の魔法のコマンドがあり、fileファイルの最初のバイトを分析してファイルタイプを検出します。これは非常に古いツールであり、最初はプリントサーバーに使用されています(作成されていない場合...わかりません)。

file myfile.txt
myfile.txt: UTF-8 Unicode text

file -b --mime-type myfile.txt
text/plain

標準の拡張機能は/etc/mime.types(私のDebian GNU / Linuxデスクトップ上にあります。man fileおよびを参照してくださいman mime.types。おそらく、fileユーティリティとmime-supportパッケージをインストールする必要があります):

grep $( file -b --mime-type myfile.txt ) </etc/mime.types
text/plain      asc txt text pot brf srt

あなたが作成することができます 正しい拡張子を決定するための関数。少しの(完璧ではない)サンプルがあります:

file2ext() {
    local _mimetype=$(file -Lb --mime-type "$1") _line _basemimetype
    case ${_mimetype##*[/.-]} in
        gzip | bzip2 | xz | z )
            _mimetype=${_mimetype##*[/.-]}
            _mimetype=${_mimetype//ip}
            _basemimetype=$(file -zLb --mime-type "$1")
            ;;
        stream )
            _mimetype=($(file -Lb "$1"))
            [ "${_mimetype[1]}" = "compressed" ] &&
                _basemimetype=$(file -b --mime-type - < <(
                        ${_mimetype,,} -d <"$1")) ||
                _basemimetype=${_mimetype,,}
            _mimetype=${_mimetype,,}
            ;;
        executable )  _mimetype='' _basemimetype='' ;;
        dosexec )     _mimetype='' _basemimetype='exe' ;;
        shellscript ) _mimetype='' _basemimetype='sh' ;;
        * )
            _basemimetype=$_mimetype
            _mimetype=''
            ;;
    esac
    while read -a _line ;do
        if [ "$_line" == "$_basemimetype" ] ;then
            [ "$_line[1]" ] &&
                _basemimetype=${_line[1]} ||
                _basemimetype=${_basemimetype##*[/.-]}
            break
        fi
        done </etc/mime.types
    case ${_basemimetype##*[/.-]} in
        executable ) _basemimetype='' ;;
        shellscript ) _basemimetype='sh' ;;
        dosexec ) _basemimetype='exe' ;;
        * ) ;;
    esac
    [ "$_mimetype" ] && [ "$_basemimetype" != "$_mimetype" ] &&
      printf ${2+-v} $2 "%s.%s" ${_basemimetype##*[/.-]} ${_mimetype##*[/.-]} ||
      printf ${2+-v} $2 "%s" ${_basemimetype##*[/.-]}
}

この関数は、後で使用できるBash変数を設定できます。

(これは@Peteshの正解に触発されています):

filename=$(basename "$fullfile")
filename="${filename%.*}"
file2ext "$fullfile" extension

echo "$fullfile -> $filename . $extension"

8

わかりましたので、私が正しく理解している場合、ここでの問題は、複数の拡張子を持つファイルの名前と完全な拡張子を取得する方法stuff.tar.gzです。

これは私にとってはうまくいきます:

fullfile="stuff.tar.gz"
fileExt=${fullfile#*.}
fileName=${fullfile%*.$fileExt}

これによりstuff、ファイル名と.tar.gz拡張子が表示されます。0を含む任意の数の拡張機能で機能します。これが同じ問題を抱えている人に役立つことを願っています=)


正しい結果は(os.path.splitextOPが求めるものである)です('stuff.tar', '.gz')
Cyker、2016

6

次のスクリプトを使用します

$ echo "foo.tar.gz"|rev|cut -d"." -f3-|rev
foo

これはまったく効率的ではありません。このコマンドは、外部コマンドやフォークを必要とせずに純粋なB​​ashで実行できるため、非常に不必要な回数のフォークが多すぎます。
codeforester 2018年

5
$ F = "text file.test.txt"  
$ echo ${F/*./}  
txt  

これはファイル名に複数のドットとスペースを提供しますが、拡張子がない場合はファイル名自体を返します。しかし、簡単に確認できます。ファイル名と拡張子が同じかどうかをテストするだけです。

当然、この方法は.tar.gzファイルに対しては機能しません。ただし、これは2段階のプロセスで処理できます。拡張子がgzの場合は、tar拡張子もあるかどうかをもう一度確認してください。


5

魚のファイル名と拡張子を抽出する方法:

function split-filename-extension --description "Prints the filename and extension"
  for file in $argv
    if test -f $file
      set --local extension (echo $file | awk -F. '{print $NF}')
      set --local filename (basename $file .$extension)
      echo "$filename $extension"
    else
      echo "$file is not a valid file"
    end
  end
end

警告:最後のドットで分割します。これは、ドットが含まれているファイル名には適していますが、ドットが含まれている拡張子には適していません。以下の例を参照してください。

使用法:

$ split-filename-extension foo-0.4.2.zip bar.tar.gz
foo-0.4.2 zip  # Looks good!
bar.tar gz  # Careful, you probably want .tar.gz as the extension.

これを行うにはもっと良い方法があるでしょう。それを改善するために私の答えを自由に編集してください。


処理する拡張機能のセットが限られていて、それらすべてを知っている場合は、次のことを試してください。

switch $file
  case *.tar
    echo (basename $file .tar) tar
  case *.tar.bz2
    echo (basename $file .tar.bz2) tar.bz2
  case *.tar.gz
    echo (basename $file .tar.gz) tar.gz
  # and so on
end

これには最初の例のような警告はありませが、すべてのケースを処理する必要があるので、予想できる拡張機能の数によっては面倒になる可能性があります。


4

これがAWKのコードです。より簡単に行うことができます。しかし、私はAWKが苦手です。

filename$ ls
abc.a.txt  a.b.c.txt  pp-kk.txt
filename$ find . -type f | awk -F/ '{print $2}' | rev | awk -F"." '{$1="";print}' | rev | awk 'gsub(" ",".") ,sub(".$", "")'
abc.a
a.b.c
pp-kk
filename$ find . -type f | awk -F/ '{print $2}' | awk -F"." '{print $NF}'
txt
txt
txt

最後の例の最初のawkステートメントは必要ありませんよね?
BHSPitMonkey 2013

別の方法でAwkをAwkにパイプするのを避けることができsplit()ます。 awk -F / '{ n=split($2, a, "."); print a[n] }' uses / `を最上位の区切り文字として使用しますが、2番目のフィールドを分割.し、新しい配列の最後の要素を出力します。
tripleee 2017年

4

単に使う ${parameter%word}

あなたの場合:

${FILE%.*}

それをテストしたい場合は、以下のすべての作業を行い、拡張機能を削除してください:

FILE=abc.xyz; echo ${FILE%.*};
FILE=123.abc.xyz; echo ${FILE%.*};
FILE=abc; echo ${FILE%.*};

2
なぜ反対票か。=標識の周りにスペースがあってはいけませんが、それはまだ役に立ちます。
SilverWolf-モニカを

1
これは正常に動作します。ありがとうございました!(現在、それが反対投票された理由である場合、等号の周りにスペースがありません)
アレックス。S.

3

Peteshの回答から構築し、ファイル名だけが必要な場合は、パスと拡張子の両方を1行で削除できます。

filename=$(basename ${fullname%.*})

「basename:オペランドがありません。詳しくは、「basename --help」を試してください。」
Helmy

奇妙なことに、Bashを使用していますか?私の場合、バージョン3.2.25(古いCentOS)と4.3.30(Debian Jessie)の両方で、問題なく動作します。
cvr 2016

ファイル名にスペースがあるのでしょうか?使ってみるfilename="$(basename "${fullname%.*}")"
Adrian

の2番目の引数basenameはオプションですが、削除する拡張子を指定します。basenameシェルの組み込みで実際にこれらのすべての置換を実行できるため、置換は依然として有用かもしれませんが、実際には役に立たないかもしれません。
tripleee 2017年

3

@ mklement0の優れた、そしてランダムで有用なバシズムがぎっしり詰まっていることに主に基づいています -これに対する他の回答/他の質問/「インターネットをくそーだ」...私はそれを少し、少しわかりやすいものにまとめました、再利用可能な機能の私の(またはあなた)のため.bash_profileのより堅牢なバージョンがどうあるべきか(私は考える)の世話をするdirname/ basename/ 何があなたを持っています ...

function path { SAVEIFS=$IFS; IFS=""   # stash IFS for safe-keeping, etc.
    [[ $# != 2 ]] && echo "usage: path <path> <dir|name|fullname|ext>" && return    # demand 2 arguments
    [[ $1 =~ ^(.*/)?(.+)?$ ]] && {     # regex parse the path
        dir=${BASH_REMATCH[1]}
        file=${BASH_REMATCH[2]}
        ext=$([[ $file = *.* ]] && printf %s ${file##*.} || printf '')
        # edge cases for extensionless files and files like ".nesh_profile.coffee"
        [[ $file == $ext ]] && fnr=$file && ext='' || fnr=${file:0:$((${#file}-${#ext}))}
        case "$2" in
             dir) echo      "${dir%/*}"; ;;
            name) echo      "${fnr%.*}"; ;;
        fullname) echo "${fnr%.*}.$ext"; ;;
             ext) echo           "$ext"; ;;
        esac
    }
    IFS=$SAVEIFS
}     

使用例...

SOMEPATH=/path/to.some/.random\ file.gzip
path $SOMEPATH dir        # /path/to.some
path $SOMEPATH name       # .random file
path $SOMEPATH ext        # gzip
path $SOMEPATH fullname   # .random file.gzip                     
path gobbledygook         # usage: -bash <path> <dir|name|fullname|ext>

1
よくできました。いくつかの提案:- $IFSまったく依存していないようです(そうである場合は、local設定の効果をローカライズするために使用できます)。- local変数を使用する方が良い。-エラーメッセージは(を使用)ではstderrなくに出力され、ゼロ以外の終了コードが返されます。-名前を変更した方がよい(前者はdirコンポーネントを含むパスを提案する)。- オリジナルに(ピリオド)が無条件に追加されます。単にユーティリティを使用することもできますが、終了するは無視されることに注意してください。stdout1>&2fullnamebasenamename.basename/
mklement0 2013年

2

簡単な答え:

POSIX変数 answerを拡張するには、より興味深いパターンを実行できることに注意してください。したがって、ここで詳しく説明するケースでは、これを単純に行うことができます。

tar -zxvf $1
cd ${1%.tar.*}

これにより、最後の.tarがカットされます。<何か>

より一般的には、最後に出現したを削除したい場合。<何か><something-else> then

${1.*.*}

正常に動作するはずです。

上記の答えのリンクは死んでいるようです。ここでは、TLDPからBashで直接実行できる一連の文字列操作について説明します


大文字と小文字を区別せずに一致させる方法はありますか?
tonix 2015年

2

空の拡張機能も許可する場合は、これが私が思いつく最短のものです。

echo 'hello.txt' | sed -r 's/.+\.(.+)|.*/\1/' # EXTENSION
echo 'hello.txt' | sed -r 's/(.+)\..+|(.*)/\1\2/' # FILENAME

1行目の説明:PATH.EXTまたはANYTHINGに一致し、それをEXTに置き換えます。ANYTHINGが一致した場合、extグループはキャプチャされません。


2

これは私のために働いた唯一のものです:

path='folder/other_folder/file.js'

base=${path##*/}
echo ${base%.*}

>> file

これは文字列補間にも使用できますが、残念ながらbase事前に設定する必要があります。


1

以下は、大文字小文字の関係で名前が競合する場合に名前を一意にするBashスクリプトを作成したときに、ファイルの名前と拡張子を見つけるために使用したアルゴリズムです。

#! /bin/bash 

#
# Finds 
# -- name and extension pairs
# -- null extension when there isn't an extension.
# -- Finds name of a hidden file without an extension
# 

declare -a fileNames=(
  '.Montreal' 
  '.Rome.txt' 
  'Loundon.txt' 
  'Paris' 
  'San Diego.txt'
  'San Francisco' 
  )

echo "Script ${0} finding name and extension pairs."
echo 

for theFileName in "${fileNames[@]}"
do
     echo "theFileName=${theFileName}"  

     # Get the proposed name by chopping off the extension
     name="${theFileName%.*}"

     # get extension.  Set to null when there isn't an extension
     # Thanks to mklement0 in a comment above.
     extension=$([[ "$theFileName" == *.* ]] && echo ".${theFileName##*.}" || echo '')

     # a hidden file without extenson?
     if [ "${theFileName}" = "${extension}" ] ; then
         # hidden file without extension.  Fixup.
         name=${theFileName}
         extension=""
     fi

     echo "  name=${name}"
     echo "  extension=${extension}"
done 

テスト実行。

$ config/Name\&Extension.bash 
Script config/Name&Extension.bash finding name and extension pairs.

theFileName=.Montreal
  name=.Montreal
  extension=
theFileName=.Rome.txt
  name=.Rome
  extension=.txt
theFileName=Loundon.txt
  name=Loundon
  extension=.txt
theFileName=Paris
  name=Paris
  extension=
theFileName=San Diego.txt
  name=San Diego
  extension=.txt
theFileName=San Francisco
  name=San Francisco
  extension=
$ 

参考までに:完全な文字変換プログラムとその他のテストケースについては、https//www.dropbox.com/s/4c6m0f2e28a1vxf/avoid-clashes-code.zip?dl = 0をご覧ください。


:すべてのソリューションから、このファイルは拡張子を持つ持たない空の文字列を返すだけであるextension=$([[ "$theFileName" == *.* ]] && echo ".${theFileName##*.}" || echo '')
f0nzieを

1

サンプルファイルを使用すると/Users/Jonathan/Scripts/bash/MyScript.sh、このコードは次のようになります。

MY_EXT=".${0##*.}"
ME=$(/usr/bin/basename "${0}" "${MY_EXT}")

なります${ME}ことMyScript${MY_EXT}されて.sh


脚本:

#!/bin/bash
set -e

MY_EXT=".${0##*.}"
ME=$(/usr/bin/basename "${0}" "${MY_EXT}")

echo "${ME} - ${MY_EXT}"

いくつかのテスト:

$ ./MyScript.sh 
MyScript - .sh

$ bash MyScript.sh
MyScript - .sh

$ /Users/Jonathan/Scripts/bash/MyScript.sh
MyScript - .sh

$ bash /Users/Jonathan/Scripts/bash/MyScript.sh
MyScript - .sh

2
なぜこれが非常に多くの反対票を持っているのかわからない-それは実際に受け入れられた答えよりも効率的です。(後者としては、拡張子のない入力ファイル名でも壊れます)。への明示的なパスの使用basenameは、おそらくやり過ぎです。
mklement0 2014

1

上記の答えから、Pythonを模倣する最短のワンライナー

file, ext = os.path.splitext(path)

あなたのファイルが本当に拡張子を持っていると仮定すると、

EXT="${PATH##*.}"; FILE=$(basename "$PATH" .$EXT)

私はこれに反対票を持っています。私は答えを削除することを検討しています、人々はどういうわけかそれを嫌います。
commonpike 2014

basenameはパスを削除するのではなく、拡張子を削除します。
David Cullen

私がSUFFIXオプションについて忘れてしまったmanページを見たので、それは長い間経ちました。
David Cullen

何を入れればよいかを知る前に、どのエクステンションを取り除きたいかを知る必要がありますEXT。これにより、カメがずっと下に落ちます。(また、プライベート変数名にはすべて大文字を使用しないでください。これらはシステム変数用に予約されています。)
tripleee
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.