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

Bashチルダ展開(Tilde Expansion)による高速ディレクトリ操作

~ で表現されるBashのチルダ展開(Tilde Expansion)。自分のホームディレクトリを参照する記号としてお馴染みですが、実はそれ以外のディレクトリも一瞬で参照できることをご存知でしょうか?

本記事では、チルダ展開を使い倒してターミナル操作を高速にするテクニックから、スクリプトでの落とし穴まで徹底解説します。

目次

  • 概要とメリット
  • チルダ展開の基本ルールとクォートの罠
    • クォートの注意点
  • 自分のホームディレクトリ($HOME)
  • 他のユーザのホームディレクトリ
  • 現在の作業ディレクトリ($PWD)
  • 一つ前にいた作業ディレクトリ ($OLDPWD)
  • ディレクトリスタック
  • 変数への代入ルール
    • PATH変数への代入で使うべきでない理由
  • 他のBash展開との優先順位
  • チルダ展開に失敗するケース
    • 引用符(クォート)で囲まれている
    • 単語の先頭以外に ~ がある
    • ログインユーザが見つからない
    • スタックが見つからない
    • 変数が設定されてない
  • 展開の成功・失敗を判定する方法
  • まとめ

概要とメリット

チルダ展開とは、特定の文字列をフルパスへ自動置き換えするBashの基本機能です。

  • 入力の削減: 長いパス(/home/username/projects/...)を数文字で表現。
  • 可搬性の向上: ユーザー名をハードコードせず、どの環境でも動くスクリプトが書ける。
  • 高速移動: ディレクトリスタック(履歴)を活用した瞬時のアクセス。

チルダ展開の基本ルールとクォートの罠

チルダ展開が発動するには、「単語の先頭がクォートされていない ~ であること」が絶対条件です。

クォート(引用符)の注意点

チルダプレフィックス(展開される範囲)の中に少しでもクォートが含まれると、展開は無効化されます。

# Bash
echo ~         # ◎ 展開される(例: /home/user)
echo "~"       # × 展開されない。文字通りの "~"
echo ~/test    # ◎ 展開される(例: /home/user/test)
echo ~/'test'  # ◎ 展開される(スラッシュ以降のクォートは影響しない)
echo ~'/test'  # × 展開されない(スラッシュをクォートすると、~'/test' 全体が対象になり無効化される)

自分のホームディレクトリ($HOME)

最も一般的な使い方です。単体の ~ または ~/ は、環境変数 $HOME の値に置き換わります。

# Bash
# ホームディレクトリの直下に移動
cd ~

# スクリプト内での利用
config_file="~/config.json" # これは展開されない(変数代入の右辺全体をクォートしているため)
config_file=~/config.json   # これなら展開される

他のユーザのホームディレクトリ

~ の直後にユーザー名を記述すると、そのユーザーのホームディレクトリに展開されます。

# Bash
# user01のホームディレクトリにある公開ディレクトリへ移動
cd ~user01/public_html

# rootユーザーのホームを確認
ls -ld ~root

現在の作業ディレクトリ($PWD)

~+ は、現在の作業ディレクトリ($PWD)の値に展開されます。

# Bash
# 現在のディレクトリのフルパスを表示(pwdコマンドと同じ結果)
echo ~+

# 相対パスをフルパスに変換して渡す際に便利
ls -l ~+/backup.log

.(ドット)との違い

カレントディレクトリを指す .(ドット)と ~+(チルダプラス)はどちらも「今いる場所」を指しますが、シェルの解釈に大きな違いがあります。

表現性質シェルの挙動
.相対パスそのまま「.」として扱われる。OSが「今の場所」を探しに行く。
~+絶対パス実行した瞬間に、環境変数 $PWD の中身(例: /home/user/work)に展開される。

対話型シェルで使う際は入力が楽な.で十分ですが、スクリプトで変数にファイルパスを保存する時は参照先がズレない~+がおすすめです。

一つ前にいた作業ディレクトリ($OLDPWD)

~- は、直前にいた作業ディレクトリ($OLDPWD)に展開されます。

# Bash
cd /var/log
cd /tmp
ls ~-  # /var/log の中身が表示される

直前のディレクトリに戻るcd - と似ていますが、以下の違いがあります。

項目– (単なるハイフン)~- (チルダマイナス)
正体cd コマンド固有のオプションBashのチルダ展開機能
仕組みcd コマンドが内部で $OLDPWD を参照するシェルが実行前に $OLDPWD の値に置き換える
汎用性cd コマンドでしか使えないどのコマンド(ls, cp, echo等)でも使える

7. ディレクトリスタック

pushdpopd で管理される履歴(スタック)にもチルダでアクセス可能です。

  • ~+N: dirs -v で表示される番号 N 番目のディレクトリ(先頭から)
  • ~-N: スタックの末尾からN番目

いずれの場合でもNは0から数えます。

# Bash
# 3つのディレクトリをスタックに追加しながら移動
pushd /tmp
pushd /var/mail
pushd /etc

