sed の ブロック {} 内で i, r, e コマンドを使うと “unmatched `{‘” とエラーになる

Pocket

本記事は、 シェルスクリプト Advent Calendar 2021 の 3日目 の記事だ。
3日目が終わりそうになっても誰も書きそうにないので、最近 sed コマンドで ブロック {} を使っていたら、 "unmatched `{" というエラーにハマったので、そのメモ。


target.txt:

foo
bar
foo
bar
foo

insert.txt:

***

上記のような、2つのファイルがあったとする。

target.txt ファイルに対して、 正規表現アドレスbar から始まる行を選択し、 その後ろに r コマンド insert.txt のファイルの中身を挿入する。

するとこんな結果になる。

$ sed -e '/^bar/rinsert.txt' target.txt
foo
bar
***
foo
bar
***
foo

では、アドレス指定の後ろにブロック {} を追加し、以下のように bar が2回以上ヒットしたらエラーコード出して終了するようにしてみる。

$ sed -e '/^bar/{rinsert.txt;x;/./Q129;g}' target.txt
sed: -e expression #1, char 0: unmatched `{'
$ echo $?
1

はい、別のエラーで失敗した。
ちゃんと {} の数はマッチしているのに……

これは、 i, r, e などのコマンドは、 コマンドの終了区切りに改行が必須となっていて、 セミコロン (;) などで区切ろうとしても、 その文字もコマンドのオプションとして渡されてしまうためだ。 1

これを回避する場合は、 コマンドの後ろに改行を入れるか、 -e を使ってコマンド区切る必要がある。

$ sed -e '/^bar/{rinsert.txt' -e 'x;/./Q129;g}' target.txt
foo
***
bar
foo
***
$ echo $?
129
$ sed -e '/^bar/{rinsert.txt' -e 'x;/./Q129;g}' <<EOF
> foo
> bar
> foo
> foo
> EOF
foo
bar
***
foo
foo
$ echo $?
0

様々な区切り文字でつかわれるので忘れがちだけど、セミコロンをファイル名にすることだって、できるもんな。
そう考えれば納得。

ちなみに、同様の振る舞いをするコマンド ("Commands Requiring a newline") は、以下の通り。

  • a,c,i (append/change/insert)
  • # (comment)
  • r,R,w,W (reading and writing files)
  • e (command execution)
  • s///[we] (substitute with e or w flags)

コメントを残す

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

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