sh -cコマンドや、そこからスクリプトを呼び出したときの挙動、特にzsh周りの設定ファイル(.zshrcなど)がどう読み込まれるかについて整理する。
DockerのエントリポイントやCI/CDの設定、外部ツールからの呼び出しで地味にハマるポイントだ。
sh -cとは何か?
一言で言うと「引数の文字列をコマンドとして解釈して実行する」やつ。
sh -c "echo Hello World"普通にコマンドを打つのと何が違うかという話だが、主に以下のシーンで必須になる。
sudoでリダイレクトしたい時
sudo echo "text" > /etc/fileは権限エラーになる。リダイレクトは一般ユーザー権限で行われるためだ。
sudo sh -c "echo 'text' > /etc/file"ならシェル全体がrootで動くので成功する。
Docker/K8sで&&や|を使いたい時
CMDやENTRYPOINTで複数のコマンドを繋げたりパイプを使いたい場合、シェルを介さないと構文解析されない。
Go製バイナリなどをsh経由で実行したい時
sh ./my-binaryは「バイナリをシェルスクリプトとして読もうとする」のでエラーになる。
sh -c ./my-binaryは「そのパスにあるコマンドを実行せよ」となるので、バイナリでも実行できる。
環境変数の継承ルール
sh -cで呼び出した先のスクリプトに、環境変数は渡るのか?
検証コード
# 親側export MY_ENV="hoge"sh -c "./sample.sh"echo "$MY_ENV"結果
- exportしていれば渡る (
hogeと出る) - exportしていなければ渡らない (空になる)
exportされた変数は子プロセス(sh -c)から孫プロセス(sample.sh)へと引き継がれる。当たり前だけど、sh -cは別プロセスが立ち上がることを意識しておく必要がある。
Shebang(シバン)は有効か?
main.shからsh -c経由でzsh用のスクリプトを呼んだ場合。
main.sh
sh -c "./sample.sh"sample.sh
#!/usr/bin/env zsh# zsh特有の処理...結果
ちゃんとzshとして動く。
shは単なるランチャーとして機能し、OSがsample.shの1行目(Shebang)を見てzshを起動してくれる。
ただし、source(または.)で読み込んだ場合はNG。
- ✅
sh -c "./sample.sh"→ zshが起動して実行(Shebang有効) - ❌
sh -c ". ./sample.sh"→ shのまま読み込まれる(Shebang無視=シンタックスエラー)
zshの設定ファイルは読み込まれるか?
sample.sh(zshスクリプト)が実行されたとき、.zshrcや.zprofileは読み込まれるのか?
結論: ほとんど読み込まれない
| ファイル名 | 読み込まれる? | 理由 |
|---|---|---|
.zshenv | YES | 唯一読み込まれる。全モードで有効。 |
.zprofile | NO | 「ログインシェル」ではないため無視。 |
.zshrc | NO | 「対話的シェル」ではないため無視。 |
.zlogin | NO | 「ログインシェル」ではないため無視。 |
スクリプト実行(非対話モード)では、余計な出力やエイリアスによる事故を防ぐため、.zshrcはスキップされる。
.zshrcに書いたエイリアスがスクリプト内で効かないときはこれが原因。
どうしても読み込ませたい場合
スクリプト内で明示的にsourceするしかない。
#!/usr/bin/env zshsource ~/.zshrc # 無理やり読み込む
my_alias_commandただし、.zshrcにechoなどの出力が含まれているとスクリプトの出力に混ざるので非推奨。
スクリプトで必要な環境変数は.zshenvに書くのが正解(いいか悪いかは置いといて)。
まとめ
sh -cは便利だが、プロセスの境界や設定ファイルの読み込みルールを理解していないとハマる。
特にDockerやCI/CD環境では、ローカルで動いてもコンテナ内で動かないことがある。環境変数のexport忘れや、.zshrcへの依存が原因のことが多い。
スクリプトで使う設定は.zshenvに集約し、エイリアスではなく関数やスクリプトで対応するのが無難だ。
hsb.horse