# スタックを確認
dirs -v
# 0 /etc, 1 /var/mail, 2 /tmp, 3 ~

# スタックの2番目のディレクトリ(/tmp)へ移動
cd ~+2

# +を省略してスタックの2番目のディレクトリへ移動
cd ~2

# スタックの末尾から3番目のディレクトリ(/etc)へ移動
cd ~-3

変数への代入ルール

変数の代入時、= の直後や、環境変数の区切り文字 : の直後に ~ がある場合も展開されます。

# Bash
# 代入時の展開
MY_PATH=~/bin
echo $MY_PATH  # /home/user/bin と表示される

# コロン区切りリストでの展開
export PATH=$PATH:~/scripts

# 2回目の=以降は展開されない 
DIR=PATH=~/bin #"PATH=~/bin"という文字列がDIRに代入される

【重要】PATH設定でチルダを使うべきでない理由

便利ですが、.bashrc などで PATH にチルダを含めるのは非推奨です。

  1. 他言語の不具合: PythonやGoなどのプログラムは、PATH内の ~ を「ホームディレクトリ」として解釈してくれない場合があります。
  2. クォート問題: PATH="~/bin:$PATH" と書くと、ダブルクォートにより展開が止まり、パスが通りません。

推奨される書き方:

# $HOME を使えば、ダブルクォート内でも安全に展開されます
PATH="$HOME/bin:$PATH"

他のBash展開との優先順位

Bashの展開プロセスには順序があります。

  1. ブレース展開
  2. チルダ展開
  3. 変数展開 ($VAR)
  4. 算術展開、コマンド置換
  5. パス名展開(ワイルドカード * など)

つまり、変数の中に ~ を入れても、その変数を呼び出した時にはチルダ展開は行われません。

# Bash
DIR="~"
ls $DIR  # エラー: "~" という名前のディレクトリを探しに行く

# 以下の書き方だとチルダ展開が1行目で行われ、2行目で変数展開される
DIR=~
ls $DIR

チルダ展開に失敗するケース

展開に失敗した場合、Bashはエラーを出さず、元の文字列をそのまま残します。

引用符(クォート)で囲まれている

シングル・ダブルどちらのクォートでも展開は止まります。

# Bash
echo "~"
# 結果: ~

echo "~/bin"
# 結果: ~/bin

    単語の先頭以外に ~ がある

    チルダ展開は、単語の先頭(または : や=の直後)にある場合のみ発動します。

    # Bash
    echo foo~ 
    # 結果: foo~
    
    echo /usr/local/~
    # 結果: /usr/local/~

    ログインユーザが見つからない

    ~user 形式の場合、そのユーザーがシステム(/etc/passwd など)に存在しないと展開されません。

    # Bash
    echo ~non_existent_user
    # 出力: ~non_existent_user (そのまま)

    スタックが見つからない

    スタックの範囲外を指定した場合。

    # Bash
    cd ~+99
    # 出力:-bash: cd: ~+99: そのようなファイルやディレクトリはありません

    変数が設定されてない

    参照している変数がunsetされている場合、展開されません。

    unset PWD
    echo ~+
    # 出力:PWD environment variable not set

    ただし、HOMEunsetされている場合に限り、現在のログインユーザのホームディレクトリが代入されて展開されます。

    unset HOME
    echo ~
    # 出力: /home/user

    展開の成功・失敗を判定する方法

    シェルスクリプトなどで「正しく展開されたか」をチェックするには、いくつかのテクニックがあります。

    方法A:単純な比較(スクリプト向け)

    展開が失敗すると、チルダは「ただの文字」として残ります。これを利用して、「先頭が ~ のままかどうか」をチェックするのが最も確実です。

    # Bash
    path=~user/data
    
    if [[ "$path" == "~"* ]]; then
        echo "展開に失敗しました:$path"
    else
        echo "展開に成功しました:$path"
    fi

    方法B:printfecho で確認(デバッグ向け)

    手入力で確認する場合は、単純に echo してみるのが一番ですが、スペースが含まれる可能性を考慮して以下のように確認します。

    # Bash
    # 展開されるかテスト
    test_path=~/Desktop
    printf "%q\n" "$test_path"
    # 展開されていれば、フルパスが表示されます。

    方法C:ディレクトリの存在確認

    そもそもパスとして正しいかを確認する実用的な方法です。

    # Bash
    target=~user/logs
    if [ -d "$target" ]; then
        echo "ディレクトリとして存在します"
    else
        echo "展開失敗、またはディレクトリが存在しません"
    fi

    まとめ

    チルダ展開をマスターすれば、複雑なパス移動もタイピング最小限でこなせるようになります。

    1. 基本は ~~- で爆速移動。
    2. スクリプトでは、クォートの影響を避けるために $HOME を活用。
    3. スタック ~+N を使いこなして、複数ディレクトリを自在に行き来する。

    チルダ展開を使って、明日からのターミナル作業をぜひ高速化してみてください。