世界を動かす技術を、日本語で。

あなたのPATHにあるチルダは、あなたのHOMEではないかもしれません

2026年10月11日原文(disconnect3d.pl)

概要

  • PATH 設定時の ~(チルダ)展開 の落とし穴について解説
  • .bashrc や .zshrc での PATH 追加時の注意点
  • ~ を使った場合の予期せぬ挙動とその理由
  • $HOME を使った安全な記述方法の紹介
  • PATH の確認方法と修正手順

PATH設定時の~(チルダ)展開に関する注意

  • .bashrc や .zshrc で export PATH="$PATH:~/.local/bin/" のように記述すると、 ~ が正しく ホームディレクトリ に展開されない場合が多い
  • Bash や Zsh では、 チルダ展開 は アンコーテッド(クォートされていない)~ にのみ適用
  • クォート された~は、そのままの文字列として扱われ、 /home/<user>/.local/bin/ ではなく ./~/.local/bin/ となる危険性
  • PATH に ./~/.local/bin/ が追加されることで、意図しないディレクトリが参照されるリスク
  • $HOME を使った export PATH="$PATH:$HOME/.local/bin/" が安全な記述方法

Bashドキュメントにおけるチルダ展開の仕様

  • Bash公式ドキュメント では、 アンコーテッドの~ のみが チルダ展開 対象と明記
  • 変数代入 時や :(コロン) の直後で アンコーテッド~ が現れると展開される仕様
  • export PATH=$PATH:~/.local/bin のような記述は Bash や Zsh で展開されるが、 ホワイトスペース が混入すると失敗する脆弱性
  • クォート すると展開されず、意図しないディレクトリがPATHに追加される事例

実際の挙動と再現例

  • mkdir -p ./~/.local/bin/ で カレントディレクトリ 配下に ~/.local/bin/ ディレクトリ作成
  • gcc で ./~/.local/bin/kek バイナリを作成し、 PATH="~/.local/bin/" kek で実行
  • tree -f . で ./~/.local/bin/kek が実行されたことを確認
  • ホームディレクトリ ではなく、 カレントディレクトリ 配下の ~/.local/bin/ が参照された実例

PATHの確認方法と修正手順

  • echo "$PATH" | grep -- '~' で PATH 内に ~ が含まれているか確認
  • echo "$PATH" | tr ':' '\n' | grep '~' で 各PATHエントリ を1行ずつ表示し、 ~ の有無を確認
  • ~ が表示された場合は、 .bashrc/.zshrc/.profile 内の該当記述を $HOME で置換し修正

nonoツールの警告について

  • nono agent sandboxing tool は PATH に書き込み可能なエントリとして ~/.local/bin/ を警告
  • この警告により、 PATH設定ミス や 意図しないディレクトリ参照 を未然に防止
  • 警告文 をより詳細にする提案(PR)も進行中

まとめ

  • PATH 設定時は ~ の展開挙動に注意
  • $HOME を明示的に使うことで安全性向上
  • PATH の内容を定期的に確認し、不要・危険なエントリを除去
  • nono などのツールによる警告も有効活用

Hackerたちの意見

~/.local/binのPATHが動いてない時点で、気づくべきだったんじゃない?

それか、チルダを引用しないって手もあるよね。みんなBashで「文字列」を無意識に引用するけど、実は(ほぼ)全てが文字列で、大抵の文字列は引用しないのが普通なんだよね(例えば、誰も "ls" "-a" "foo" なんてやらないし)。

└

コツは、明示的かつ正しく引用することだよ。「と’は違うからね。

└

ただし、Pythonからシェルコマンドを実行する場合は別だね。ああいう風にコマンド文字列が「文字列」引数に分解されるのを見たのは初めてだった。

└

それか、bashを使うのをやめるのもあり。書くのがめちゃくちゃ面倒な言語だし、足を引っ張る要素が多いから。

└

人は引用をしすぎたり、逆に全然しなかったりするよね。一般的なルール1. ダブルクォートはドルサインの式にだけ使って、他は使わない。foo "$bar"/foo baz:"$(cat example.txt)" exec cmd "$@" 2. リテラルの特殊文字を含む単語はシングルクォートで囲む。それ以外はなし。 'Die Hard' 'ke$ha' --- ただ、著者の例は異なる引用で修正されるわけじゃないから注意してね。# 元の export PATH="$PATH:~/.local/bin/" # 不要な引用なしで export PATH="$PATH":~/.local/bin/ チルダの展開は単語の最初でしか起こらないからね。

