3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

zshenv vs $PATH vs SSH

Last updated at Posted at 2025-03-29

環境

  • MacBook Air M2 2022
  • Sonoma 14.3.1
  • zsh 5.9 (x86_64-apple-darwin23.0)

起こったこと

sshのエラー

ある日、研究室のサーバに ssh しようとすると、以下のようになった。

% ssh remote_host
zsh:1: command not found: ssh
kex_exchange_identification: Connection closed by remote host
Connection closed by UNKNOWN port 65535

command not foundと出たのでパスが通ってないのかな?と思い、確認したが大丈夫そう。

% which ssh
/usr/bin/ssh
% print -l $path | grep /usr/bin
/System/Cryptexes/App/usr/bin
/usr/bin
/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin
/Library/Apple/usr/bin

やったこと

zsh の設定をいじることがあったので、そのせいかな?と思い、下記を実行。

% zsh --no-rcs
% ssh remote_host
zsh:1: command not found: ssh
kex_exchange_identification: Connection closed by remote host
Connection closed by UNKNOWN port 65535

この時もwhich sshをすると見つかるし、$PATHにも/usr/binは追加されている。
だめだ、、、

ただ、その過程でこのようなエラーに出くわした。

% zsh
/etc/zshrc:7: command not found: locale

そんな訳はないだろうと思い、which localeを実行するも、locale not foundとのこと。
え?じゃあ$PATHどうなってるねんということで、確認した結果をいかに示す。

# 上のコマンドで立ち上げているzshの中で実行
% print -l $path
/opt/local/libexec/gnubin
/usr/local/bin
/opt/local/bin
/opt/local/sbin
/usr/local/lib
/usr/sbin

これらはすべて、私の~/.zshenvで設定しているものである。
ちなみに、exitして元の zsh に戻ると$PATHは元通りになっている。

ここまでのまとめ

  1. ssh を実行するとcommand not foundが出るが、パスは通っていそう
  2. zsh --no-rcsで新たに立ち上げた shell の中でも同じ結果に
  3. zshにより立ち上げた shell は様子($PATHの設定)がおかしい

原因

~/.zshenv を以下のように変更していたのが原因だった。

# 変更前
% cat ~/.zshenv
- export PATH="/opt/local/libexec/gnubin:/usr/local/bin:/opt/local/bin:/opt/local/sbin:/usr/local/lib:/usr/sbin:$PATH"

# 変更後
% cat ~/.zshenv
+ export PATH="/opt/local/libexec/gnubin:/usr/local/bin:/opt/local/bin:/opt/local/sbin:/usr/local/lib:/usr/sbin"

差分は最後に$PATHをつけるかつけないか、つまり、$PATHの設定を追加にするか上書きにするかである。

考察

なぜこのようなことがおきたのだろうか?
そもそも、zshenvzsh の設定ファイルの中で最初に読まれる設定ファイルのため、上書きしても元の$PATHが空なので問題ないはずである。

zsh の設定ファイルと$PATHの設定

設定ファイルの読み込み順

まず、zshの設定ファイルの読み込み順を確認しておこう。
man zshを見てみると、以下のように書いてあった。

Commands are first read from /etc/zshenv; this cannot be overridden.
[中略]
Commands are then read from $ZDOTDIR/.zshenv. If the shell is a login shell, commands are read from /etc/zprofile and then $ZDOTDIR/.zprofile. Then, if the shell is interactive, commands are read from /etc/zshrc and then $ZDOTDIR/.zshrc. Finally, if the shell is a login shell, /etc/zlogin and $ZDOTDIR/.zlogin are read.
[中略]
If ZDOTDIR is unset, HOME is used instead. Files listed above as being in /etc may be in another directory, depending on the installation.

要するに、$ZDODIRが設定されていない私のような環境では、zsh を立ち上げた時以下の順で設定ファイルが読み込まれる。

順番 設定ファイル名 備考
1 /etc/zshenv 私の環境にはない
2 ~/.zshenv
3 /etc/zprofile ログインシェルの時のみ
4 ~/.zprofile ログインシェルの時のみ
5 /etc/.zshrc
6 ~/.zshrc
7 /etc/zlogin ログインシェルの時のみ (私の環境にはない)
8 ~/.zlogin ログインシェルの時のみ (私の環境にはない)

$PATHに関する設定

