置換ができない/複数ある場合に sed の終了コード0以外にする

Pocket

本記事は、 シェルスクリプト Advent Calendar 2021 の 4日目 の記事だ。
そして、 且つ docker Advent Calendar 2021 4日目 の記事でもある。

どちらのカレンダーもまだまだスッカスカなので、禁じ手で埋めにかかってしまった。


Docker 公式イメージ などをベースにして、カスタムしてイメージをビルドして使おうとした際、 なるべくなら /etc/apt/apt.conf.d/ 等のように、設定用のファイルを追加して、ツール側がいい感じにマージして利用してくれるのが望ましい。
しかし、 場合によってはやむを得ず、既存のファイルを sed コマンドなどで編集せざるを得ないこともあるだろう。

カスタムイメージの Dockerfile をビルドする際に、当初は意図通り書き換えられていても、イメージが更新された結果、イメージのリビルド時にファイルの書き換えが意図しない結果となってしまう場合がある。 1

通常、 sed コマンドは、置換が発生してもしなくても、 終了コード 0 で終了する。
このため、書き換えの成否にかかわらず、 docker build 時にエラーにならないため、コンテナ実行時に初めて置換が意図しない結果だったことに気づくことがある。

そこで、sed コマンドの書き換えで適切なパターンが見つからなかった場合に 0以外の終了コードを返し、ビルド時にエラーとする方法を考える。

以下、 sed は GNU sed を前提とし、 "行頭の foo" を BARfooBAR に置き換える場合の例。

ひとつもヒットしなかったら終了コード16 のエラー

まずは、 書き換えるパターンが見つからなかった場合に、エラーコードを返す方法。

sed -e '/^foo/{s//BAR\0BAR/;h};$!b;p;x;/./Q;Q16'

参考: https://stackoverflow.com/a/15966279

ざっとコマンドの流れを解説すると、以下のようになる。

  1. まず、 正規表現アドレス で置換する行を選択する。
  2. ブロック {} を用いて、正規表現に一致する行について以下を実行する。
    1. s コマンド で、後方参照を使って FOObarBAR に置換する。
      空の正規表現 '//'は最後の正規表現のマッチを繰り返すので、正規表現アドレスでマッチした "行頭の foo" が置き換えられる。
    2. h コマンド で、パターンスペース の内容をホールドスペース (sed 内のクリップボードみたいなもの) にコピーする。
  3. $!b の部分は、最終行でなければ次のサイクルに移動する。 すなわち、以降のコマンドは最終行でのみ実行される。
  4. p コマンド でパターンスペースの内容を出力にプリントする。
  5. x コマンド でパターンスペースの内容とホールドスペースをスワップする。
    パターンスペースの内容は結果的に 、1度でも最初の正規表現がヒットすればその文字列に、 最後まで一度も正規表現がヒットしなければ空っぽになる。
  6. 最後、パターンスペースの内容が空でない (即ち、1度以上正規表現がヒットした) なら、 終了コード 0 で終了し、 そうでなければ 16 で終了する。

ヒットしなければ終了コード16, 2つ以上ヒットしたら終了コード32 のエラー

さらに一歩踏み込んで、 書き換えるパターンが見つからない場合と、 2つ以上見つかってしまった場合両方で、エラーにする方法。

sed -e '/^foo/{s//BAR\0BAR/;x;/./Q32;g};$!b;p;x;/./Q;Q16'

基本的な動きは、ひとつもヒットしなかったパターンと同じだ。
ただ、ブロック {} 内部のコマンドを以下のように変更して、複数ヒットした場合にエラーで終了している。

  1. まず、 正規表現アドレス で置換する行を選択する。
  2. ブロック {} を用いて、正規表現に一致する行について以下を実行する。
    1. s コマンド で、後方参照を使って FOObarBAR に置換する。
    2. x コマンド でパターンスペースの内容とホールドスペースをスワップする。
      パターンスペースの内容は結果的に 、以前の行でも正規表現がヒットすればその文字列に、 最後まで一度も正規表現がヒットしなければ空っぽになる。
    3. パターンスペースの内容が空でない (即ち、正規表現のヒットが2回目) なら、 終了コード 32 でエラー終了し、 そうでなければそのまま次へ。
    4. g コマンド でホールドスペースの内容をパターンスペースにコピーして戻す
  3. 以降は「ひとつもヒットしなかったら…」と同じ

なお、このコマンドは、 2つヒットした時点で出力が止まる。

おわりに

モダンなプログラミング言語が軒並み型推論でコンパイル時にエラーとするように、 Dockerfile もビルド時にエラーにしてしまおう。


  1. カスタムイメージの Dockerfile ベースイメージを選択する際、基本的にはタグである程度絞っておくべきだが、それはさておき。 

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください