Open15

闇のpath_helperに対する防衛術 (※2025年時点)

ピン留めされたアイテム
enchanenchan

About this scraps

macOSの path_helper について掘り下げて悩んでまとめる

enchanenchan

path_helper問題の歴史 (2025年時点)

  • 1999年:
    • zsh 3.0.6, 3.1.6 リリース[1]
    • /etc/zshenv 以降の /etc/z* ファイルをスキップする NO_GLOBAL_RCS が追加された[2]
  • 2008年:
    • path_helper は OS X Leopard から追加されたよう[3]
      この時点ではバイナリではなく、スクリプトとして実装されていた
    • path_helper の構造的問題がApple support communityに投稿される[4]
  • 2009年:
    • OS X Snow Leopard リリース。
    • path_helper が現在のバイナリ形式に変更された。なんと2021年までソースコードが公開されていた模様。現在でも2012年ごろのソースは閲覧できる[5]
  • 2012年?:
    • path_helper を呼ぶコードは zprofile ではなく zshenv に置かれていたよう[6][7]
  • 2013年:
    • path_helper は壊れている!」「/etc/zshenv/etc/zprofile にすれば解決する!」という論争が起こる[8]
  • 2015年9月末
    • OS X El Capitan リリース。
    • path_helper の呼び出しコードが /etc/zprofile に移動した[9][10]
  • 2015年末~2016年?:
    • zshのオプション no_global_rcs による対策例が投稿されはじめる [10:1][11]
  • 2017年:
    • macOS用の.NET Core がインストール時に /etc/paths.d/dotnet を生成する事例が投稿される[12]
  • 2018年:
    • brew install時のオプション --without-etcdir が削除された[13][14]
    • 当該PRに基づくissueでは非常に激しい議論の痕が残されている。
  • 2021年:
    • VMware Fusionが /etc/paths.d/com.vmware.fusion.public を残していく事例が投稿される[15]
  • 2023年:
    • 私が手元でインストールしたgoが /etc/paths.d/go を生成していた。
脚注
  1. 1999-08-01 : Releases 3.0.6 & 3.1.6 ↩︎

  2. New features in zsh version 3.1.6 (beta version) - Release Notes ↩︎

  3. 最近のMac OSXで、PATHをスマート(?)に管理するやり方。 - こせきの技術日記 ↩︎

  4. path_helper is seriously fubar'd - Apple Community ↩︎

  5. Source Browser - opensource.apple.com (アーカイブ) ↩︎

  6. 【Mac】zshをサブシェルで起動するとPATHがおかしくなる - よんちゅBlog ↩︎

  7. OSX + screen + rbenv でハマった話 - kurainの壺 ↩︎

  8. The notice about path_helper on OS X is incorrect · Issue #381 · sorin-ionescu/prezto ↩︎

  9. Mac OSX El Capitan での ~/.zshenv の扱いについて #MacOSX - Qiita ↩︎

  10. El Capitanにしたらzsh上でのPATHが上書きされた - すぎゃーんメモ ↩︎ ↩︎

  11. zshでPATHがおかしくなる問題の解決編 - 理系学生日記 ↩︎

  12. path_helper ($PATHを設定するコマンド) (macOS, /etc/paths.d, /etc/paths, shell間をまたいだパス設定) - いろいろ備忘録日記 ↩︎

  13. zsh: default unicode9, remove options. by MikeMcQuaid · Pull Request #34422 · Homebrew/homebrew-core ↩︎

  14. Akira Matsudaさんのポスト / X ↩︎

  15. VMware Fusionのアンインストール後も/etc/paths.d/com.vmware.fusion.publicが残る - あとがきのようなもの ↩︎

enchanenchan

主張・思想

人々が何を主張しているのかわからなくなってきた

