スタートアップCTOによるITエンジニアのためのブログ

Bashのパス名展開(Pathname Expansion)でワイルドカードを使いこなそう

Bashで似たような複数のファイル名を手動で入力してコマンドに渡していないでしょうか?

Bashのパス名展開(Pathname Expansion)を使いこなせるようになると、そのような煩わしさから解放され、高速でファイルを操作できるようになります。本記事では、パス名展開で使えるワイルドカードの種類や、その仕組みを徹底解説します。

目次

  • 概要とメリット
  • 3種類のワイルドカード
  • 正規表現との違い
  • パス名展開の抑止方法
  • パターンマッチングのルールと設定変更
  • 変数と配列への代入
  • パス名展開とコマンド実行の順番
  • 他の展開との順序
  • 脆弱性と対策
  • まとめ

概要とメリット

Bashのパス名展開は、ワイルドカード(特殊文字)を使って特定のパターンに一致するファイル名やディレクトリ名を検索し、展開する仕組みのことです。一般的には「グロブ(Globbing)」とも呼ばれます。

コマンドを実行する前にBashが裏側でファイルを探してくれるので、効率的なファイル操作には欠かせない機能です。

メリット

  • ファイル名を手動で入力する手間が省ける。
  • 実際に存在するファイルしかマッチしないため、入力ミスを防げる。

3種類のワイルドカード

パス名展開には、主に3種類のワイルドカードが存在します。

ワイルドカード意味
*0文字以上の任意の文字列*.txt(すべてのtxtファイル)
?任意の1文字image?.jpg(image1.jpg, imageA.jpgなど)
[...]括弧内のいずれか1文字file[123].log(file1, file2, file3のみ)

1. 0文字以上の任意の文字列 *

*(アスタリスク)を使うと、長さに関係なく(0文字でも)任意の文字列にマッチします。

# Bash
# cronで始まるファイルとディレクトリを表示
cd /etc/
echo cron*
# 出力: cron.d cron.daily cron.hourly cron.monthly cron.weekly crontab

2. 任意の1文字 ?

?(疑問符)は、ちょうど1文字分にマッチします。

# Bash
touch image1.jpg imageA.jpg image2B.jpg
ls image?.jpg
# 出力: image1.jpg imageA.jpg

? を連続させることで、文字数を指定して検索することも可能です。

# Bash
ls image??.jpg
# 出力: image2B.jpg

3. 指定したいずれか1文字 [...]

[...](角括弧)は、括弧内に列挙された文字のどれか1文字にマッチします。

# Bash
touch file1.log file2.log file3.log
ls file[12].log
# 出力: file1.log  file2.log
  • 範囲指定: -(ハイフン)を使って [0-9][a-z] のように範囲を指定できます。
  • POSIX文字クラス: [[:alnum:]](英数字)や [[:alpha:]](英字)なども利用可能です。
  • 除外(否定): 先頭に ! または ^ を付けると、「含まれない文字」を指定できます。(例: file[!2].logfile2.log 以外にマッチ)

正規表現との違い

パス名展開は「正規表現」と似ていますが、記号の意味が異なります。混同に注意しましょう。

意味パス名展開(グロブ)正規表現
任意の1文字?.
0文字以上の任意の文字列*.*
直前の文字の0回以上の繰り返し(なし)*
文字クラス(いずれかの文字)[...][...]
文字クラスの否定[!...] または [^...][^...]

パス名展開の抑止方法

ワイルドカードを記号としてそのまま扱いたい(展開を止める)場合は、以下の方法をとります。

1. クォートまたはエスケープ

シングルクォート ' '、ダブルクォート " "、またはバックスラッシュ \ を使うと、ワイルドカードとしての機能を無効化できます。

# Bash
touch 'what?.txt'
ls what\?.txt
# 出力: what?.txt

2. set -f で無効化する

シェル全体でパス名展開を一時的にオフにすることも可能です。

$ set -f       # パス名展開を無効化
$ echo *.txt
# 出力: *.txt (展開されずにそのまま表示される)
$ set +f       # 再び有効化

パターンマッチングのルールと設定変更

Bashのオプション(shopt)を変更することで、展開の挙動をカスタマイズできます。

