【Linux】basename・dirnameの使い方|パスからファイル名/ディレクトリを取り出す・拡張子除去

【Linux】basename・dirnameの使い方|パスからファイル名/ディレクトリを取り出す・拡張子除去 Linux

basenamedirnameは、ファイルパスを「ファイル名の部分」と「ディレクトリの部分」に分解するコマンドです。/home/user/docs/report.txtのようなパスから、basenamereport.txt(ファイル名)dirname/home/user/docs(ディレクトリ)を取り出します。単体で使うことは少なく、シェルスクリプトの中でパスを組み立て直すときに大活躍します。

地味なコマンドですが、スクリプトの汎用性を高めるうえで欠かせません。「スクリプト自身が置かれたディレクトリを基準にファイルを読む」「拡張子だけ.txtから.bakに変える」といった処理は、この2つがあってこそ書けます。この記事では、実機のLinux(WSLのDebian)で挙動を確認しながら、basenamedirnameの使い方と、拡張子除去や末尾スラッシュの扱いといった注意点を整理します。

先に結論

  • basename パスファイル名の部分dirname パスディレクトリの部分を返します。
  • basename パス .拡張子拡張子を除去できます。
  • 拡張子除去は「末尾がその文字列なら消す」だけで、拡張子かどうかは判定しません。
  • 末尾スラッシュ(/a/b/c/)は無視され、basenamecを返します。
  • スラッシュを含まない単語では、dirname.(カレント)を返します。
  • スクリプトでは"$(dirname "$0")"自身の場所を得るのが定番です。

パスの元になるファイルの作成はtouch・mkdir、パスをたどるcdはcd、シェルスクリプトでの活用はcronもあわせて参考になります。

スポンサーリンク

基本:ファイル名とディレクトリを分解する

basenameはパスの最後の要素(ファイル名)dirnameそれ以外(ディレクトリ部分)を返します。1つのパスを2つに分けるイメージです。

basename と dirname の基本
# ファイル名の部分だけを取り出す
basename /home/user/docs/report.txt
# report.txt

# ディレクトリの部分だけを取り出す
dirname /home/user/docs/report.txt
# /home/user/docs

# 2つを組み合わせれば元のパスに戻る
# dirname + / + basename = 元のパス

実機でも、basename /home/user/docs/report.txtreport.txtdirname/home/user/docsと、パスがきれいに2つに分解されることを確認しました。この2つは「パスからファイル名だけ欲しい」「パスからフォルダだけ欲しい」というスクリプトの頻出ニーズにそのまま応えます。文字列操作(sedなど)でスラッシュを頑張って処理するより、専用コマンドを使うほうが確実で読みやすくなります。

拡張子を除去する(basenameの第2引数)

basenameには便利な使い方があります。第2引数に拡張子を渡すと、その拡張子を取り除いた「ファイル名の本体」を返します。ファイル名から.txt.jpgを外したいときに使います。

拡張子の除去
# 第2引数に拡張子を渡すと、それを取り除く
basename /home/user/docs/report.txt .txt
# report        ← .txt が除去された

# 画像ファイルの本体名だけ取り出す
basename photo.jpg .jpg
# photo

実機でも、basename report.txt .txt拡張子を除いたreportが得られることを確認しました。これは「元のファイル名を保ちつつ拡張子だけ変える」といった処理の土台になります。ただし、この拡張子除去には注意すべき仕様があります。

【罠】拡張子除去は「末尾一致を消すだけ」

basenameの第2引数は、「拡張子かどうか」を判定しているわけではありません。単に「ファイル名の末尾がその文字列と一致したら、その部分を消す」だけの動作です。この違いを知らないと、意図しない結果になります。

拡張子除去の落とし穴(実証)
# .tar.gz のような二重拡張子で .gz だけ指定すると…
basename report.tar.gz .gz
# report.tar    ← .gz だけ消え、.tar は残る

# 末尾が一致しない文字列を指定しても、何も消えない
basename report.txt .md
# report.txt    ← .md は末尾ではないので、そのまま

# 「拡張子を賢く判定して消す」わけではない点に注意
拡張子判定ではなく「末尾一致の除去」(実証)

実機で確認したところ、basename report.tar.gz .gzreport.tarを返しました(.gzだけが消え、.tarは残る)。またbasename report.txt .mdreport.txtのままで、何も変わりませんでした(.mdは末尾に一致しないため)。つまりbasenameの第2引数は、「渡した文字列がファイル名の末尾にちょうど一致していれば、その分を切り取る」という単純な動作です。「これは拡張子だから消そう」という賢い判定はしていません。そのため、二重拡張子(.tar.gz)や、拡張子が不定なファイルを一律に処理したい場合は、この方法だけでは不十分です。そうしたケースでは、シェルのパラメータ展開"${file%.*}"で最後のドット以降を除去)やsedを併用します。とはいえ、拡張子が分かっている単純なケースでは、basename ファイル .拡張子が最も手軽で読みやすい方法です。

末尾スラッシュ・スラッシュなしの扱い

パスの形によって、basenamedirnameの挙動が少し変わります。末尾のスラッシュは無視され、スラッシュを含まない単語では特別な結果になります。

特殊なパスの挙動
# 末尾スラッシュは無視される
basename /a/b/c/
# c            ← 末尾の / は無視され、c が返る

dirname /a/b/c/
# /a/b

# スラッシュを含まない単語の場合
basename hello
# hello        ← そのまま返る

dirname hello
# .            ← ディレクトリ部分が無いので「.」(カレント)

