ysapでbash入門①

·
#CLI#Terminal#ysap#bash

bash学習記録:基礎編(Chapter 02, Section 02まで)

学習教材


学習環境の準備

zsh vs bash問題

世間はバレンタインデーの中、講師であるロン毛で髭のDave氏を画面越しに見つめてbash入門をスタート。 講師はiTerm2でbashを使用していたが、自分の環境はweztermでzsh。

結論: zshはbashの上位互換的な存在なので、そのまま進めることに決定。

  • 基礎的なコマンド、スクリプト、構文はほぼ同じ
  • zshの方が強力な補完機能や優れた機能が多い
  • macOSのデフォルトシェルもzsh(2019年以降)

特定の演習でbash固有の挙動が必要になったら、その時だけbashコマンドで一時的に切り替える方針。


安全なファイル操作のためのalias設定

重要な発見

Ubuntuサーバーでは設定していたalias rm='rm -i'をMacの.zshrcに設定していなかったことに気づいた。

推奨設定

alias rm='rm -i'    # 削除前に確認
alias cp='cp -i'    # コピー時も確認
alias mv='mv -i'    # 移動/リネーム時も確認

-iオプションの制限

重要: rm -rfと打つと、-f (force) フラグが -i を上書きしてしまう!

より安全な代替案

alias rm='rm -I'    # -I (大文字のアイ) を使う

-Iオプションの特徴:

  • 3ファイル以上削除する時、または再帰的削除(-r)の時に1回だけ確認
  • -fと一緒に使っても確認してくれる

最終決定: バランスの良いalias rm='rm -I'を採用。


weztermの検索機能設定

問題

iTerm2ではCtrl+Fで検索ができるが、weztermでは動作しなかった。

原因

weztermの設定でconfig.disable_default_key_bindings = trueが有効になっており、デフォルトのキーバインドが無効化されていた。

解決方法

keybinds.luaに以下を追加:

-- 検索機能
{ key = "f", mods = "CTRL", action = act.Search("CurrentSelectionOrEmptyString") },

コピーモードとの関係

コピーモード内ではCtrl+Fは「ページダウン」として動作するが、通常モードでは「検索」として動作する。同じキーでもモードによって動作が変わる設計。

