プログラマのための IT 教科書
第1部 開発者の道具箱

マシンとシェル

読了目安 30

この章を読むとできるようになること
  • シェルが何をしているか説明できる
  • PATH を読んでコマンドがどう解決されるかを追える
  • macOS と Linux で挙動が違う場面を予測できる

プログラマの仕事道具は、エディタとブラウザとターミナルです。 このうちターミナルだけは、最初に少し勉強しないと何もできません。逆に、一度慣れると一生使えます。

この章では、ターミナルの中で動いている「シェル」が何をしているのかを理解します。 コマンドを暗記するのではなく、打ったコマンドがどう処理されているかを分かるようにするのが目的です。

シェルとは何か

ターミナル(Mac なら Terminal.app や iTerm2、VS Code の統合ターミナル)を開くと、 こういう行が出ます。

intern@macbook ~ %

これはプロンプトと呼ばれ、「コマンドを受け付ける準備ができた」という合図です。 そして、ここでコマンドを待っているプログラムがシェルです。

シェルの仕事は驚くほど単純です。

  1. あなたが打った文字列を受け取る
  2. それを解釈して、対応するプログラムを探す
  3. そのプログラムを起動する
  4. 結果を画面に出して、またプロンプトを出す

つまりシェルは「プログラムを起動する係」です。 ls と打つと、シェルが ls というプログラムを見つけて起動し、その出力を画面に流しています。 ls はシェルの機能ではなく、独立したプログラムです。

ターミナルとシェルは別のもの

ターミナルは「文字を表示して、キー入力を渡す窓」です。 シェルは「その窓の中で動いているプログラム」です。 同じターミナルの中で、シェルを zsh から bash に切り替えることもできます。

まず自分の現在位置を確認してみましょう。

演習

今いるディレクトリの絶対パスを表示してください。ホームディレクトリにいるはずです。

コマンドを入力して Enter を押してください。

~%
  • 未達最後の出力が指定どおりになっている
help で使えるコマンドが見られます

プロンプトの ~ホームディレクトリを表す省略記号です。 Mac なら /Users/あなたの名前、Linux なら /home/あなたの名前 になります。

macOS が Unix である意味

「Linux の使い方を勉強しよう」と言われて、手元にあるのは Mac だと思います。これは問題ありません。

macOS は Unix 系の OS です。Linux も Unix 系です。両者は POSIX という共通の仕様を土台にしているので、 ls cd grep cat といった基本的なコマンドや、ファイルの権限、パイプの仕組みは同じように動きます。 Linux の入門書を Mac で読んでも、9割はそのまま通用します。

問題は残りの1割です。

macOS のコマンドは BSD 系、Linux のコマンドは GNU 系で、 名前は同じなのにオプションの書き方が違うものがあります。

コマンドmacOS (BSD)Linux (GNU)
ファイルを直接書き換えるsed -i '' 's/a/b/' fsed -i 's/a/b/' f
日付を計算するdate -v-1ddate -d '1 day ago'
並列実行xargs -P 4xargs -P 4(ほぼ同じ)

sed -i の差は特に有名で、ネット上のコマンドをコピペして「動かない」と悩む原因の第1位です。 macOS の sed -i は直後にバックアップ用の拡張子を要求するので、空文字 '' を渡す必要があります。

これが本当に効いてくる場所

私たちが本番で動かしているのは GKE 上の Linux コンテナです。 Docker イメージの中身も Linux(Alpine や Debian)です。

つまり、あなたが Mac で書いたシェルスクリプトは、本番では GNU 系で動きます。 「ローカルでは動いたのに CI やコンテナで落ちる」の原因はたいていこれです。 自動化スクリプトを書く時は、この違いを思い出してください。

zsh と付き合う

macOS の標準シェルは zsh です(少し前までは bash でした)。 zsh は補完や履歴が強力で、覚えるだけで作業速度が変わる機能がいくつかあります。

全部打たない

Tab キーで補完できます。 ファイル名でもディレクトリ名でもコマンド名でも効きます。 cd Doc と打って Tab を押せば cd Documents/ になります。

これは速度のためだけではありません。補完が効かないということは、その名前が存在しないということです。 タイプミスをその場で検出できます。

前に打ったコマンドを探す

  • ↑ キー: 1つ前のコマンドを呼び出す
  • Ctrl+R: 履歴を検索する。Ctrl+R を押して docker と打つと、 過去に打った docker を含むコマンドが出てきます

長い kubectl コマンドを毎回打つ人と、Ctrl+R で引っ張り出す人では、1日の作業量が変わります。

行の中を素早く動く

キー動作
Ctrl+A行の先頭へ
Ctrl+E行の末尾へ
Ctrl+W直前の1単語を削除
Ctrl+U行を全部消す
Ctrl+L画面をきれいにする(clear と同じ)

矢印キーを連打して行頭に戻っているなら、Ctrl+A を覚えるだけで作業が軽くなります。

よく打つものに別名をつける

同じコマンドを何度も打つなら、alias で短くできます。

alias ll='ls -la'
alias gs='git status'
alias k='kubectl'

これをターミナルで打つとその場で使えますが、ターミナルを閉じると消えます。 残したい場合は後述の .zshrc に書きます。

PATH — コマンドはどう見つかるのか

ここがこの章の中心です。

ls と打った時、シェルはどうやって ls というプログラムの場所を知るのでしょうか。 答えは、PATH という環境変数に書かれたディレクトリを、左から順に探すからです。

echo $PATH
# /opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin

: 区切りのディレクトリのリストです。シェルはこの順に ls を探し、 最初に見つかった1つを実行します。見つからなければ command not found になります。

which コマンドを使うと、実際にどれが使われるかが分かります。

which go
# /usr/local/bin/go

この「左から順に、最初に見つかったものを使う」という性質が、実務で頻繁に問題を起こします。

演習

このマシンには Go が2つ入っています。今は古い /usr/bin/go が使われています。新しい /usr/local/bin/go が使われるように PATH を直して、which で確かめてください。

コマンドを入力して Enter を押してください。

~%
  • 未達最後の出力が指定どおりになっている
help で使えるコマンドが見られます
「バージョンを上げたのに古いままだ」

新しいバージョンをインストールしたのに古いものが動く、という現象の原因はほぼ PATH です。 古い方が PATH の左側にいるので、そちらが先に見つかっています。

疑うべきは3つです。

  1. which そのコマンド で、実際にどこのものが動いているか確認する
  2. echo $PATH で順序を確認する
  3. 新しい場所を左に足すexport PATH=/new/place:$PATH

環境変数と .zshrc

PATH のような、シェルが持っている設定値が環境変数です。 起動したプログラムにも引き継がれるので、「アプリの設定を外から渡す仕組み」としても使われます。

echo $HOME     # ホームディレクトリ
echo $USER     # ユーザー名
echo $SHELL    # 使っているシェル
env            # 全部の一覧

設定するには export を使います。

演習

環境変数 EDITOR に vim を設定してください。設定できたら env で一覧に出るか確かめましょう。

コマンドを入力して Enter を押してください。

~%
  • 未達環境変数 EDITOR が設定されている
help で使えるコマンドが見られます

新しいターミナルを開くと消える

export した設定は、そのシェルの中だけで有効です。 ターミナルを閉じたり、新しいタブを開いたりすると消えます。

永続化するには、シェルが起動時に読み込むファイルに書きます。zsh の場合は ~/.zshrc です。

# ~/.zshrc
export PATH=/opt/homebrew/bin:$PATH
export EDITOR=vim
alias ll='ls -la'

.zshrc新しいシェルが起動するたびに、上から順に実行されるシェルスクリプトです。 特別な設定ファイル形式ではなく、ただのコマンドの並びです。

編集した後に今のシェルにも反映したい場合は source します。

source ~/.zshrc
演習

先輩が .zshrc に PATH の設定を書きましたが、元の PATH を引き継ぐのを忘れています(source すると grep や git が見つからなくなります)。元の PATH を末尾に引き継ぐ形に書き直してください。

コマンドを入力して Enter を押してください。

~%
  • 未達~/.zshrc の内容が指定どおりになっている
help で使えるコマンドが見られます
PATH を上書きして壊す

上の演習が、実際に起きる事故そのものです。