└

これは常識だと思うし、わざわざ言うほどのことじゃないと思う。でも、記事を見る限り、どうやら常識じゃないみたい。私もどこかでこれに遭遇したことがあるかな?もちろんあるよ。引用をもっと上手に、適切なところだけでできるようになったかな?それも間違いなくそうだね。Bourne互換のシェルは習得に時間がかかるし、経験も必要だよね。これは変わらないけど、代替手段もあって、それぞれに注意点があるよ。

└

POSIXのルールに従うと、適切な方法はこうだと思う: export PATH="$PATH:"~/.local/bin もしかしたら間違ってるかもしれないけど、間違ってなければ、これはどのPOSIX準拠のシェルでも動くよ。追記: これ、私の携帯のmkshでちゃんと動くみたいだ: :/ $ export PATH="$PATH:"~/.local/bin :/ $ print $PATH /product/bin:/apex/com.android.runtime/bin:/apex/com.android.art/bin:/system_ext/bin:/system/bin:/system/xbin:/odm/bin:/vendor/bin:/vendor/xbin:~/.local/bin

└

マニュアルを読んだ方がいいよ。

└

この機能、もしくは問題の構文単位には「ベアワード」って名前があるよ。PerlやRuby、YAML、他の言語でも同じようなものがあるかも、よくわからないけど。一般的にシェルを使いこなしてる人でもこれを見たことがあるよ。よくわからないけど。

└

ティルダを引用しないのは役に立たないね。: tmp; echo $PATH:~ /usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:~ 編集: mjmasが指摘してくれたけど、変数の代入には異なるカルバンボールのルールが適用されるから、俺が間違ってた。: tmp; x=$PATH:~ : tmp; echo "$x" /usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/home/user それに、一般的に君のアドバイスはすごく悪いアドバイスだよ。コマンドライン引数では、ほとんどのシェルスクリプトのコードがそうなんだけど、変数展開を含む文字列は常に引用符で囲むべきだよ。そうしないと、変数展開の後にスペースで暗黙的に分割されちゃうからね。変数に格納しているものは原子的な文字列じゃなくて、スペースで区切られたリストだから、これが欲しいことはほとんどないし、リストを格納したい稀な場合でも、シェルの暗黙のスペース分割はスペースを含むファイル名で壊れちゃうことが多いんだ。Bashには実際の配列変数があって、これを一級の方法で扱うことができるけど、すごく面倒だよ。: tmp; x=("foo bar" baz) : tmp; echo "${x[@]}" foo bar baz : tmp; touch "${x[@]}" : tmp; ls -l "${x[@]}" -rw-r--r-- 1 user user 0 Oct 11 17:52 baz -rw-r--r-- 1 user user 0 Oct 11 17:52 'foo bar' このkshの機能はBourneシェルやdashにはないけど、MirBSD ksh、Bash、zshにはあるよ。一般的に、シェルスクリプトの中でダブルクォートの外に$variable $expansionがあるときは、ファイル名やディレクトリ名にスペースが含まれていると、シェルスクリプトが失敗する可能性が高いと思っておくべきだよ。多くの場合、これがパスインジェクションのセキュリティ脆弱性につながるんだ。引用されていない$variable $expansionが安全なコンテキストもあるけど、それは比較的少ないよ。でも、ここでの重要な詳細!その安全なコンテキストの一つは実際には変数の代入で、mjmasが下の役立つコメントで指摘してくれたように、引用されていない変数展開は実際には完全に安全なんだ。: tmp; a='x y' : tmp; b=α:$a:ω : tmp; echo "$b" α:x y:ω

「いつも私にはうまくいくように見える」って言おうと思ったら、「…実際にはBashとZshで動くから…」って見ちゃった。ポータブルにしようとする時に注意しないといけないBash特有のことだね。多くのシステムでは/bin/shはbash(またはzsh)じゃないから、Bash特有の機能に頼りたいなら(または探すのが面倒なら)、具体的に「#!/bin/bash」を使うべきだよ。Debianとかだと、例えばdashが使われてることが多いし。

Hacker Newsで議論の続きを見る