ysapでbash入門⑧

bash学習記録:基礎編(Chapter 11 , Section 03から最終章まで)

ysapのbashコース、ついに最終章まで完走した。今回はChapter 11のSection 03から最後まで。学習環境もDockerからHetznerのVPSに移行するという変化もあった。

Chapter 11 - 配列とファイル読み込み

whileとreadの役割分担

while read -r line; do
    array+=("$line")
done < data.txt

最初、whileがファイルを読み込んで格納していると思っていたが、実際は役割が分かれている。whileはループの継続判断をするだけで、read -r lineが1行読んで変数に格納し、array+=が配列に追加する。それぞれが独立した仕事をしている。

-rオプションはrawの意味ではなく、バックスラッシュをエスケープ文字として解釈しないという意味。基本的につける癖をつけておくのが安全。

mapfileとwhileの違い

mapfile -t -n 2 foo < data.txt

mapfileはwhileループをワンライナーにしたようなコマンドで、readarrayとも呼ばれる。-tで各行末の改行を取り除き、-nで読み込む行数を制限できる。

-cオプションと-Cでコールバック関数を指定することもできる。

mapfile -t -C my_func -c 1 foo < data.txt

1行読むたびにmy_funcを呼び出す。ドキュメントではこの処理単位をquantumと呼んでいる。デフォルトは5000行ごとなので、明示的に小さくしないと実感しにくいオプション。覚えようとするより「こういうものがある」程度の認識で十分。

nについて

\nのnはnewline(改行)の頭文字で、-nオプションのnはnumber(数)の頭文字。由来が違うのにたまたま被っている。


Chapter 12 - testと[[と((

ビルトインとシェルキーワードの違い

test[はビルトインコマンドとして動く。シェルが普通のコマンドと同じように引数を解釈するので、変数にスペースが含まれると単語分割が起きて予期しない動作になることがある。

[[はシェルキーワードなので、シェル自体が構文として特別扱いする。変数の左辺はクォートなしで安全に扱えて、&&||、正規表現マッチ(=~)も使える。Daveは常に[[を使うと言っていた。

クォートの扱い

[[の中では変数の左辺はクォート不要。ただし右辺にスペースが含まれる場合はクォートが必要。

ワイルドカードを使いたい場合は右辺をあえてクォートしない。

[[ $var == *world ]]   # グロブとして展開される
[[ $var == "*world" ]] # 文字列リテラルになってしまう

数値は((

文字列は[[、数値の計算や比較は((を使う。((の中は算術式として評価されるので$も不要。

num=10
(( num > 5 ))  # $numではなくnumでOK

完全にプログラミング言語の感覚で書けると覚えると混乱しない。


Chapter 13 - Named Pipes

Named Pipesとは

mkfifoで作る特殊なファイル。普通のファイルと違ってデータが蓄積されず、流れて消える。書く側は読まれるまでブロックして待ち、読んだらデータは消える。

mkfifo pipe

使い終わったらrm pipeで削除する。特殊とはいえファイルシステム上は普通のファイルと同じように扱える。

複数プロセスの監視

複数のクライアントプロセスが同時にnamed pipeに書き込んで、読み取り側が全部受け取るという使い方ができる。複数のサービスのログを一箇所に集めて一つの監視スクリプトで拾う、という実用的な応用がある。

これはbashの機能というよりLinuxカーネルの仕組みで、「全てはファイル」というLinuxの哲学が現れている部分。パイプもソケットもデバイスも全部ファイルとして扱えるからこそ成立する。


Chapter 14 - カラー出力

エスケープシーケンス

\eはESC文字(ASCIIコードの27番)で、ターミナルに対して「次に来るのは制御命令だよ」と伝えるための合図。\e\033\x1bは全部同じESC文字の書き方の違いで意味は同じ。

printf '\e[1mBold\e[0m\n'   # Boldにする
printf '\e[32mGreen\e[0m\n' # 緑色にする

エスケープシーケンスは正規表現に見た目が近いが別物。慣れるまでは呪文に見える。

カーソル操作

printf '\e7'        # カーソル位置を保存
printf '\e[20;20H'  # 座標指定で移動
printf '\e8'        # カーソル位置を復元

これを組み合わせると自由にターミナル上にレイアウトが作れる。curl ysap.shでターミナルにアスキーアートが表示されるのもこの仕組みを使っている。サーバー側がUser-Agentを見てcurlからのアクセスと判断し、HTMLではなくエスケープシーケンス込みのテキストを返している。

xxdコマンド

echo "hello" | xxd

16進数ダンプコマンド。実際にどんなバイトが流れているか確認できる。エスケープシーケンスが正しく出力されているか確認するのに使う。

PS1のブラッシュアップ

この章をきっかけに.bashrcのPS1を見直した。ハードコードされていたenokimisfit\u\hに変更。どのサーバーに持っていっても自動でユーザー名とホスト名が表示されるようになった。


Chapter 15 - PS1 Variable

PS1は.bashrcで使っているプロンプト設定そのものの章だった。タイミングよくPS1を整理した直後にこの章が来た。

PROMPT_COMMANDに関数を登録することでプロンプト表示のたびに処理を実行できる。タイトルバーの更新やgitブランチの表示などに使われる。


Chapter 16 - lsの落とし穴

lsを信用しすぎるな

# bad
for f in `ls files`; do
    echo "file is $f"
done

# good
for f in ./files/*; do
    echo "file is $f"
done

スペースや改行を含むファイル名があるとlsはそれを分割して出力する。d eというファイル名がdeの2ファイルに見えてしまう。

lsは人間の目のためのコマンドで、ターミナルに繋がっているときに見やすく整形して出力するだけ。スクリプトでlsの出力を信頼するのは表示と実態が違う砂上の楼閣になる。スクリプトでファイルを扱うならglobやfindを使って実際のファイルシステムに直接問い合わせる。


Chapter 17 - Fork Bomb

コースの締めくくりはfork bomb。

:(){ :|:& };:

:という関数が自分自身を2つ呼び出してバックグラウンドで実行し、それぞれがまた2つ呼び出すことで1→2→4→8と指数関数的にプロセスが爆発してシステムのリソースを食い尽くす。

Daveは関数の中でインクリメントして制御する方法を見せてくれた後、仮想空間で実際に爆発させてコース終了。最高の締め方だった。


学習環境の変化

今回のコースを機にHetznerのVPSを借りた。Macのzshとの差異を気にせずにbashをLinuxネイティブ環境で学べるようになった。

cowrieのハニーポットも設置して攻撃ログを収集中。SSHの入り口は本番、他のポートはcowrieで受けている。

.bashrcもDaveのスタイルを参考にしながら整理した。HISTCONTROLの重複削除、GREP_COLORからGREP_COLORSへの変更、タイトルバー設定の一本化など細かい改善をした。


総括

bashが怖くなくなったのが一番の収穫。全部理解できたわけではないが、「地図を手に入れた」感覚はある。named pipesやfork bombのあたりはbashというよりLinuxカーネルの話で、深く掘ろうとすると尽きない。「こういうものがある」程度の認識で先に進むという割り切りも大事な学びだった。

bashは学問というより筋トレに近い。広く浅く知識を仕入れて、実際に使う中で定着していく。これからはVPSをいじりながら少しずつ書けるようになっていきたい。