問題(正解率25%……独自調査)
シェル変数$s
に格納された文字列をAWKに渡し、文字列をそのまま表示すると共に、文字数を表示するコードを書いてください。ただし、 $s
は英数のみならず記号類が入っている可能性があります。 また、標準入力は使ってはいけないものとします(他のデータが受け取れなくなってしまうため)。
よくある誤解答
次の回答はどちらも不正解です。
その1(AWKソースコードに直接埋め込む)
誤解答1
awk 'BEGIN{s="'"$s"'"; print s; print length(s);}'
その2(AWK -vオプションを使う)
誤解答2
awk -v s="$s" 'BEGIN{print s; print length(s);}'
反例
「うそだー」と思うなら、$s
にバックスラッシュを含む文字列をセットして実行してみてください。
反例
$ s='\\'
$ echo "$s"
\\ # ← $sには、確かにバックスラッシュ2個が入っている
$ awk 'BEGIN{s="'"$s"'"; print s; print length(s);}'
\
1 # ← あれ?
$ awk -v s="$s" 'BEGIN{print s; print length(s);}'
\
1 # ← あれあれ?
解説
誤解答1,2が含む問題
AWKは、文字列を取り込む時に一部の記号をエスケープ処理してしまい、文字列が変わることがあります。(\
はその記号の一つ)
それだけではありません。記号類が含まれていると、それが取り込み対象の文字列の一部なのか、それとも取り込むために記述している構文で出てくる記号なのかの判断を誤る場合があります。例えば、誤解答1ではダブルクォーテーション"
が含まれていると失敗しますし、誤解答2では先頭にイコール=
が含まれていると失敗します。
じゃあどうすればいいか
まずは、正解の一例をご覧ください。
正解
octed_s=$(printf '%s' "$s" |
od -v -A n -t o1 |
tr -d '\n' |
sed 's/[[:blank:]]*$//' |
sed 's/[[:blank:]]\{1,\}/\\/g')
awk 'BEGIN{s=sprintf("'$octed_s'");print s; print length(s);}'
AWKのエスケープや構文記号の影響を受けないように、printf用のフォーマット文字列形式にエンコードして渡し、それをAWK内のsprintf関数でデコードして使えば無事に渡すことができます。
まぁ、特殊な記号類が含まれないと始めからわかっているなら、こんな細工をしなくていいんですけどね。