1. 一致するファイルがない場合

デフォルトでは、マッチするファイルがない場合、パターン文字列がそのまま残ります。

これを変更するには以下のオプションを使います。

  • nullglob: マッチしない場合、パターンを削除(空文字に)します。
  • failglob: マッチしない場合、エラーを返してコマンドを実行しません。
# Bash
shopt -s nullglob
echo *.nonexistent  # 何も表示されない

2. 隠しファイル(ドットファイル)の扱い

通常、*. で始まる隠しファイルにマッチしません。shopt -s dotglob を設定すると、これらも展開対象に含まれるようになります。

# Bash
touch .hidden
ls * # 何も表示されない

shopt -s dotglob
ls * # .hidden

なお、dotglobを設定せずに隠しファイルを表示するには、ls .[!.]*を使います。ls .*だと隠しファイルに加え. (カレントディレクトリ)や..(親ディレクトリ)にも展開されてしまいます。

3. 再帰的検索(globstar)

shopt -s globstar を有効にすると、** を使ってサブディレクトリを再帰的に検索できるようになります。

# Bash
shopt -s globstar
# すべてのサブディレクトリから .conf ファイルを探す
ls **/*.conf

変数と配列への代入

変数に代入する際はクォートの有無にかかわらずパス名展開されませんが、配列代入時にはされます。

# Bash
touch file1.txt file2.txt

text_files=*.txt
echo "$text_files"
# 出力: *.txt

text_files=(*.txt)
echo "${text_files[@]}"
# 出力: file1.txt file2.txt

パス名展開とコマンド実行の順番

ここが最も重要なポイントです。パス名展開はコマンドが実行される前にBashによって行われます。

例えば ls *.txt を実行する際:

  1. Bashがカレントディレクトリを走査し、*.txtfile1.txt file2.txt に置き換える。
  2. Bashが実際に実行するコマンドは ls file1.txt file2.txt となる。

このため、find コマンドなどでワイルドカードを使いたい場合は、Bashに展開させないようクォートする必要があります。

# Bash
# NG: Bashが先に展開してしまう
find . -name *.txt 

# OK: クォートしてワイルドカードをそのまま find コマンドに渡す
find . -name '*.txt'

他の展開との順序

Bashには多くの「展開」がありますが実行される順番が決まっており、パス名展開は最後に行われます。

  1. ブレース展開 {a,b}
  2. 同列順位:
    • チルダ展開 ~
    • パラメータと変数展開 ${var}
    • 算術展開 $(( ))
    • コマンド置換 $( )
    • プロセス置換 <()
  3. 単語分割 (Word Splitting)
  4. パス名展開(ワイルドカード) *

*チルダからプロセス置換までは一つのフェーズとして、左から右へ出現順に処理されます。

この順序があるため、ブレース展開と組み合わせたり、変数の中身にワイルドカードを入れておき、後から展開させるというテクニックも使えます。

# Bash
# 拡張子が .jpg, .jpeg, .png のいずれかを表示 (ブレース展開との組み合わせ)
ls *.{jpg,jpeg,png}

files="*.txt"
ls $files  # 変数展開の後にパス名展開が行われるため、正しく展開される

脆弱性と対策

非常に強力な機能ですが、意図しない展開はセキュリティリスク(引数インジェクションなど)や誤操作の原因になります。

  • 変数は常にダブルクォートする: $files ではなく "$files" と書くことで、予期せぬパス名展開を防げます(クォート内ではパス名展開は行われません)。
  • ワイルカードはパスの一部として使う:ファイル名に - で始まるものがある場合、rm * とすると、ファイル名がコマンドのオプション(例: -rf)として解釈される恐れがあります。rm ./* のようにパスを指定すると安全です。
  • 破壊的コマンドの前にechorm * を実行する前に echo * で対象を確認する。

まとめ

  • *, ?, [...] を使って柔軟にファイル指定ができる。
  • パス名展開はコマンド実行前に行われる。
  • shopt を使って隠しファイルや再帰検索の挙動を変えられる。
  • 意図しない展開を防ぐため、「基本はクォート、展開したい時だけクォートなし」を徹底する。

パス名展開をマスターして、ターミナル操作をより安全かつスマートに進化させましょう。