export PATH=/opt/homebrew/bin        # 元の PATH が消える
export PATH=/opt/homebrew/bin:$PATH  # 元の PATH を引き継ぐ(正しい)

前者を .zshrc に書くと、次にターミナルを開いた瞬間から gitgrep も見つからなくなります。この状態は自力で復旧できますが、 .zshrc を読めるエディタが必要になるので、 第3章の vim を知っているかどうかで難易度が変わります。

シングルクォートとダブルクォートの違い

'...' は中身をそのまま文字として扱います。 "..." は中身の $変数 を展開します。

echo 'PATH は $PATH です'   # PATH は $PATH です
echo "PATH は $PATH です"   # PATH は /opt/homebrew/bin:... です

設定ファイルに $PATH という文字を書きたい時は、シングルクォートを使います。

Homebrew

Mac に開発ツールを入れる時は、公式サイトから .dmg を落とすのではなく、 Homebrew というパッケージマネージャを使うのが普通です。

brew install jq          # インストール
brew upgrade jq          # 更新
brew uninstall jq        # 削除
brew list                # 入っているものの一覧

コマンド1行で入り、1行で消せて、更新も一括でできます。 何が入っているかを一覧できるのも重要で、環境を再現しやすくなります。

brew install coreutils

さきほどの BSD と GNU の違いを、Homebrew で埋めることもできます。

brew install coreutils

これで GNU 版のコマンドが gsed gdate gls のように g 付きの名前で入ります。 Linux と同じ挙動で動かしたい時に使います。

ジョブ制御 — 「固まった」の正体

コマンドを実行したまま反応がなくなることがあります。多くの場合、固まっているのではなく プログラムがまだ動いているか、入力を待っているだけです。

操作意味
Ctrl+C実行中のプログラムに「やめろ」と伝える(SIGINT)
Ctrl+Z一時停止してシェルに戻る
fg一時停止したものを再開する
bg一時停止したものを裏で動かす
jobs裏で動いているものの一覧
Ctrl+D入力の終わり(EOF)を伝える。シェル自体を終了させることもある

よくあるのは、cat を引数なしで実行して入力待ちになるパターンです。 これは固まっているのではなく、キーボードからの入力を待っています。Ctrl+D で終わります。

この節はシミュレーションでは試せません

ジョブ制御は「プロセスが動いていること」が前提なので、この教科書のターミナルでは再現できません。 実機のターミナルで sleep 30 を実行して Ctrl+CCtrl+Z を押してみてください。 5秒で終わる実験です。

実務の落とし穴まとめ

この章で出てきた「時間を溶かすポイント」を再掲します。

  1. sed -i の BSD / GNU 差 — Mac で動くスクリプトがコンテナで落ちる
  2. PATH の順序 — 「バージョンを上げたのに古いままだ」の原因
  3. .zshrc で PATH を上書き — 次のターミナルからコマンドが見つからなくなる
  4. export はそのシェル限り — 「設定したのに消えた」の原因
  5. ダブルクォート内の $ 展開 — 設定ファイルに意図しない値が焼き込まれる

まとめ

  • シェルは「プログラムを起動する係」。ls はシェルの機能ではなく独立したプログラム
  • macOS は Unix 系なので Linux の知識がほぼ通じる。ただし BSD と GNU の差がある
  • 本番は Linux コンテナ。スクリプトを書く時はこの差を思い出す
  • PATH左から順に探し、最初に見つかった1つを使う。困ったら which
  • 環境変数は export で設定。永続化は ~/.zshrc
  • Tab 補完と Ctrl+R を使うだけで作業速度が変わる

章末問題

新しくインストールした Go 1.25 を使いたいのに、go version と打つと 1.19 が表示されます。最初に確認すべきことは何ですか。

~/.zshrc に export PATH=/opt/homebrew/bin と書いてターミナルを開き直したら、git も grep も見つからなくなりました。何が起きていますか。

設定ファイルに「$PATH という文字列そのもの」を書きたい時、正しいのはどれですか。

次の章では、シェルの上で実際に使うコマンドを覚えます。 パイプを使ってコマンドをつなげられるようになると、ログ調査が一気に楽になります。

読み終わったら記録しておくと、目次で進み具合が分かります。