【読了】新しいシェルプログラミングの教科書

一介のソフトウェアエンジニアとしてシェルスクリプトくらいさらさらと書けますよね?......僕は書けません。というわけで「新しいシェルプログラミングの教科書」を読んだのでその際のメモです。

www.amazon.co.jp

  • Chapter01: シェルとは
    • ログインシェルの確認は$ cat /etc/shells
    • 新しいシェルの起動は$ bash、終了は$ exit
    • シェル=コマンドインタプリタ
    • 「どれだけ便利にコマンドを実行するか」
    • コマンドインタプリタの機能をカーネルから取り出したもの → 「1つのことをうまくやらせる」
    • コマンド実行はforkし、子プロセス上でexecする
  • Chapter02: シェルスクリプトとは
    • シェルスクリプトを書く = シェルでプログラミングする
    • コマンドを組み合わせて新しいコマンドをつくる
    • シェルスクリプトのメリット
      • 再利用性
      • typoの防止
    • デメリット
    • 利用例
      • 新たなコマンドの作成
        • コマンドが実行可能フィアルなのかシェルスクリプトなのかはfileコマンドで確認できる
  • Chapter03: 基本
    • 改行=エンターキー = 実行
    • 複数行に書きたいときは\
    • 一つの行にまとめたいときは;
    • 複数行コメントは存在しない
    • bashコマンドは引数をスクリプトとして読み込んで実行→実行権限がいらない
      • スクリプトがどのシェルで書かれているかを知っていないといけない
      • ./scriptはいつでも有効だが、bash scriptはscriptが例えばバイナリだと動かない
  • Chapter04: 変数
    • 値は文字列として解釈される
    • $をつけて参照
    • 明示的に参照するときは${}で囲む
      • 変数のすぐ後ろに入力がある時使える
    • 環境変数 = コマンドに引き継がれる変数
      • コマンドから参照できるかどうか
      • exportで変数を環境変数にする
      • 慣例として変数名を大文字にする
    • $1, $2, $3 … でスクリプトに渡された引数を参照できる
      • $*, $@で全てを一度に参照できる
      • 通常は$@で参照
    • IFSは区切り文字を表し、参照する際はecho "$IFS" | od -aとする
    • 特殊パラメータ
      • $# ... 引数の個数
      • $? ... 前回コマンドの終了ステータス
      • $$でプロセスIDを取得、tmpのファイル名などに利用
    • declareコマンドで変数の型を宣言できる
      • 整数型の場合代入の右辺は算術評価される
    • 配列の宣言はvar=(a b c)、要素の参照は${var[i]}、追加はvar+=(value1 value2)、削除はunset var[i]、要素数の取得は${#var[@]}
    • 連想配列を作る際はdeclare -Aで明示する必要がある
  • Chapter05: 展開・クオーティング
    • 展開の種類 ... パス名展開、ブレース展開、チルダ展開、パラメータ展開、コマンド置換、算術評価・展開、プロセス置換、履歴展開
    • パス名展開
      • マッチするファイルが存在する場合のみ有効
      • 正規表現とまた違うマッチング規則なので注意
      • ドットで始まるファイルを展開したいときは明示する必要がある
    • ブレース展開
      • マッチするファイルがなくてもいい
        • 新しいファイルを作るときなどに利用
    • チルダ展開
    • パラメタ展開
      • $varとか${var}とかのこと
      • ${name:-value} ... デフォルト値の指定
      • ${name:=value} ... デフォルト値を代入して返す
      • ${name:?value} ... 値がない時のエラーメッセージの指定
      • それぞれ:を除くと値が未設定の時のみの動作になり、空文字のときは空文字がそのまま返る
      • ${name:index:length}で文字列のスライシングができる
      • ${#name} ... 文字数が返る
      • ${name#pattern} ... 先頭から最短マッチでpatternを切り落とす
        • ファイル名の取得 ${path##*/}
      • ${name%pattern} ... 末尾から最短マッチでpatternを切り落とす
      • それぞれ記号を二つ重ねると最長マッチになる
      • ${name/pattern/substring} ... マッチするパターンを置換して返す
        • 使い所不明
    • コマンド置換
      • $()と``は等価
    • 算術評価・展開
      • ((算術式)) ... 終了ステータスが返る
      • $((算術式)) ... 評価結果が返る
      • exprコマンドでもいいけどこっちの方が早い
      • letも等価だが、読みやすさを考えると代入のみに使うのがおすすめ
    • プロセス置換
      • <()で標準入力が二つ欲しい時にパイプラインの代わりとして使える
      • (パイプラインの本質は一時ファイルなんだなー...。)
    • 履歴展開
      • !から始まる入力でコマンドの実行履歴を参照できる
      • スクリプトで使うことはまずない
    • クオーティングで特殊文字を無効化する
      • \特殊文字エスケープ
      • ''で全ての展開のエスケープ
      • ""は特殊文字を一部だけ無効化する
        • 「パラメタ展開」「コマンド置換」「算術展開」
  • Chapter06: 制御構造
    • 条件分岐
      • if (条件); then ~ elif ~ else ~ fi
      • 条件はコマンドとして実行され、終了ステータスが0のとき真、0以外のとき偽に評価される
      • []はtestという"コマンド"
      • :はヌルコマンドといい、必ず正常終了する
      • command1 && command2 ... command1の終了ステータスが0のときのみcommand2を実行する
      • command1 || command2 ... command1の終了ステータスが0以外のときのみcommand2を実行する
      • []に対して[[]]を使う最大のモチベーションは"読みやすさ"
        • [[]]はコマンドではなく構文
        • パターンマッチも使える
    • ループ
      • for (変数) in (単語リスト) do ~ done
        • break、continueが使える
      • case (文字列) in (パターン1) ~ ;; (パターン2) ~ ;; ... esac
        • *)でデフォルト句を表す
      • whileとその逆のuntilもあるよ
  • Chapter07: リダイレクト・パイプ
    • 標準入出力がファイルだからリダイレクト(元|先)つまり標準入出力の切り替え先もファイルだよ
    • (ファイルディスクリプタ番号)> (接続先ファイル)が基本形
    • &(ファイルディスクリプタ番号)で他の接続先を指定できる
      • 標準出力にエラー出力を繋ぐ2>&1は定型文
    • 出力を捨てるときは/dev/nullを接続先に指定
      • 標準出力を捨てるのはifの条件部など
      • 標準エラーを捨てるのはエラーを無視する時
      • 両方捨てる決まり文句は> /dev/null 2>&1
    • >で上書き、>>で追記
    • (コマンド) <<< (終了文字列) ~ (終了文字列)でヒアドクが書ける
      • 終了文字列をクオーティングすることでクオーティングのルールを適用できる
    • パイプラインは一時ファイルが本質
    • {}()で複数コマンドのグループ化ができる
      • 複数コマンドの出力をまとめて出力したいときとか
      • {}は同一プロセス内、()はサブシェル内で実行される→変数を参照する時注意
  • Chapter08: 関数
    • 三通りの書き方の中で(関数名)() { ~ }が一般的
    • ローカルスコープで変数を宣言したいときは必ずlocalを使う
    • 引数の参照は同様に位置パラメータを使う
    • returnで終了ステータスを明示的に返すことができる
  • Chapter09: 組み込みコマンド
    • :
      • 無限ループが作れる
    • printf
      • printf '%s\n' "~"をよく使う
    • command, builtin
      • commandは組み込みコマンドと実行可能ファイル、builtinは組み込みコマンドから探す
    • type
      • コマンドの種別を調べたい時
    • shift
      • 解析済みの位置パラメータを削除するとき
      • オプションの解析とかで意外とよく使う
    • set
      • 目的が複数あるという意味で一番UNIXらしくないコマンド
      • 1)シェルのオプションの表示・変更、2)位置パラメータの設定
    • unset
      • 変数を削除したい時
    • read
      • 標準入力から一行取り出すとき
      • ループのリスト部によく使う例えばwhile IFS= read -r line do ~ done
        • 入力行がlineに代入される、変数名を複数指定したときはIFS区切りでそれぞれ代入される
    • trap
      • 指定したシグナルを補足して指定の処理をする
      • 終了前の後処理を記述したいときとか
    • wait
      • バックグラウンドで実行した処理の終了を待ちたいとき
    • exec
      • 現在のシェルをコマンドのプロセスに差し替えたいとき
      • 現在のシェルのリダイレクト先を変更するときにも使う
    • eval
      • コマンドを動的に組み立てて実行したいとき
      • 実行シェルの環境変数も設定したいスクリプト、具体例はssh-agent
  • Chapter10: 正規表現・文字列操作
    • grep, sedそれからawkも不自由なく使えるようになろうね
    • grepはパターンの検索、sedは置換に使う
  • Chapter11: シェルスクリプトの実行
    • shebangはexecシステムコールの仕様
      • ファイルの先頭2バイトが#!だった時に内容を(バイナリではなく)テキストファイルとして解析する
      • #!以降をコマンドとしてファイル名を引数に実行する
        • → 実行コマンドをスクリプト内に記述できる = 呼び出し側はどのコマンドかを気にせずただ実行するだけでいい
    • sourceは引数ファイルを現在のシェル内で実行する
      • 変数など環境のセットアップを一つのファイルにまとめておける
  • Chapter12: 具体例
  • Chapter13: シェル補完の書き方とか
  • Chapter14: テスト・デバッグ
    • 静的解析
      • noexecオプション ... 構文チェック
      • ShellCheck ... Webサイトおよびコマンドラインツール
    • Batsというツールでテストスクリプトが書ける
    • デバッグ
      • シェルオプション
        • verbose ... 実行するコマンドラインの出力
        • xtrace ... 各種展開の結果を出力
        • nounset ... 未定義変数参照時にエラーとともにexitする
        • errexit ... 終了ステータスが0以外のコマンドを実行した直後にexitする
    • パイプラインで繋いだ後ろはサブシェルなので変数を参照できない点に注意

まとめ

「新しいLinuxの教科書」が読みやすかったので期待しましたが期待通りでした。シェルスクリプトを学ぼうという方には非常におすすめです。文法がわかったのでどんどん書いて身につけていこうというところですが実務ではあんまり使わないのでどうしたものか......。