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