本記事は、 シェルスクリプト 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)