sed の落とし穴・・・sed の s/// は何もしない? 実はパターンスペースを破壊する

目次

sed のデバッグをしている時、落とし穴に遭遇したので備忘録として残す。

例えば、以下のスクリプトは、行頭に複数の「:」があれば一個だけに置換し、「チェック」を「判定」に置換することを想定したもの。

1:top
2s/^:*/:/
3s/チェック/判定/
4t top

実はこのスクリプトはバグが潜んでおり、そのまま実行すると無限ループとなり、いつまで経っても処理が終わらずプロンプトが表示されない。 (バグが潜んでいるが、当記事の本題ではないので、ひとまず脇に置いておく)

1$ echo '::: t コマンドは、直前の s コマンドの成否だけをチェックしているわけではない' | sed '
2> :top
3> s/^:*/:/
4> s/チェック/判定/
5> t top'
6^C   ← 終わらないので Ctlr + C で強制終了した
7$

当初の私の想定では、t コマンドは直前の s コマンドの成否に応じて分岐するものと思い込んでいたので、1回目の s/チェック/判定/ は成功して :top にジャンプするが、2回目のs/チェック/判定/ はすでに置換対象がないので失敗し、ジャンプは発生せず、スクリプト処理が終了するものと思っていた。

そこで、t コマンドの直前に、必ず置換が失敗すると見込んだコマンド s/// を追加して、ジャンプしないことを確認しようとしたのだった。

1$ echo '::: t コマンドは、直前の s コマンドの成否だけをチェックしているわけではない' | sed '
2:top
3s/^:*/:/
4s/良い/悪い/
5s///
6t top'
7^C   ← 終わらないので Ctlr + C で強制終了した
8$

ところが、予想に反してこちらも無限ループ状態となり、処理が終わらなかった。

基本に立ち返り、sed のマニュアルを精読したところ、以下の記述を見つけた。

https://www.gnu.org/software/sed/manual/sed.html#The-_0022s_0022-Command
4.3 selecting lines by text matching

/regexp/

This will select any line which matches the regular expression regexp.
(中略)
The empty regular expression ‘//’ repeats the last regular expression match (the same holds if the empty regular expression is passed to the s command).

🔶 翻訳結果

/regexp/

これは、正規表現 regexp にマッチするすべての行を選択する。
(中略)
空の正規表現 // は、直前に使用した正規表現によるマッチを繰り返す(この挙動は、s コマンドに空の正規表現を与えた場合も同様である)。

つまり、s/// は「直前に使われた正規表現を、空文字列に置換することになる。
s///s/<直前の正規表現>// と同じだ。
だから、直前の正規表現にマッチすることがあればその部分が削除される、すなわち、置換が成功したことになるのだ。


例.

1$ echo 'abc123DE45f6' | sed 's/[0-9][0-9]*//; s///'
2abcDEf6
  1. s/[0-9][0-9]*//

    • 123 を削除
    • パターンスペース:abcDE45f6
  2. s///

    • 直前の正規表現 [0-9][0-9]* を再利用 → 45 を削除
    • パターンスペース:abcDEf6

可読性が極端に下がるため、 意図的でない限り s/// は使わない方が安全だ


最後に、元のスクリプトのバグを修正してこの記事を締める。「:」の置換コマンドをループの直前に移動するだけだ。

1s/^:*/:/
2:top
3s/チェック/判定/
4t top

元々、s/^:*/:/ がループ内(:topt top の間)に存在していたわけだが、それだと常に置換が成功するため、t コマンドで置換成功と判定されて無限ループに陥っていたのだ。

実行結果は以下。

1$ echo '::: t コマンドは、直前の s コマンドの成否だけをチェックしているわけではない' | sed '
2> s/^:*/:/
3> :top
4> s/チェック/判定/
5> t top'
6: t コマンドは、直前の s コマンドの成否だけを判定しているわけではない
7$

関連ページ