ysapでbash入門②
bash学習記録:基礎編(Chapter 03, Section 05まで)
Dave Eddy(ysap.sh)のBashコースで学んだことをまとめます。今回は02-03 vim Crash Courseから03-05 Input/Outputまでの基礎編です。(vimの設定が時間掛かった…
開発環境のセットアップ
Vimの設定:LazyVimとの共存
Daveの設定を使いたいが、普段使っているLazyVimも残したい。調査の結果、Neovimは ~/.config/nvim/ を、Vimは ~/.vimrc を参照するため、競合しないことが判明。
# Daveのdotfilesをクローン
git clone https://github.com/bahamas10/dotfiles.git ~/dotfiles
# vimrc だけをシンボリックリンク
ln -sf ~/dotfiles/vimrc ~/.vimrc
# vimディレクトリもリンク(カラースキームなど)
ln -sf ~/dotfiles/vim ~/.vim
# vim-plugをインストール
curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
# プラグインをインストール
vim +PlugInstall +qall
結果:vim = Daveの設定、nvim = LazyVimで完全に共存
Daveのvimrc の特徴
- vim-plug ベースのシンプルな構成
- プラグインは最小限(airline、ale、rust.vim など)
set nonumberで行番号なし(ミニマル志向)set textwidth=80で80カラム制限- 明るい背景が好み(
set background=light)
カスタマイズ:jkでEsc
ホームポジションから手を動かさずにノーマルモードに戻りたかったので、vimrcに追加:
" Custom key mappings
inoremap jk <Esc>
Escキーは遠いので、この設定は快適。シェル職人?の多くが使っている定番マッピング。僕自身lazyvimを使いたての時からEscがhhkbで若干遠いために使っていたマッピングで、Claude、gptから教えてもらいました。
Dave Eddyのスタイルから学んだこと
ターミナル中心の哲学
Daveの設定を見て気づいたこと:
gitconfig がミニマル
[user]
name = Dave Eddy
email = dave@daveeddy.com
[color]
ui = auto
[push]
default = matching
[pull]
rebase = false
エイリアスなし。完全に素のgitコマンドを使っている。理由:
- コマンドを体で覚える
- どのマシンでも同じ操作(環境依存を避ける)
- スクリプト内でエイリアスは使えないから
シバンの深掘り:なぜ #!/usr/bin/env bash なのか
シバンとは何か
#! は "shebang"(シバン)または "hashbang"(ハッシュバン)と呼ばれる。
#!/usr/bin/env bash
# ↑ この1行目がシバン
役割:
- カーネルに「このスクリプトをどのインタープリタで実行するか」を伝える
- 必ず1行目に書く(2行目以降は無効)
- スクリプトに実行権限(
chmod +x)があれば、./scriptで直接実行できる
3つの書き方とその違い
1. /bin/bash 方式(固定パス)
#!/bin/bash
- bashが
/bin/bashにあると仮定 - Linuxサーバーでは標準的
- PATH検索しないので、若干速い
- 問題:環境依存が強い
2. /usr/bin/env bash 方式(PATH検索)- Dave推奨
#!/usr/bin/env bash
envコマンドがPATHからbashを探す- どこにbashがあっても動く
- 異なる環境でも移植性が高い
- モダンなベストプラクティス
3. /bin/sh 方式(POSIX互換)
#!/bin/sh
- POSIX互換シェル(bashではない)
- 最大の移植性
- Bash固有の機能(
[[, 配列など)は使えない
実際の環境での違い
macOSの例:
# システムデフォルトのbash(古い)
/bin/bash --version
# GNU bash, version 3.2.57 (2007年!)
# Homebrewでインストール
brew install bash
# 新しいbashの場所
which bash
# /opt/homebrew/bin/bash
/opt/homebrew/bin/bash --version
# GNU bash, version 5.2.26 (2024年)
この状況で:
#!/bin/bash→ 古いバージョン(3.2)が使われる#!/usr/bin/env bash→ 新しいバージョン(5.2)が使われる(PATHの順序による)
なぜDaveは env を使うのか
1. 環境の違いに対応
# Linux
/bin/bash
# macOS (Homebrew)
/opt/homebrew/bin/bash
# FreeBSD
/usr/local/bin/bash
# ユーザー独自インストール
/home/user/.local/bin/bash
env を使えば、どこにbashがあっても動く。
2. 新しいバージョンを優先
システムに古いbashがあっても、PATHで新しいものを優先できる。
3. Daveの哲学(多分?:「どこでも動くコードを書け」
環境依存を減らすことで:
- 自分のマシンで動く
- チームメンバーのマシンで動く
- CIサーバーで動く
- 将来別のOSに移行しても動く
セキュリティの考慮
ただし、固定パスの方が安全な場面もある:
# root権限で実行するスクリプト
#!/bin/bash
# PATHが改ざんされていても、固定パスなら安全
サーバー環境やセキュリティが重要な場面では、固定パスも選択肢。しかし一般的な用途では env の柔軟性が勝る。
実験:シバンの有無での違い
# シバンなしのスクリプト
echo 'echo "hello"' > test
chmod +x test
./test
# → 現在のシェルで実行される(zshならzsh)
# シバンありのスクリプト
echo '#!/usr/bin/env bash' > test
echo 'echo "hello"' >> test
chmod +x test
./test
# → bashで実行される
シバンがないと、現在のシェルで実行される。これは予期しない動作の原因になる。
拡張子をつけない:Unixの流儀
Daveはスクリプトに .sh 拡張子をつけない。これもUnix哲学の一部。
なぜ拡張子をつけないのか
1. Unixの伝統
# 標準コマンドを見てみる
ls /usr/bin/ | head
# grep, awk, sed, cat, ls, cp, mv...
# どれも拡張子がない
Unix/Linuxの世界では、実行可能ファイルに拡張子は不要。シバンがあればインタープリタが分かる。
2. 実装の隠蔽
# 今日はBashで書いた
mycommand
# 明日Pythonに書き換えても、呼び出し方は同じ
mycommand
拡張子がないと、中身の実装を変えてもユーザーに影響しない。
例:
# backup というコマンド
./backup
# 最初はシェルスクリプト
#!/usr/bin/env bash
# 後でPythonに書き換え
#!/usr/bin/env python3
# ユーザーは気にしない、./backup で呼ぶだけ
3. コマンドとして使う
# これは不自然
./backup.sh
git-commit.sh
# これが自然
./backup
git-commit
PATHに入れて使う時、拡張子は邪魔。
# ~/bin に配置
~/bin/deploy.sh # ✗ 呼び出し: deploy.sh
~/bin/deploy # ✓ 呼び出し: deploy
4. タブ補完が楽
# 拡張子あり
./back<Tab>up.sh<Tab> # 2回必要
# 拡張子なし
./back<Tab> # 1回で完了
いつ拡張子をつけるか
つけた方がいい場合:
- ソースコードとして管理
project/
├── src/
│ ├── helper.sh # ライブラリ(sourceで読み込む)
│ ├── utils.sh
│ └── config.sh
└── bin/
├── deploy # 実行ファイル(拡張子なし)
└── backup
- 複数言語が混在するプロジェクト
scripts/
├── build.sh
├── test.py
├── analyze.rb
└── deploy.js
ファイルの種類を一目で判別したい時。
- Windows互換性が必要
Windowsでは拡張子が重要。クロスプラットフォームを意識するなら .sh をつける。
つけない方がいい場合(Dave式):
- コマンドとして使う
~/bin/mycommand # PATH に入れる
./deploy # プロジェクトのスクリプト
/usr/local/bin/backup
- Unix的に使いたい
標準コマンドのように扱いたい時。
Daveのリポジトリでの使い分け
実際にDaveのdotfilesを見ると:
# 実行ファイル(拡張子なし)
~/bin/mycommand
~/bin/deploy
# ライブラリ(拡張子あり)
~/lib/helper.sh
~/lib/utils.sh
原則:
- 実行するもの = 拡張子なし
- 読み込むもの(source) =
.shをつける
シバンと拡張子の関係
#!/usr/bin/env bash
# ↑ シバンがあれば、中身がbashだと分かる
# だから拡張子は不要
# ファイル名だけで、その役割を表現する
backup
deploy
git-cleanup
これがUnix哲学:実装の詳細を隠し、インターフェースをシンプルに。
必須ツールのインストール
# bat(catの強化版)
brew install bat
# シンタックスハイライト付きでファイル表示
bat script.sh
Bashの基礎文法
クォートの違い
name='dave'
# シングルクォート = 完全にリテラル(展開なし)
echo 'hello $name' # → hello $name
# ダブルクォート = 変数展開される
echo "hello $name" # → hello dave
覚え方:
- シングル = 静的(そのまま)
- ダブル = 動的(展開される)
条件式:[[ vs [
Daveは [[ を積極的に使う。
# Bash推奨
if [[ -n $1 ]]; then
echo "引数あり"
fi
# 古い書き方(POSIX互換)
if [ -n "$1" ]; then
echo "引数あり"
fi
[[ の利点:
- クォート不要で安全
- 正規表現が使える
&&や||が使える
よく使う条件テスト:
[[ -n $var ]] # 文字列が空でない(not empty)
[[ -z $var ]] # 文字列が空(zero length)
[[ -f $file ]] # ファイルが存在
[[ -d $dir ]] # ディレクトリが存在
[[ $a == $b ]] # 文字列が等しい
for文:2種類のスタイル
シェル伝統的な書き方(リスト反復):
for name in "$@"; do
echo "$name"
done
for file in *.txt; do
echo "$file"
done
C言語風の書き方(カウンター):
for (( i = 0; i < max; i++ )); do
echo "$i"
done
Daveのポイント:C-styleの方が速い
理由:
- 外部コマンド(
seq)を呼ばない - Bash内部で完結
- メモリ効率が良い
# 遅い(seqコマンドを起動)
for i in $(seq 1 1000000); do
:
done
# 速い(Bash内部で処理)
for (( i = 1; i <= 1000000; i++ )); do
:
done
関数
#!/usr/bin/env bash
greet() {
local name=$1 # ローカル変数
echo "Hello $name"
}
# 関数呼び出し
greet "dave"
# コマンド置換で出力をキャプチャ
var=$(greet "dave")
echo "var is $var" # → var is Hello dave
重要:
echo= 標準出力(キャプチャ可能)echo >> file= ファイルに書き込み(キャプチャ不可)
入力処理:read
#!/usr/bin/env bash
# 標準入力から1行ずつ読む
while read -r line; do
echo "we read line: $line"
done
-r オプション必須:
read line # バックスラッシュをエスケープとして解釈
read -r line # rawモード(そのまま読む)
使い方:
# ファイルから読む
./read-input < file.txt
# パイプで読む
cat file.txt | ./read-input
# コマンド出力を処理
ls -1 | while read -r filename; do
echo "Found: $filename"
done
基礎的な発見
隠しファイルの罠
# 間違い:ドットで始まると隠しファイルになる
cp ../hello .hello
# 正解:カレントディレクトリにコピー
cp ../hello .
# 隠しファイルを表示
ls -la
. で始まるファイルは ls で表示されない。基礎中の基礎だが、実際にハマった。
改行の扱い
echo "hello" # 改行が追加される
echo -n "hello" # 改行なし
xxd でバイナリを確認すると:
echo "hello" | xxd
# 68 65 6c 6c 6f 0a ← 最後の 0a が改行
echo -n "hello" | xxd
# 68 65 6c 6c 6f ← 改行なし
変数にコマンド出力を代入する時、改行が含まれていると問題になる場合がある。
Vimの組み合わせ文法
Daveのコースを見ながら気づいた:Vimは「動詞 + 範囲」の組み合わせ。
動詞(操作):
d= delete(削除)c= change(変更 = 削除して入力モード)y= yank(コピー)
範囲(対象):
iw= inner word(単語の中身)aw= a word(単語全体、スペース含む)i"= 「"」の中身ip= inner paragraph(段落)
組み合わせ例:
ciw # 単語を削除して入力モード
di" # "の中身を削除
dap # 段落を削除
ci( # ()の中身を変更
これを知ってから、Vimの操作が格段に速くなった。
学びの本質
このセクションを学んで気づいたこと:
プログラミング言語よりも根本的なことを学んでいる。
.が「現在のディレクトリ」- 隠しファイルの仕組み
- 標準入力/出力の概念
- パイプとリダイレクト
- ファイルシステムの基礎
多くの開発者は「なんとなく」使っているこれらの概念を、本質的に理解できている実感がある。
Unix/Linuxの哲学とシェルは、全てのプログラミングの土台。この基礎を丁寧に学ぶことが、長期的に大きな力になる。
次のステップ
Chapter 4以降で学ぶ内容:
- Case文
- 配列(indexed / associative)
- コマンド置換
- プロセス置換
- テキスト処理(sed, awk, grep)
引き続き、Dave の「実用主義」と「ミニマリズム」から学んでいこうと思う。というか、めちゃめちゃ楽しいぞこれ。
参考リンク: