- Linux
Linux basename・dirnameの使い方 ファイル名と拡張子取得
basename はパスから「最後の要素(ファイル名)」を、dirname は「最後の要素を除いた部分(親ディレクトリ)」を取り出すコマンドです。 どちらもパスを文字列として切り分けるだけで、ファイルが実在するかどうかは見ません。絶対パスに直したいときは realpath を組み合わせます。
この記事では、「パスから欲しい部分 → コマンド → パラメータ展開 → 出力」を1枚の早見表にまとめたうえで、.tar.gz、ドットで始まるファイル、拡張子なし、末尾スラッシュ、空白入りパスといった境界例を すべて実際に実行した結果で示します。basename -s と -a、realpath と readlink -f の違い、macOS(BSD 版)で使えないオプション、スクリプト自身の場所を取る定型句も載せました。
実行環境は Ubuntu 24.04 LTS、GNU coreutils 9.4(basename・dirname・realpath・readlink)、GNU bash 5.2.21 です。出力中のホームディレクトリは /home/taro/demo に置き換えています(表示は環境に合わせて置き換えています)。
basename・dirname の早見表
例のパスは p=/var/log/app/access.log です。出力はすべて実行して確かめたものです。
| 欲しい部分 | コマンド | パラメータ展開(bash / sh) | 出力 |
|---|---|---|---|
| ファイル名 | basename "$p" |
"${p##*/}" |
access.log |
| 拡張子なしのファイル名 | basename "$p" .log または basename -s .log "$p" |
f="${p##*/}"; "${f%.*}" |
access |
| 拡張子だけ | (専用コマンドなし) | f="${p##*/}"; "${f##*.}" |
log |
| 親ディレクトリ | dirname "$p" |
"${p%/*}" |
/var/log/app |
| 2つ上のディレクトリ | dirname "$(dirname "$p")" |
q="${p%/*}"; "${q%/*}" |
/var/log |
| 絶対パス(リンクも解決) | realpath "$p" |
(なし) | /var/log/app/access.log |
| 複数パスのファイル名 | basename -a p1 p2 |
ループで "${x##*/}" |
1行に1つずつ |
パラメータ展開の読み方は次の4つだけ覚えれば足ります。
| 書き方 | 意味 | 覚え方 |
|---|---|---|
${p##*/} |
先頭から、最後の / までを取り除く(最長一致) |
# は左側を削る。2つ重ねると最長 |
${p%/*} |
末尾から、最後の / 以降を取り除く(最短一致) |
% は右側を削る。1つは最短 |
${f%.*} |
末尾から、最後の . 以降を取り除く |
拡張子を1つ外す |
${f##*.} |
先頭から、最後の . までを取り除く |
拡張子だけ残す |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Linux basename・dirnameの使い方 ファイル名と拡張子取得」
basename でファイル名を取り出す
基本の形
basename /var/log/app/access.log
dirname /var/log/app/access.log
access.log
/var/log/app
basename は最後のスラッシュより後ろ、dirname は最後のスラッシュより前を返します。どちらも文字列を切り分けるだけなので、存在しないパスを渡しても同じ結果になります。
拡張子を外す2つの書き方
basename /var/log/app/access.log .log # 第2引数に接尾辞
basename -s .log /var/log/app/access.log # -s オプションで接尾辞
access
access
結果は同じですが、-s は複数のパスをまとめて渡せるところが違います。第2引数の形は、パスを2つ並べると2つ目が「接尾辞」として解釈されます。
basename /a/one.txt /b/two.txt # 2つ目は接尾辞あつかい
basename /a/one.txt /b/two.txt /c/3.txt # 3つ目は受け取れない
basename -s .txt /a/one.txt /b/two.txt # -s なら複数パスOK
one.txt
basename: extra operand '/c/3.txt'
Try 'basename --help' for more information.
one
two
1行目は「/a/one.txt から接尾辞 /b/two.txt を外す」と解釈され、外すものがないので one.txt がそのまま出ています。エラーにならないぶん気づきにくいので、複数のパスを渡すときは -a か -s を付けると覚えておくと安心です。
basename -a で複数パスをまとめて処理する
basename -a /a/one.txt /b/two.txt
basename -a -s .txt /a/one.txt /b/two.md
one.txt
two.txt
one
two.md
-a(--multiple)はすべての引数をパスとして扱います。-s は -a を兼ねるので、接尾辞を外したいときは -s だけで十分です。2つ目の例のとおり、接尾辞が一致しないファイル(two.md)はそのまま出力されます。
basename・dirname のオプション一覧
| コマンド | オプション | 働き |
|---|---|---|
| basename | -a / --multiple |
引数をすべてパスとして処理する |
| basename | -s 接尾辞 / --suffix=接尾辞 |
末尾の接尾辞を外す(-a を兼ねる) |
| basename | -z / --zero |
出力の区切りを改行ではなく NUL 文字にする |
| dirname | -z / --zero |
出力の区切りを NUL 文字にする |
| 共通 | -- |
これ以降をオプションではなくパスとして扱う |
dirname は -a なしで複数のパスを受け取れます。
dirname /a/one.txt /b/c/two.txt
/a
/b/c
-z は、改行を含むファイル名でも安全に受け渡したいときに xargs -0 などと組み合わせて使います。
basename -z /a/one.txt | od -c | head -1
0000000 o n e . t x t \0
境界例の実行結果をまとめて確認する
ここがこの記事の中心です。basename・dirname とパラメータ展開は、ふつうのパスでは同じ結果になりますが、境界例では結果が分かれます。 次の表は、1行ずつ実際に実行して得た結果です([] は空文字列)。
ファイル名と親ディレクトリ
| 入力パス | basename |
dirname |
${p##*/} |
${p%/*} |
|---|---|---|---|---|
/data/backup.tar.gz |
backup.tar.gz |
/data |
backup.tar.gz |
/data |
/home/taro/.bashrc |
.bashrc |
/home/taro |
.bashrc |
/home/taro |
/usr/bin/make |
make |
/usr/bin |
make |
/usr/bin |
/data/my report.txt |
my report.txt |
/data |
my report.txt |
/data |
/var/log/app/(末尾スラッシュ) |
app |
/var/log |
[] |
/var/log/app |
/var/log/app// |
app |
/var/log |
[] |
/var/log/app/ |
/ |
/ |
/ |
[] |
[] |
access.log(スラッシュなし) |
access.log |
. |
access.log |
access.log |
./a.txt |
a.txt |
. |
a.txt |
. |
a/b |
b |
a |
b |
a |
. |
. |
. |
. |
. |
.. |
.. |
. |
.. |
.. |
/access.log(ルート直下) |
access.log |
/ |
access.log |
[] |
| 空文字列 | [] |
. |
[] |
[] |
結果が分かれるのは次の4つの場面です。
| 場面 | コマンドの結果 | パラメータ展開の結果 | 対応 |
|---|---|---|---|
| 末尾にスラッシュがある | 末尾のスラッシュを無視して切り分ける | ファイル名が空になる | 先に p="${p%/}" で末尾を1つ外す |
| スラッシュを含まない | dirname は . を返す |
${p%/*} は元の文字列のまま |
case "$p" in */*) ...;; *) d=.;; esac で分ける |
| ルート直下のファイル | dirname は / を返す |
${p%/*} は空になる |
空なら / を補う |
/ そのもの |
どちらも / |
どちらも空 | コマンドを使う |
末尾スラッシュを外してから切り出す例です。
p=/var/log/app/
echo "[${p##*/}]"
p2=${p%/}
echo "[${p2##*/}]"
[]
[app]
入力の形が読めないとき(利用者が入力したパス、末尾スラッシュつきのディレクトリなど)は basename・dirname、形が決まっているループの中ではパラメータ展開、と使い分けると迷いません。
拡張子の取り出し
ファイル名 f="${p##*/}" を取り出したあとの結果です。
| ファイル名 f | ${f%.*}(最後の拡張子を外す) |
${f%%.*}(最初のドット以降を外す) |
${f##*.}(最後の拡張子) |
${f#*.}(最初のドットより後ろ) |
|---|---|---|---|---|
report.txt |
report |
report |
txt |
txt |
backup.tar.gz |
backup.tar |
backup |
gz |
tar.gz |
archive.v1.2.tgz |
archive.v1.2 |
archive |
tgz |
v1.2.tgz |
my report.txt |
my report |
my report |
txt |
txt |
.bashrc |
[] |
[] |
bashrc |
bashrc |
.config.yml |
.config |
[] |
yml |
config.yml |
make(拡張子なし) |
make |
make |
make |
make |
ここから3つのことが読み取れます。
.tar.gzをまとめて外したいときは%%.*、最後の1つだけ外したいときは%.*を使います。ただしarchive.v1.2.tgzのように名前の途中にドットがあると%%.*はarchiveまで削るので、接尾辞がわかっているならbasename "$p" .tar.gzと明示するほうが確実です。- ドットで始まるファイル(.bashrc)は、
${f%.*}が空になります。先頭のドットを拡張子の区切りとして扱ってしまうためです。 - 拡張子がないファイル(make)は、
${f##*.}がファイル名そのものを返します。「拡張子なし」を空文字列として受け取りたいときは、下の関数のように場合分けします。
.tar.gz を接尾辞で外す例です。
basename /data/backup.tar.gz .tar.gz
backup
もう1つ知っておくと役立つのが、パス全体に直接 ${p##*.} を当てると、ディレクトリ名のドットに反応することです。
p=/data.d/make
echo "${p##*.}"
d/make
先に ${p##*/} でファイル名にしてから拡張子を取り出す、という順番を守るとこの結果を避けられます。
拡張子を安全に取り出す関数(コピペ用)
ドットで始まるファイルと拡張子なしのファイルを、空文字列として返す関数です。
#!/usr/bin/env bash
# 拡張子を返す。拡張子がなければ空文字列を返す
ext() {
local f="${1##*/}" # まずファイル名だけにする
case "$f" in
.*.*) echo "${f##*.}" ;; # .config.yml -> yml
.*) echo "" ;; # .bashrc -> (なし)
*.*) echo "${f##*.}" ;; # report.txt -> txt
*) echo "" ;; # make -> (なし)
esac
}
for p in /data/report.txt /home/taro/.bashrc /usr/bin/make \
/data/backup.tar.gz /data/.config.yml /data.d/make; do
printf '%s -> [%s]\n' "$p" "$(ext "$p")"
done
/data/report.txt -> [txt]
/home/taro/.bashrc -> []
/usr/bin/make -> []
/data/backup.tar.gz -> [gz]
/data/.config.yml -> [yml]
/data.d/make -> []
接尾辞がファイル名全体と同じ場合
basename /data/.log .log
basename -s .log /data/.log
.log
.log
接尾辞がファイル名全体と一致するときは、basename は何も外しません。 空文字列になることを防ぐための仕様で、パラメータ展開の ${f%.*} が空を返すのと対照的です。
空白入りパスとエラーになる例
変数は必ずダブルクォートで囲む
p="/data/my report.txt"
basename $p # クォートなし
basename "$p" # クォートあり
dirname $p # クォートなし
my
my report.txt
/data
.
クォートがないと、シェルが空白でパスを2つに分けてからコマンドに渡します。basename は「/data/my から接尾辞 report.txt を外す」と解釈して my を返し、dirname は2つのパスとして処理して2行を返します。どちらもエラーにならずに別の結果を返すので、"$p" と書く習慣が最も効きます。
よく出るエラー文と直し方
| 実行したコマンド | 出力 | 終了ステータス | 直し方 |
|---|---|---|---|
basename |
basename: missing operand |
1 | 引数を渡す。変数が空でないか確認する |
dirname |
dirname: missing operand |
1 | 同上 |
basename a b c |
basename: extra operand 'c' |
1 | -a を付けるか、変数をクォートする |
basename -n.txt |
basename: invalid option -- 'n' |
1 | basename -- -n.txt と書く |
basename "" |
(空行) | 0 | 空かどうかを先に確認する |
dirname "" |
. |
0 | 同上 |
ハイフンで始まるファイル名は、-- を挟むとパスとして扱われます。
basename -- -n.txt
-n.txt
missing operand は、クォートなしの変数が空だったときによく出ます。basename $p は p が空だと引数なしの basename になるためです。basename "$p" と書けば空行が返るだけで、エラーにはなりません。スクリプトでは basename -- "$p" の形を定型にしておくと、空白・空文字列・ハイフン始まりの3つをまとめて扱えます。
空の変数を早めに知らせたい場合は、次の1行を足します。
: "${p:?パスが空です}"
realpath で絶対パスを取り出す
basename・dirname は文字列を切るだけなので、../conf/app.conf のような相対パスやシンボリックリンクはそのまま残ります。実際の場所を知りたいときは realpath を使います。
検証用に次の構成を作りました。
/home/taro/demo/
├── real/sub/app.conf ← 実体
├── link -> real ← ディレクトリへのリンク
└── work/
├── conf.lnk -> ../real/sub/app.conf
└── broken -> nowhere ← リンク先が存在しない
realpath link/sub/app.conf
realpath real/sub/../sub/./app.conf
realpath work/conf.lnk
realpath -s work/conf.lnk
readlink work/conf.lnk
/home/taro/demo/real/sub/app.conf
/home/taro/demo/real/sub/app.conf
/home/taro/demo/real/sub/app.conf
/home/taro/demo/work/conf.lnk
../real/sub/app.conf
- realpath は
..や.を畳み、シンボリックリンクをたどった絶対パスを返します。 -s(--no-symlinks)を付けると、リンクをたどらずに絶対パスへ直すだけになります。- オプションなしの readlink は、リンクに書かれている内容をそのまま表示します。
realpath と readlink -f の違い
どちらも絶対パスを返しますが、存在しないパスを渡したときのメッセージと、相対パスへの変換機能が違います。
| 確認したこと | realpath | readlink |
|---|---|---|
| 既定の動作 | 絶対パスに直す | リンクの中身を表示する |
| 絶対パスに直す | realpath p |
readlink -f p |
| 最後の要素だけ存在しない | パスを表示(終了 0) | -f でパスを表示(終了 0) |
| 途中のディレクトリが存在しない | エラー文を表示(終了 1) | -f は何も表示しない(終了 1) |
| すべて存在することを求める | -e |
-e |
| 存在しなくても計算する | -m |
-m |
| リンクをたどらない | -s |
(なし) |
| 相対パスに直す | --relative-to=DIR |
(なし) |
| リンクでないファイルを渡す | 絶対パスを表示 | オプションなしだと何も表示せず終了 1 |
実行結果です。
realpath work/none.txt # 最後の要素だけ存在しない
realpath work/none/none.txt # 途中のディレクトリも存在しない
readlink -f work/none/none.txt; echo "rc=$?"
realpath -e work/none.txt
realpath -m work/none/none.txt
realpath work/broken # リンク先が存在しないリンク
/home/taro/demo/work/none.txt
realpath: work/none/none.txt: No such file or directory
rc=1
realpath: work/none.txt: No such file or directory
/home/taro/demo/work/none/none.txt
/home/taro/demo/work/nowhere
readlink -f は何も表示せずに終了ステータス 1 だけを返すので、原因を画面で確かめたいときは realpath のほうが読みやすいです。これから作るファイルのパスを計算したい場合は -m が向いています。
相対パスに直す
realpath --relative-to=work real/sub/app.conf
realpath --relative-base=/home/taro/demo/real real/sub/app.conf /etc/hosts
../real/sub/app.conf
sub/app.conf
/etc/hosts
--relative-to は「指定したディレクトリから見た相対パス」を返します。--relative-base は、指定したディレクトリの下にあるものだけ相対パスにして、外にあるものは絶対パスのまま返します。シンボリックリンクを相対パスで張るときに便利です。
pwd と pwd -P の違い
リンク経由で移動したディレクトリでは、pwd の表示と実体の場所が変わります。
cd link/sub
pwd
pwd -P
/home/taro/demo/link/sub
/home/taro/demo/real/sub
pwd は移動してきた経路、pwd -P はリンクを解決した実体の場所を表示します。あとで出てくる「スクリプト自身の場所」を取る定型句でも、この違いが効いてきます。
スクリプト自身の場所を取る定型句
設定ファイルや補助スクリプトを「スクリプトと同じディレクトリ」から読みたい場面は多くあります。カレントディレクトリに頼らず、スクリプトの場所を基準にするのが定石です。
#!/usr/bin/env bash
echo "\$0 = $0"
echo "BASH_SOURCE = ${BASH_SOURCE[0]}"
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
echo "SCRIPT_DIR = $SCRIPT_DIR"
REAL_DIR="$(dirname -- "$(realpath -- "${BASH_SOURCE[0]}")")"
echo "REAL_DIR = $REAL_DIR"
echo "SCRIPT_NAME = $(basename -- "${BASH_SOURCE[0]}")"
これを sd/bin/where.sh として保存し、sd/lnk/w というシンボリックリンクも作って、呼び出し方を変えて実行しました。
| 呼び出し方 | $0 |
SCRIPT_DIR(cd と pwd) | REAL_DIR(realpath) |
|---|---|---|---|
./sd/bin/where.sh |
./sd/bin/where.sh |
/home/taro/demo/sd/bin |
/home/taro/demo/sd/bin |
cd sd && bin/where.sh |
bin/where.sh |
/home/taro/demo/sd/bin |
/home/taro/demo/sd/bin |
bash sd/bin/where.sh |
sd/bin/where.sh |
/home/taro/demo/sd/bin |
/home/taro/demo/sd/bin |
PATH 経由で where.sh |
/home/taro/demo/sd/bin/where.sh |
/home/taro/demo/sd/bin |
/home/taro/demo/sd/bin |
source sd/bin/where.sh |
呼び出し元の名前 | /home/taro/demo/sd/bin |
/home/taro/demo/sd/bin |
リンク経由 sd/lnk/w |
sd/lnk/w |
/home/taro/demo/sd/lnk |
/home/taro/demo/sd/bin |
読み取れることは2つです。
sourceで読み込むと$0は呼び出し元を指すので、${BASH_SOURCE[0]}を使うほうが広い場面で同じ結果になります。- シンボリックリンク経由で起動されると、cd と pwd の形はリンクが置かれたディレクトリを返します。実体の場所が欲しいときは realpath の形を使います。
用途別の選び方です。
| 目的 | 定型句 |
|---|---|
| ふつうはこれ(bash) | SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" |
| リンク経由でも実体の場所が欲しい | SCRIPT_DIR="$(dirname -- "$(realpath -- "${BASH_SOURCE[0]}")")" |
| sh(dash など)でも動かしたい | SCRIPT_DIR="$(cd -- "$(dirname -- "$0")" && pwd)" |
| スクリプト名をメッセージに出す | SCRIPT_NAME="$(basename -- "$0")" |
使い方の例です。
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/lib/common.sh" # 同じ階層の lib を読む
CONF="$SCRIPT_DIR/../conf/app.conf" # 1つ上の conf を指す
echo "使い方: $(basename -- "$0") 入力ファイル"
ループでの使い方とパラメータ展開の速さ
拡張子を付け替えて一括処理する
for f in lp/*; do
n=$(basename -- "$f")
echo "${n%.*}.bak"
done
a.bak
b c.bak
d.tar.bak
lp/ には a.txt、b c.txt、d.tar.gz を置いています。3つ目のとおり、%.* は最後の拡張子だけを外すので d.tar.bak になります。d.bak にしたい場合は basename -- "$f" .tar.gz のように接尾辞を明示します。
find と組み合わせる
find lp -type f -print0 | xargs -0 basename -a
find lp -type f -printf "%f\n" | sort
find lp -type f -printf "%h\n" | sort -u
d.tar.gz
b c.txt
a.txt
a.txt
b c.txt
d.tar.gz
lp
-print0 と xargs -0 の組み合わせで、空白入りのファイル名も1つのパスとして basename に渡せます。GNU find では -printf "%f\n" がファイル名、"%h\n" が親ディレクトリを直接出力するので、basename・dirname を呼ばずに済みます。
2,000回呼んだときの時間
time (for i in $(seq 2000); do basename /a/b/c.txt >/dev/null; done)
time (for i in $(seq 2000); do p=/a/b/c.txt; : "${p##*/}"; done)
real 0m2.947s # basename を2,000回
real 0m0.007s # パラメータ展開を2,000回
basename は外部コマンドなので、呼ぶたびにプロセスを起動します。この環境では2,000回で約2.9秒、パラメータ展開は約0.007秒でした。数十回なら差は気になりませんが、数千件を回すループではパラメータ展開が向いています。 時間は環境によって変わるので、目安として読んでください。
どちらを使うかの判断表
| 状況 | おすすめ | 理由 |
|---|---|---|
| 対話的に1回だけ確かめたい | basename / dirname | 読みやすく、境界例の扱いが安定している |
| 入力の形が読めない(末尾スラッシュ、スラッシュなし) | basename / dirname | 早見表の4つの場面でも期待どおりに切り分ける |
| 件数の多いループ | パラメータ展開 | プロセスを起動しないので速い |
| 拡張子だけ欲しい | パラメータ展開 | basename には拡張子だけを返す機能がない |
| 複数パスをまとめて処理 | basename -a / -s |
1回の起動で済む |
| sh(dash)で動かす | どちらも可 | ##・% は POSIX sh でも使える |
最後の行は /bin/sh が dash の環境で確かめました。
sh -c 'p=/var/log/a.txt; echo "${p##*/} ${p%/*}"'
a.txt /var/log
macOS(BSD 版)で使えないオプション
macOS に入っている basename・dirname・realpath・readlink は BSD 系の実装で、GNU coreutils とはオプションが一部異なります。この節は macOS の実機がなく未実行です。一般に知られた仕様として整理しているので、手元の man basename などで確かめてから使ってください。
| やりたいこと | Linux(GNU) | macOS(BSD)での扱い(未実行) | 両方で動く書き方 |
|---|---|---|---|
| 接尾辞を外す | basename -s .log p |
-s は使える |
basename p .log |
| 複数パス | basename -a p1 p2 |
-a は使える |
ループで1つずつ |
| 長いオプション | --suffix=.log、--multiple |
使えない | 短いオプションを使う |
| NUL 区切り | basename -z / dirname -z |
使えない | "${p##*/}" をループで |
| 絶対パス | realpath p |
新しい macOS には realpath があるが、-m・-e・-s・--relative-to は使えない |
cd と pwd -P の組み合わせ |
| リンクを解決 | readlink -f p |
新しい macOS では -f が使えるが、以前の版では使えない |
同上 |
| find でファイル名 | find -printf "%f\n" |
-printf が使えない |
find ... -exec basename {} \; |
macOS と Linux の両方で動かすスクリプトでは、次の形が扱いやすいです。
# ディレクトリの実体の絶対パス(GNU / BSD 共通)
abs_dir="$(cd -- "$(dirname -- "$p")" && pwd -P)"
abs_path="$abs_dir/$(basename -- "$p")"
GNU 版と同じオプションを macOS で使いたい場合は、Homebrew の coreutils を入れると gbasename、grealpath、greadlink のように先頭に g が付いた名前で使えます(未実行)。
目的別の逆引き表
| やりたいこと | 書き方 |
|---|---|
| ファイル名を取り出す | basename -- "$p" |
| 拡張子なしのファイル名 | basename -- "$p" .txt |
.tar.gz をまとめて外す |
basename -- "$p" .tar.gz |
| 拡張子を付け替える | f="${p##*/}"; new="${f%.*}.bak" |
| 同じディレクトリに別名で出力する | out="$(dirname -- "$p")/$(basename -- "$p" .csv).json" |
| 親ディレクトリの名前だけ | basename -- "$(dirname -- "$p")" |
| 2つ上のディレクトリ | dirname -- "$(dirname -- "$p")" |
| 絶対パスに直す | realpath -- "$p" |
| まだ存在しないパスを絶対パスに | realpath -m -- "$p" |
| リンクをたどらず絶対パスに | realpath -s -- "$p" |
| リンクの中身を見る | readlink -- "$p" |
| ある場所から見た相対パス | realpath --relative-to="$base" -- "$p" |
| スクリプトのあるディレクトリ | cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd |
| 複数ファイルの名前を一覧 | basename -a -- "$@" |
「同じディレクトリに別名で出力する」を動かすとこうなります。
p="/data/in/sales 2026.csv"
out="$(dirname -- "$p")/$(basename -- "$p" .csv).json"
echo "$out"
/data/in/sales 2026.json
書く前のチェックリスト
| 確認すること | 見るポイント |
|---|---|
| 変数をダブルクォートで囲んだか | basename "$p"。空白入りパスが2つに分かれない |
-- を入れたか |
ハイフンで始まるファイル名をパスとして扱える |
複数パスに -a か -s を付けたか |
2つ目が接尾辞あつかいにならない |
| 末尾スラッシュの可能性があるか | パラメータ展開なら先に ${p%/} |
| 拡張子なし・ドット始まりを考えたか | ${f##*.} はファイル名そのものを返す |
パス全体に ##*. を当てていないか |
ディレクトリ名のドットに反応する |
| 絶対パスが必要か | basename・dirname は相対パスのまま。realpath を使う |
| macOS でも動かすか | 短いオプションと pwd -P の形にそろえる |
よくある質問
Q. basename で拡張子だけを取り出せますか?
A. basename には拡張子だけを返す機能がありません。f="${p##*/}" でファイル名にしたあと "${f##*.}" で取り出します。拡張子がないファイルではファイル名そのものが返るので、空文字列で受け取りたい場合は本文の ext 関数のように場合分けしてください。
Q. dirname に相対パスやファイル名だけを渡すとどうなりますか?
A. スラッシュを含まない access.log を渡すと .(カレントディレクトリ)が返ります。相対パスは相対パスのまま切り分けるので、絶対パスが必要なら realpath で直してから dirname に渡します。
Q. realpath と readlink -f はどちらを使えばよいですか?
A. 絶対パスに直す目的ならどちらも同じ結果になります。途中のディレクトリが存在しないときに realpath はエラー文を表示し、readlink -f は何も表示せず終了ステータス 1 を返します。相対パスへの変換(--relative-to)やリンクをたどらない指定(-s)は realpath だけにあります。
Q. basename "$p" .log と basename -s .log "$p" は何が違いますか?
A. 1つのパスなら結果は同じです。-s は複数のパスをまとめて渡せるところが違います。第2引数の形でパスを2つ並べると、2つ目が接尾辞として解釈されます。
まとめ
- basename は最後のスラッシュより後ろ、dirname は前を返す。どちらも文字列を切るだけで、ファイルの存在は見ない
- パラメータ展開は
${p##*/}(ファイル名)、${p%/*}(親)、${f%.*}(拡張子なし)、${f##*.}(拡張子) の4つで足りる - 末尾スラッシュ・スラッシュなし・ルート直下・
/の4つの場面でコマンドとパラメータ展開の結果が分かれる .tar.gzはbasename "$p" .tar.gzと接尾辞を明示すると確実。ドット始まりと拡張子なしは場合分けする- 複数パスは
-aか-s、変数はbasename -- "$p"の形を定型にする - 絶対パスは realpath。存在しないパスは
-m、相対パスは--relative-to - スクリプトの場所は
cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd。リンク経由でも実体が欲しければ realpath を使う
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Linux basename・dirnameの使い方 ファイル名と拡張子取得」
株式会社RJCでは、Linuxコマンドやシェルスクリプトの書き方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。