コピーモードとは?

  • ターミナルの出力履歴をキーボードだけで移動・選択・コピーできるモード
  • Leader + [Ctrl+Q[)で入入
  • Vimライクな操作(hjklで移動、vで選択開始、yでコピー)
  • 上級者向けの機能だが、通常のマウス操作でも全く問題ない

コマンドライン操作の効率化

基本的な移動コマンド(Emacs由来)

Ctrl + A    # 行の先頭へ
Ctrl + E    # 行の末尾へ
Ctrl + W    # カーソル位置の前の単語を削除

HHKBでの実用的なアプローチ

単語ジャンプ(Option + ←/→)はHHKBではOptionキーが押しにくいため、上記3つのコマンドだけで運用することに決定。

理由: この3つだけで9割のケースはカバーできる。あとはカーソルキーで微調整すれば十分。


シェルビルトインコマンドの理解

manコマンドで表示されないコマンドがある

historyコマンドなどはmanで表示されない。これは「シェルビルトイン」という特殊なコマンドだから。

コマンドの2つのタイプ

1. 外部コマンド(独立したプログラム)

  • ls, grep, catなど
  • /bin//usr/bin/に実行ファイルが存在
  • man lsでマニュアルが見られる

2. シェルビルトイン(シェルに組み込まれた機能)

  • cd, history, echo, aliasなど
  • シェル自体の機能なので独立した実行ファイルがない
  • manでは見られない(マニュアルページがない)

確認方法

type history  # → history is a shell builtin
type ls       # → ls is /bin/ls

ビルトインのヘルプを見る方法

help history        # bashの場合
man zshbuiltins     # zshの場合は全ビルトインのマニュアル

なぜビルトインが必要か?

シェルの内部状態にアクセスする必要があるから

例:

  • cd → シェルのカレントディレクトリを変更
  • history → シェルが記憶しているコマンド履歴を表示
  • export → シェルの環境変数を設定
  • alias → シェルのエイリアステーブルを変更

外部プログラムは別プロセスで動くので、親プロセス(シェル)の状態は変えられない。だからcdなどはビルトインである必要がある。

プログラミングの概念との類似: スコープやカプセル化と同じ。{}の中の変数を外部から直接いじれないのと似ている。

ビルトインと外部コマンドの両方が存在するケース

typeコマンドで調べると、echoprintfkillなどは両方に存在する:

type echo
# → echo is a shell builtin
# → echo is /bin/echo

なぜ両方存在するのか?

  1. ビルトイン版: 速い(プロセス起動不要)、シェルの機能と統合
  2. 外部版: 互換性のため(シェルがない環境でも使える)

どちらが実行される?

ビルトインが優先される。

明示的に外部版を使う方法

/bin/echo "Hello"           # フルパス指定
command echo "Hello"        # commandコマンドでビルトインをスキップ

なぜprintfがシェルにあるのか?

printfはC言語の関数だが、シェルスクリプトでも必要だから。

echoの問題点: シェルによって挙動が違う(移植性が低い)

echo "Hello\nWorld"
# bash: Hello\nWorld (そのまま表示)
# zsh: Hello
#      World (改行される)

printfの利点: POSIX標準で定義されており、どのシェルでも同じ動作

printf "Hello\nWorld\n"     # どのシェルでも同じ!
printf "%s: %d\n" "Count" 42  # フォーマット指定も可能

$PATHの理解

PATHとは?

コマンドの探索パス = シェルが「コマンドがどこにあるか探す場所のリスト」

動作の仕組み

echo $PATH
# /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

これは「:で区切られたディレクトリのリスト」。

lsと打つと:

  1. シェルが/usr/local/bin/lsを探す → ない
  2. 次に/usr/bin/lsを探す → ない
  3. 次に/bin/lsを探す → **あった!**実行

左から順番に探して、最初に見つかったものを実行する。

「PATHを通す」の意味

export PATH="$HOME/bin:$PATH"

これで~/bin/にあるスクリプトがどこからでも実行できるようになる = 「PATHを通した」

自分の環境のPATH確認

見やすく表示する方法:

echo $PATH | tr : '\n'

主要なディレクトリの意味:

  • /opt/homebrew/bin - Homebrewでインストールしたコマンド
  • /usr/local/bin - ユーザーがインストールしたコマンド
  • /usr/bin - macOS標準のコマンド(ls, grepなど)
  • /bin - 基本的なシステムコマンド(sh, bashなど)

PATH設定の整理

問題発見

PATHに重複したエントリがあることを発見:

echo $PATH | tr : '\n'
# .volta/binや.local/binが2回ずつ表示される

原因

.zshrc.zprofileの両方でPATHを設定していた:

.zshrc:

export PATH="$HOME/.volta/bin:$PATH"
export PATH="$PATH:$HOME/.local/bin"

.zprofile:

export PATH="$VOLTA_HOME/bin:$PATH"
export PATH="$PATH:$HOME/.local/bin"

解決方法

.zprofileの重複したPATH設定を削除。

理由:

  • .zprofileはログイン時に1回だけ読まれる
  • .zshrcは新しいシェルを開くたびに読まれる
  • 通常は.zshrcに集約する方が管理しやすい

alias設定でのエラー発見

整理中に.zshrcでエラーが発生:

/Users/username/.zshrc:126: bad assignment

原因: aliasの=の前後にスペースが入っていた

# 間違い
alias rm = 'rm -i'

# 正しい
alias rm='rm -i'

重要: aliasやexportなどの代入文では、=の前後にスペースを入れてはいけない。どっかで見て「そうかそうか」と理解してたものの、実際にミスると重要さがわかるものです。


変数展開時のダブルクォートの重要性

講師の推奨事項

echo "$PATH"のようにダブルクォートを使うことを強く推奨。

理由: スペースが省略されることがあり、バグの原因になる。

具体例で理解する

ケース1: ファイル名にスペース

filename="my file.txt"

# ダメな例
cat $filename
# → cat my file.txt と解釈される
# → エラー: catは"my"と"file.txt"という2つのファイルを探す

# 正しい例
cat "$filename"
# → cat "my file.txt" と解釈される
# → 正しく1つのファイルとして扱われる

ケース2: 空の変数

myvar=""

# ダメな例
[ $myvar = "test" ]
# → [ = "test" ] となり構文エラー!

# 正しい例
[ "$myvar" = "test" ]
# → [ "" = "test" ] となり正常動作

ケース3: 改行を含む変数

list="apple
banana
cherry"

# ダメな例
echo $list
# → apple banana cherry (改行が消える)

# 正しい例
echo "$list"
# → apple
# → banana
# → cherry (改行が保持される)

絶対ルール

変数を使う時は常に"$variable"とダブルクォートで囲む

echo "$PATH"        # ✅
cat "$filename"     # ✅
cd "$directory"     # ✅
rm "$file"          # ✅

例外(クォートなしでもOK)

  • 算術式の中: ((count = $num + 1))
  • [[ ]]の中(bashの場合): [[ $var = test ]]

ただし、これらもクォートありで書いても問題ないので、迷ったら常にダブルクォート!

これはシェルスクリプトのバグの大半の原因となる重要事項。


次回の予定

Chapter 02, Section 03から継続予定。


学びのポイント

  1. zsh vs bash: zshは上位互換なので問題なし
  2. 安全なファイル操作: alias rm='rm -I'を設定
  3. シェルビルトイン: 内部状態を操作するためシェルに組み込まれている
  4. $PATH: コマンド探索パスのリスト、左から順に探索
  5. ダブルクォート: 変数展開時は必ずダブルクォートで囲む(絶対ルール)
  6. 設定ファイルの整理: 重複を避け、.zshrcに集約

特に変数のダブルクォートは、シェルスクリプトでの最重要事項として強く意識する必要がある。