実機でも、basename /a/b/c/末尾スラッシュを無視してcを返し、dirname hello(スラッシュなし)は.(カレントディレクトリ)を返しました。特にdirname.を返す挙動は、「ファイル名だけが渡されたときも、.を付ければ相対パスとして成立する」ように設計されているためで、スクリプトで安全にパスを組み立てられます。末尾スラッシュが無視されるのも、ディレクトリをpath/と書いてもpathと書いても同じ結果になり、扱いやすくなっています。

スクリプトでの定番パターン

basenamedirnameが最も活きるのがシェルスクリプトです。スクリプト自身の場所を基準にするファイル名を加工するといった定番パターンを紹介します。

スクリプトでの活用
# スクリプト自身が置かれたディレクトリを基準にする(超定番)
SCRIPT_DIR="$(dirname "$0")"
# → どこから実行されても、スクリプトと同じ場所のファイルを読める

# ファイル名の拡張子を .txt から .bak に変える
f="/data/notes.txt"
newf="$(dirname "$f")/$(basename "$f" .txt).bak"
echo "$newf"
# /data/notes.bak

# for ループで複数ファイルを一括処理
for f in /tmp/a.txt /tmp/sub/b.log; do
  echo "$(dirname "$f") にある $(basename "$f")"
done
拡張子の付け替えとスクリプトの自己位置取得(実証)

実機で、f="/data/notes.txt"に対して"$(dirname "$f")/$(basename "$f" .txt).bak"を組み立てると、/data/notes.bakという「同じ場所で拡張子だけ.bakに変えたパス」が正しく得られることを確認しました。dirnameで元のディレクトリを取り、basenameで拡張子を外した本体名を取り、新しい拡張子を付ける——この組み合わせが、バックアップファイルや変換後ファイルのパス生成の定番です。またSCRIPT_DIR="$(dirname "$0")"は、スクリプトがどのディレクトリから実行されても、自分と同じ場所にあるファイルを確実に参照できるようにする超定番のイディオムです($0は実行中のスクリプトのパス)。cronから実行されるスクリプトなど、実行時のカレントディレクトリが読めない場面で特に重要になります。変数は必ずダブルクォートで囲む"$f")と、スペースを含むパスでも安全に処理できます。

主な書き方一覧

basenamedirnameの要点をまとめます。

書き方 結果
basename /a/b/c.txt c.txt(ファイル名)
dirname /a/b/c.txt /a/b(ディレクトリ)
basename /a/b/c.txt .txt c(拡張子除去)
basename /a/b/c/ c(末尾スラッシュ無視)
dirname hello .(スラッシュなし)
"$(dirname "$0")" スクリプト自身の場所

よくある失敗

二重拡張子で拡張子が全部消えない

末尾一致を消すだけなので、.tar.gz.gzを指定すると.tarが残ります。

拡張子除去したのに何も変わらない

末尾が一致しない文字列は消えません。ファイルの実際の拡張子を確認します。

スペースを含むパスで壊れる

変数は"$f"のようにダブルクォートで囲みます。

スクリプトの実行場所によってファイルが読めない

"$(dirname "$0")"で自身の場所を基準にします。

dirnameが「.」を返して戸惑う

スラッシュを含まない場合の正常な挙動です。カレントディレクトリを意味します。

よくある質問

Qbasenameとdirnameは何をするコマンドですか?
Aファイルパスを分解するコマンドです。basenameはパスの最後の要素(ファイル名)を、dirnameはそれ以外のディレクトリ部分を返します。たとえば/home/user/report.txtなら、basenameはreport.txt、dirnameは/home/userを返します。主にシェルスクリプトの中でパスを組み立て直すために使います。
Qbasenameで拡張子を取り除くには?
A第2引数に拡張子を渡します。basename report.txt .txtとするとreportが返ります。ただしこれは「末尾がその文字列に一致したら消す」だけの動作で、拡張子かどうかを判定しているわけではありません。.tar.gz.gzを指定すると.tarが残る点に注意してください。
Qスクリプトが置かれた場所のファイルを確実に読むには?
ASCRIPT_DIR="$(dirname "$0")"で、スクリプト自身が置かれたディレクトリを取得できます($0は実行中のスクリプトのパス)。これを基準にすれば、どこから実行されても同じ場所のファイルを参照できます。cronから実行されるスクリプトなど、実行時のカレントディレクトリが不定な場面で特に有効です。
Qdirnameが「.」を返すのはなぜですか?
Aスラッシュを含まない単語(ファイル名だけ)を渡した場合の正常な挙動です。ディレクトリ部分が無いため、カレントディレクトリを表す.が返ります。これにより、ファイル名だけが渡されたときも./ファイル名として相対パスが成立し、スクリプトで安全に扱えます。
Qファイルの拡張子を別のものに変えるには?
Adirnameでディレクトリを取り、basename ファイル .元拡張子で本体名を取り、新しい拡張子を付けて組み立てます。例: "$(dirname "$f")/$(basename "$f" .txt).bak"で、.txt.bakに変えたパスが得られます。実機でも正しく変換されることを確認しています。

まとめ

  • basenameファイル名dirnameディレクトリを取り出します。
  • basename パス .拡張子で拡張子を除去できます。
  • 拡張子除去は「末尾一致を消すだけ」。二重拡張子や不一致に注意します。
  • 末尾スラッシュは無視、スラッシュなしのdirname.を返します。
  • スクリプトでは"$(dirname "$0")"自身の場所を基準にするのが定番です。

basenamedirnameは、単体では地味でも、シェルスクリプトのパス処理を支える縁の下の力持ちです。「ファイル名はbasename、フォルダはdirname」というシンプルな役割さえ覚えておけば、パスの組み立てで文字列操作に苦労することがなくなります。環境変数cp・mvと組み合わせて、堅牢なスクリプトを書いてみましょう。