派閥

  1. ~/.zshenv より後で PATH をいじって欲しくないよ派:
    • これはほんとうにそう
    • PATH 以外はいじられないので、〇〇env としての体裁は保てている?
  2. そもそも path_helper壊れている・バグがある派:
    • 正直わからない
    • /etc/zshenv に置かれていたのは問題あると思うケド(全シェルで毎回PATH再設定してるのはさすがにちょっと変)……
    • 2025年現在も壊れてるって主張は、ちょっとキレすぎじゃないかナ
  3. path_helperPATH の順番をいじらなければ問題はなかったはずだよ派:
    • これもほんとうにそう
    • 事前に PATH の値を取得しておいて、それを尊重するような動作にして欲しかった
    • (もしかしてmacOS的には zshenv を使って欲しくないのか?)
  4. /etc/paths, /etc/paths.d/* をいじっちゃえばいいよ派:
    • わからない
    • 全ユーザに影響が及ぶし、起動スクリプトの中にこれに依存するものがないとは言い切れない
  5. no_global_rcs, --without-etcdir で丸ごとスキップしちゃえばいいよ派:
    • わからない
    • 2023年時点でも /etc/paths.d は使われているので、path_helper を排除するのは厳しい
    • ただ、昔はこれが唯一無二の解決策だった可能性はある
  6. 諦めて ~/.zshrc で設定するのが一番楽だよ派:
    • 正直わかる
    • ただそれは 〇〇rc の責任じゃないと思う

要約

  1. macOSには path_helper というPATH設定ヘルパが存在する
  2. ~/.zshenv より後に呼ばれるので、他環境のdotfileを持ってくるとPATHがおかしな順番になることがある
  3. この子は問題なく動作するが、デフォルトのコマンドを置き換えたいときにとても困る
  4. 一部のプログラムはpath_helperが動作することを前提にしているので、我々はこの子と共存しなければならない
enchanenchan

path_helper とうまくやっていくには?

上述の通り path_helper2025年現在も使われているため、削除・スキップ等の手段で解決するのはよろしくない
ではどうすればよいのか

Apple (macOS) の意図を推測する

OS X Leopard ~ (path_helper 実装初期)

この時点では path_helper/etc/zshenv で呼ばれていたことから、いかなる状況でもシェルコマンド実行前に path_helper が実行されること を想定していた可能性が高い。

どのようにシェルが起動されようと滞りなくコマンドサーチパスが設定されるよう、 (中略) zsh用の「/etc/zshenv」に、path_helperを実行するための記述が施されている。

2010/10/12, ZDNet Japan [1]

この時代のメジャーな解決策は /etc/paths.d/ にファイルを追加する……というものだったよう:

It is very handy. All you do is create a file in /etc/paths.d that contains a single line, the directory you want to add to your path.

2010/02/20, Apple community [2]

If you need to run your PHP commands as CLI sessions, you'll also probably need to add /opt/local/bin as a new path under /etc/paths.d work. For instance, something like this:

shell> sudo echo "/opt/local/bin" >> /etc/paths.d/macports

2008/09/23, Apple Mailing Lists [3]

Messing with the [/etc/|~/.] of [profile|bashrc] files may work, but it's somewhat of a hack. The /etc/paths.d/ directory is the way to go:

2008/12/07, serverfault [4]

echo "/some/other/path" >> /etc/paths.d/GoogleDev

same for /etc/manpaths.d
Just create files with the path/manpath additions you want in those locations.

This is one of the most awesome things Apple has done
2009/09/28, Ars OpenForum [5]

しかし、この頃から手放しで称賛されていたわけではなかった:

I've always been quite happy about most of the stuff Apple does. A lot of their solutions to problems are elegant and pretty. However, there are also some cases in which they do awful stuff under the hood. Stuff that makes me cringe in disgust.

Case in point, the new path_helper command.

2007/11/18, www.kilala.nl [6]

Snow Leopard 以前では、path_helper が呼ばれる前の段階で PATH が設定されてしまっており、本来設定されるべきでない値が返るバグが存在した:

However, if you want to change the order of path lists in /etc/paths, changing the order of lines in this file seems useless ! This because the PATH variable has an initial value of /usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin before a call to path_helper take place. While the origin of this initial value stay obscure to me, this is a true fact.

2008/06/24, SOFTEC.st [7]

つまり人類はもう15年以上このプログラムと格闘しているということになる

OS X El Capitan ~ (/etc/zprofile への移動)

zshのメーリングリストでパスの変更が報告されている。

That's good news, they used to have that code in /etc/zshenv which meant you had no way to override it. It only took them a few years to fix this.

2015/07/31, Zsh Mailing List Archive [8]

しかし、Appleが path_helper/etc/zshenv から /etc/zprofile に移動した明確な動機は見つからなかった。この変更の詳細は不明だが、異なるシェル感の挙動を統一したかった意図もあったのではと個人的に考えている。

それまで、 path_helper の呼び出しは /etc/zshenv に加え /etc/profile にも書かれていた。このファイルは bash がログインシェルとして起動した際にのみ読み込まれるが、zsh (5.9 (arm-apple-darwin24.0.0)) では読み込まれない。
つまりzshは /etc/zshenv から、 bashは /etc/profile から path_helper を呼ぶことになる。

/etc/zshenv は常に読み込まれるため、以下のような挙動のねじれが発生する:

インタラクティブシェル 非インタラクティブシェル
ログインシェル bash/zsh bash/zsh
非ログインシェル zsh zsh

このねじれを「ログインシェルとして起動した際 path_helper が一度だけ呼ばれる」という挙動に統一したかった可能性はある。シェルが起動するたびに path_helper が呼ばれてしまうと サブシェルを起動するだけでPATHの順序が変わる ことになり(多分)、挙動としてもなにやらおかしなことになってしまう。

脚注
  1. Snow Leopard時代のパス管理術 - builder by ZDNet Japan ↩︎

  2. How do I set the $PATH environment variab… - Apple Community ↩︎

  3. Re: How to create/change an environment variable (path) for user "_www" in Mac OS-X Leopard ↩︎

  4. mac osx - How do I set the global PATH environment variable on OS X? - Server Fault ↩︎

  5. how do i add something to PATH in snow leopard? | Ars OpenForum ↩︎

  6. kilala.nl - path_helper: sometimes Apple does kludgy, stupid things ↩︎

  7. Mastering the path_helper utility of MacOSX ↩︎

  8. Re: PSA: Mac OS X El Capitan upgrade might break your $PATH ↩︎

enchanenchan

path_helper とは

プログラムの概要

enchanenchan

macOSの /usr/libexec/path_helper にデフォルトで存在するコマンド。man曰く、

helper for constructing PATH environment variable

The path_helper utility reads the contents of the files in the directories /etc/paths.d and /etc/manpaths.d and appends their contents to the PATH and MANPATH environment variables respectively.

つまり特定のディレクトリ以下にあるファイル群からPATHを構成・出力するもの。

enchanenchan

このコマンド自体は /etc/zprofile, /etc/profileで呼ばれている:

if [ -x /usr/libexec/path_helper ]; then
    eval `/usr/libexec/path_helper -s`
fi

zprofile, profile はシェルのスタートアップファイルのひとつ。
前者はzsh、後者はbash等がログインシェルとして起動したときに読み込まれる[1]

Catalina以降のmacOSではzshがデフォルトなので[2]、以降はzshをメインに取り扱う

脚注
  1. スタートアップ/シャットダウン ファイル | Zsh - ArchWiki ↩︎

  2. zsh を Mac のデフォルトシェルとして使う - Apple サポート (日本) ↩︎

enchanenchan

シェル起動時に -l を与えるとログインシェルとして起動する。
現在のシェルプロセスがログインシェルかどうかは以下のスクリプトで確認できる[1]:

if [[ -o login ]]; then echo "login"; else echo "not login"; fi # 'login' or 'not login'

ターミナル.appでは、「デフォルトのログインシェル」が選択されている場合は毎回 -il が渡される = ログインシェルかつインタラクティブシェルとして起動する

% ps aux | grep /bin/zsh
user      26312   0.0  0.0 410808784   4560 s008  Ss+   3:38PM   0:00.34 /bin/zsh -il

VSCodeはデフォルトで -l が渡される (terminal.integrated.profiles.<platform> で設定できる[2])

脚注
  1. Chapter 2: What to put in your startup files | A User's Guide to the Z-Shell ↩︎

  2. Terminal Profiles in Visual Studio Code ↩︎

enchanenchan

明示的にオプションが渡されなければログインシェルにはならない。

  • パスを指定して実行した場合 (./script.sh, /path/to/script.sh)、スクリプトが実行されるのは非ログインシェルとなる。shebangで -l を設定すると、再度ログイン処理(?)が走る。
  • built-in command[1]. で実行した場合(. ./script.sh)は、現在の環境を引き継ぐため、スクリプトが実行されるのはログインシェルとなる。
  • zsh に直接渡した場合は (当然ながら) コマンドラインオプションに依存する。
脚注
  1. Shell Command Language ↩︎

enchanenchan

path_helper 問題とは

なぜ人々はこのコマンドに苦しめられているのか

enchanenchan

要約すると シェル起動時にPATHの順序が変わってしまう 問題。
pythonruby, nano など、macOSにデフォルトで入っているプログラムを自分でインストールした場合、macOSのデフォルトが優先されてしまうことがある。

影響を受けるのがPATHというuser-dependentな値であること、また歴史的経緯が非常に複雑なことから、2025年現在でも完全無欠の解決策は示されていない(調査した限り)。

enchanenchan

根本的な原因は path_helper の実行タイミングにある。

先述の通り、これを呼ぶコードは /etc/zprofile に記述されている[1]。このファイルは、zshがログインシェルとして起動した際に /etc/zshenv, ~/.zshenv の後、~/.zprofile の前に読み込まれる。

そのため、仮にユーザが ~/.zshenvPATH を設定していた場合 シェル起動からログイン完了までの間に値が書き換えられてしまう。 これが path_helper 問題を引き起こしている。

脚注
  1. OS X El Capitan以降, 後述 ↩︎

enchanenchan

歴史

OS X Leopard で追加されて以来、 path_helper はさまざまな問題を引き起こしてきた。
ここではその歴史を振り返る。

enchanenchan

2007/10/26: OS X 10.5 Leopard リリース, path_helper 誕生

Appleより、最初期バージョンの path_helper を搭載した OS X Leopard がリリースされた。

当時はシェルスクリプトとして実装されていたこと、また現在とは異なり /etc/zshenv から呼ばれていたことが判明している。「/etc/paths および /etc/paths.d を読んでPATHを生成する」一連の動作については、当初から疑問視する声が上がっていた。

また、この時点では path_helper 実行前の PATH を引き継いでしまうバグ が存在したよう(Snow Leopardで修正された)。

Leopard introduced this great(?) new way of setting up default PATH and and MANPATH, in /usr/libexec/path_helper.

Leopard では、/usr/libexec/path_helper を用いてデフォルトの PATH と MANPATH を設定するための素晴らしい(?)新しい方法が導入された。

2008/11, PATH and other evironment issues in Leopard [1]

どのようにシェルが起動されようと滞りなくコマンドサーチパスが設定されるよう、 (中略) zsh用の「/etc/zshenv」に、path_helperを実行するための記述が施されている。(中略) なお、このpath_helperコマンドはLeopard当時シェルスクリプトだった

2010/10/12, Snow Leopard時代のパス管理術 - builder by ZDNet Japan [2]

I guess Apple's reasoning is that it's easier to add extra text files to /etc/paths.d, than it is to add a new PATH= line to /etc/profile. Personally, I think it an in-elegant (and rather wasteful) way of doing things :/
Wait, it's even worse! The path_helper gets called from /etc/profile! Ugh! :(

Apple は、/etc/profile に新しく PATH=... を追加するよりも、/etc/paths.d にファイルを追加する方が簡単だと考えているのではないか。個人的には、これはあまりエレガントではない (むしろ無駄の多い) やり方だと思う :/
待て、これはさらに良くない!path_helper は /etc/profile から呼び出されている! :(

2007/11/18, kilala.nl - path_helper: sometimes Apple does kludgy, stupid things [3]

どうやらpath_helperの実行前に元から設定されていたPATHが一番最初に来るようです。

2008/12/01, 最近のMac OSXで、PATHをスマート(?)に管理するやり方。 - こせきの技術日記 [4]

Therefore, if you want to place /use/local/bin in front of your path, you have to clean it before calling path_helper.

したがって、/usr/local/bin をPATHの先頭に置きたい場合、path_helperを呼び出すより前にPATHを初期化する必要がある。

2008/06/24, Mastering the path_helper utility of MacOSX

脚注
  1. 2008/11, PATH and other evironment issues in Leopard ↩︎

  2. 2010/10/12, Snow Leopard時代のパス管理術 - builder by ZDNet Japan ↩︎

  3. 2007/11/18, kilala.nl - path_helper: sometimes Apple does kludgy, stupid things ↩︎

  4. 2008/12/01 最近のMac OSXで、PATHをスマート(?)に管理するやり方。 - こせきの技術日記 ↩︎

enchanenchan

2009/08/28: OS X 10.6 Snow Leopard リリース

path_helper の実装がシェルスクリプトからバイナリに変更された。また、当時は opensource.apple.com にてソースコードが公開されていた(2021年に削除, 現在でもアーカイブ[1]は閲覧可能)。

脚注
  1. Source Browser - opensource.apple.com ↩︎