次に、$PATHに関する設定を確認しておく。上に示した設定ファイルのうち、$PATHに関する設定を行うものを 1 つずつ確認していく。

  • ~/.zshenv
    • 先ほど示したように、主に/opt以下のパスを追加している。 (MacPorts のため)
  • /etc/zprofile
    • /etc/zprofileでは、path_helperというコマンドを呼んでいる。
    • man path_helperによると、path_helperは、/etc/paths/etc/paths.d/*に書かれた内容を元に、$PATHを設定する。
    • /etc/pathsの中身は以下の通り。
      /usr/local/bin
      /System/Cryptexes/App/usr/bin
      /usr/bin
      /bin
      /usr/sbin
      /sbin
      
    • /etc/paths.d/*の中身は以下の通り。
      /var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin
      /var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin
      /var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin
      /opt/X11/bin
      /Library/Apple/usr/bin
      
  • ~/.zprofile
    • ~/.zshenvと同様、MacPorts 関係の設定をしている。

zsh から zsh を立ち上げた時の挙動

ここまででこの記事で何を言いたいのか分かってきたと思うが、zshenvを$PATH 上書き方式にした時にzshを立ち上げた時の挙動を確認しておこう。
まず、ログインシェルを立ち上げた時には、上の 3 つの設定ファイルの$PATHが問題なく読み込まれる。
なぜならば、~/.zshenvより前に$PATHについての設定はされないからである。

厳密には、~/.zshenvを読む前に/usr/bin:/bin$PATHに追加されるようだ。
(~/.zshenv の 1 行目にecho $PATHを追加して確認した)
また、terminal 特有の$PATHの設定も~/.zshenvの読み込みの前にされるようだ。
私は kitty を使っているが、その場合、上記に加えて /Applications/MacPorts/kitty.app/Contents/MacOS:/usr/sbin:/sbinなども$PATHに追加されていた。

しかしながら、ログインシェルからzshを立ち上げた場合、~/.zshenvが最初に読み込まれるため、今までの$PATHは上書きされてしまう。
/etc/zprofileはログインシェルの時のみ読み込まれるためため、/etc/zprofileで設定した/usr/bin/bin$PATHに追加されない。
そのため、localeなどのコマンドが見つからないというエラーが出てしまったのである。

sshの挙動について

次に、sshの挙動について考えてみる。
なぜログインシェルでsshを実行した時に、command not foundが出たのか。
先程の話から考えると、ログインシェルでは$PATHの設定は問題ないはずである。

ここで、zshenvを$PATH 上書き方式にした時のsshの挙動について以下のことがわかった。
前提として、私が ssh しようとしたサーバは、別のサーバを経由して接続するような設定になっている。

Host public_host
 User hoge
 HostName public_host.hoge.jp

Host remote_host
 HostName remote_host.hoge.jp
 ProxyCommand ssh -W %h:%p public_host.hoge.jp

この時、public_hostの方には問題なく ssh できるのだ。
しかしながら、remote_hostの方には接続できない。
public_host にもsshはインストールされており、$PATHにもsshまでのパスは通ってため、public_host の問題によるエラーではなさそうだ。

今までの話を考慮して、sshでは踏み台になるサーバを経由してsshする場合に限って、shell を新たに立ち上げているのではないかと考えた。

そこで、以下の github を見てsshの実装を確認してみることにした。

すると、sshconnect.cに定義されたssh_proxy_connect関数の中に以下のようなコードがあった。

ssh_proxy_connect(struct ssh *ssh, const char *host, const char *host_arg,
    u_short port, const char *proxy_command)
{
    [中略]

	if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
		shell = _PATH_BSHELL;

    [中略]

	/* Fork and execute the proxy command. */
	if ((pid = fork()) == 0) {

        [中略]
		argv[0] = shell;
		argv[1] = "-c";
		argv[2] = command_string;
		argv[3] = NULL;

		/*
		 * Execute the proxy command.  Note that we gave up any
		 * extra privileges above.
		 */
		ssh_signal(SIGPIPE, SIG_DFL);
		execv(argv[0], argv);
		perror(argv[0]);
		exit(1);
	}
  [中略]
}

おそらくこの部分でforkして新たに shell をexecして呼んでいるのだろう。
そのため、ProxyCommand を指定している場合には、sshを実行した時に新たに shell が呼ばれ、上記の$PATHの設定が誤った適用されてしまうと考えられる。

まとめ

  • ~/.zshenvの設定を上書き方式にしてしまったため、ログインシェルでない shell を立ち上げた時に、$PATHが上書きされてしまった。
  • sshでは、踏み台になるサーバを経由してsshする場合に限って、shell を新たに立ち上げているため、その場合にはcommand not foundが出てしまう。
3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?