LoginSignup
10
3

More than 1 year has passed since last update.

問「awk で \ を \\ に置換せよ」⇒答「gsub(/\\/, "\\\\")」・・・はい残念、間違いでーす。答えは「gsub(/\\/, "\\\\\\\\")」 でーす。けどこれも動きませーん。

Last updated at Posted at 2022-02-28

はじめに

困ったもんだよ awk さん

こんなふうになるよ

補足 以下の動作確認環境である Ubuntu では original-awknawk のことです。

$ printf '%s\n' '\' # printf は問題なく \ が出力されていることの確認
\

$ # 以下は全部 \ 4つ

$ printf '%s\n' '\' | gawk '{ gsub(/\\/, "\\\\"); print }'
\\
$ printf '%s\n' '\' | gawk --traditional '{ gsub(/\\/, "\\\\"); print }'
\\
$ printf '%s\n' '\' | gawk --posix '{ gsub(/\\/, "\\\\"); print }'
\

$ printf '%s\n' '\' | original-awk '{ gsub(/\\/, "\\\\"); print }'
\\
$ printf '%s\n' '\' | busybox awk '{ gsub(/\\/, "\\\\"); print }'
\\
$ printf '%s\n' '\' | mawk '{ gsub(/\\/, "\\\\"); print }'
\

はい、mawk は正しく動きません。 mawk と gawk --posix は POSIX の規定通り正しく動いています。

もう一つおまけで gawk の謎挙動互換性を保つための涙ぐましい挙動

$ printf '%s\n' '\' | gawk '{ gsub(/\\/, "\\"); print }' # \ 2個
\
$ printf '%s\n' '\' | gawk '{ gsub(/\\/, "\\\\"); print }' # \ 4個
\\
$ printf '%s\n' '\' | gawk '{ gsub(/\\/, "\\\\\\"); print }' # \ 6個
\\\
$ printf '%s\n' '\' | gawk '{ gsub(/\\/, "\\\\\\\\"); print }' # \ 8個
\\

正解は?

POSIX 的な正解はこちらによるとこれらしいです。ってホントかいな?(まあ自分の記事なんですが・・・)

awk 文法上のエスケープと置換文字列のエスケープで、8 の 半分の半分で 2 なわけですね。

$ printf '%s\n' '\' | gawk --posix '{ gsub(/\\/, "\\\\\\\\"); print }'
\\

でもこうなるのでご注意を

$ # 以下は全部 \ 8つ

$ printf '%s\n' '\' | gawk '{ gsub(/\\/, "\\\\\\\\"); print }'
\\
$ printf '%s\n' '\' | original-awk '{ gsub(/\\/, "\\\\\\\\"); print }'
\\\\
$ printf '%s\n' '\' | busybox awk '{ gsub(/\\/, "\\\\\\\\"); print }'
\\\\
$ printf '%s\n' '\' | mawk '{ gsub(/\\/, "\\\\\\\\"); print }'
\\

どうすりゃいいのさ?

\\\ にするだけならこれが簡単。(これ以外のパターンではどうするのがいいんでしょうかね?・・・)

$ printf '%s\n' '\' | awk '{ gsub(/\\/, "&&"); print }'
\\

& はパターンにマッチした文字(つまり\)に展開される特別な意味を持つ文字です。

さて、では & という文字に置換したい場合はどうすればいいでしょうか?⇒ \& という文字列にします。ただし \ という文字を使うためにはエスケープしなければいけないので・・・

$ printf '%s\n' '\' | awk '{ gsub(/\\/, "\\&"); print }'
&

これが正解となります。gawk では間違えて gsub(/\\/, "\&") と書くと警告してくれるのですが、他の awk では警告なしで & となったり \ (!?) となったりするので、この仕様を知らないと混乱します。このように awk の文法のエスケープとは別に、置換文字列の中でも特殊な文字(&)が使えるという仕様があるために二重にエスケープをしなければならず \ が8 個も必要になるわけですね。

さいごに

ひさびさに昔書いたコードを変更していたらバグってるのを見つけて、自分が前に書いた記事読み返してみて、こんなんわかるかとムカムカしたので新たに記事にしました。

これだから POSIX 標準規格書だけを眺めてたって、POSIX に準拠して書いたって、どこでも動くシェルスクリプトなんか作れやしないって言うんだ。実際の環境で動作確認しないと動くかどうかなんてわからないのが現実だ。

10
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
3