awkで改行有り無しのshell変数を受け取る時のシングルクォーテーション、ダブルクォーテーションの振る舞いを調べてみた

# 目的
- cgiでawkを多用しています。
- shell-script内で定義した変数をawkに渡して処理させることが多いのですが、改行有り無しのshell変数を受け取る時にクォーテーション、ダブルクォーテーションをどう使うのが正しいのかよく理解していなかったので調べてみました。

# 環境
- awk : GNU Awk 5.1.0, API: 3.0 (GNU MPFR 4.1.0, GNU MP 6.2.1)
- bash : GNU bash, バージョン 5.1.0(1)-release (x86_64-apple-darwin18.7.0)

# 参考文献
[gawk で文字列 "01" の比較でハマった](https://luna2-linux.blogspot.com/2016/01/gawk-01.html)

# 調査方法
- UUIDの様に長い英数字混じり文字列を受け渡しします。
- awkの`print()`と`printf()`で比較します。

```bash
$ export HOGE=$(uuidgen | tr -d '-')
$ echo $HOGE
18928D9153E44233AD940F19B2BA765C

# 次の例は数値として取り扱おうとします。
$ echo "" | awk '{ print '$HOGE' }'
18928

# 次の例も数値として扱おうとします。ダブルクォーテーションはshell側の処理になります。
$ echo "" | awk '{ print '"$HOGE"' }'
18928

# 次の例のダブルクォーテーションはawkの処理になり、期待通り文字列として扱う処理です。
$ echo "" | awk '{ print "'$HOGE'" }'
18928D9153E44233AD940F19B2BA765C

# 次の例も期待通りです。
$ echo "" | awk '{ print "'"$HOGE"'" }'
18928D9153E44233AD940F19B2BA765C 
```

つまりawkの`'{}'`の中でshell変数を扱うために`'$HOGE'`とシングルクォーテーションで囲えば読み込めますが、ダブルクォーテーションで文字列とする指示が必要です。

これは`printf(%s)`でも同じでした。

```bash
$ echo "" | awk '{ printf "%s\n", '$HOGE' }'
18928
$ echo "" | awk '{ printf "%s\n", '"$HOGE"' }'
18928
$ echo "" | awk '{ printf "%s\n", "'$HOGE'" }'
18928D9153E44233AD940F19B2BA765C
$ echo "" | awk '{ printf "%s\n", "'"$HOGE"'" }'
18928D9153E44233AD940F19B2BA765C
```

#### まとめ
awk内で改行を含まない数字で始まりの英数混じり文字列のshell変数を受け取るには`"'"$HOGE"'"`か`"'$HOGE'"`で受け取ります。

# shell変数に改行を含む場合のawkの振る舞い
- 次にshell変数に改行を含む場合はクォーテーションの有無で変わります。

```bash
$ HOGE2=$(cat << EOF
$( uuidgen |　tr -d "-")
$( uuidgen | tr -d "-")
EOF
)

# ダブルクォーテーションなしだと文字列のスペース区切りになります。
$ echo $HOGE2
8B18647B82634FB38B47000E4CA82BF8 7C8497FA8E384CDF9575CBFC1E3354AD

# ダブルクォーテーションだと文字列の改行区切りになります
$ echo "$HOGE2"
8B18647B82634FB38B47000E4CA82BF8
7C8497FA8E384CDF9575CBFC1E3354AD

# 念の為ですが、シングルクォーテーションは変数展開させない指示です。
$ echo '$HOGE2'
$HOGE2
```

さて、この改行を持つshell変数はawk内ではどう受けたら良いでしょうか。

```bash
# まずechoの出力（bashの変数展開）を試します。
$ echo $HOGE2 | awk '{ print $0}'
8B18647B82634FB38B47000E4CA82BF8 7C8497FA8E384CDF9575CBFC1E3354AD
# 出力は改行なしです

# ダブルクォーテーションで囲むとechoは改行を出力します。
$ echo "$HOGE2" | awk '{ print $0}'
8B18647B82634FB38B47000E4CA82BF8
7C8497FA8E384CDF9575CBFC1E3354AD

# もちろんawkは$1,$2を区別します。
$ echo $HOGE2 | awk '{ print $1 }'
8B18647B82634FB38B47000E4CA82BF8
$ echo $HOGE2 | awk '{ print $2 }'
7C8497FA8E384CDF9575CBFC1E3354AD

# 改行ありで受け取るとawkは一つ目を普通に出します。
$ echo "$HOGE2" | awk '{ print $1 }'
8B18647B82634FB38B47000E4CA82BF8
7C8497FA8E384CDF9575CBFC1E3354AD

# 当然awkは２つ目の項目は出しません。
$ echo "$HOGE2" | awk '{ print $2 }'
# これでechoの動きに応じたawkの通常の振る舞いです。
```

ではawkで改行付きのshell変数を受け取る実験をします。
まず`awk -v`で試します。


```bash
# 次の例はshell変数をダブルクォーテーションで囲います。
$ echo "" | awk -v HOGE2="$HOGE2" '{ print HOGE2 }'
8B18647B82634FB38B47000E4CA82BF8
7C8497FA8E384CDF9575CBFC1E3354AD
# これは行ありの変数をそのまま出力しました。

# shell変数をシングルクォーテーションとダブルクォーテーションで囲います。
$ echo "" | awk -v HOGE="'$HOGE2'" '{print HOGE}'
'8B18647B82634FB38B47000E4CA82BF8
7C8497FA8E384CDF9575CBFC1E3354AD'

# なんとシングルクォーテーションつきで返しました。知らんかった。

# ついでに
$ echo "" | awk -v HOGE="'"$HOGE2"'" '{print HOGE}'
awk: コマンドライン:1: 7C8497FA8E384CDF9575CBFC1E3354AD'
awk: コマンドライン:1:                                 ^ 式内に無効な文字 ''' があります
awk: コマンドライン:1: 7C8497FA8E384CDF9575CBFC1E3354AD'
awk: コマンドライン:1:                                 ^ syntax error
# エラーで終わりました。

# 今度はshell変数をクォーテーションなしで渡します。
$ echo "" | awk -v HOGE2=$HOGE2 '{ print HOGE2 }'
awk: 致命的: cannot open file `{ print HOGE2 }' for reading: No such file or directory

# これはなんと `{ print HOGE2 }' をファイルと思った様です。これは、shell変数がスペース付きで展開されたので、スペースがそのままawkに渡されたためでしょう。例えば次の様なワンライナーがエラーになるのと同じ意味でしょう。

$ echo "" | awk -v HOGE2=8B18647B82634FB38B47000E4CA82BF8 7C8497FA8E384CDF9575CBFC1E3354AD '{ print }'
awk: 致命的: cannot open file `{ print }' for reading: No such file or directory
# というわけでNGでした。

# これならどうだ。
echo "" | awk -v A="a
> b" '{ print A}'
a
b
# とうまくいくのです。
```
というわけで`awk -v`オプションで渡す場合はshell変数に改行がある場合は`"$HOGE2"`の様にすれば受け取ってもらえます。bashが`-`のオプション指定の場合に変数変換しないのかawkの文法なのかちょっとわかりません。でもこう動く様です。

ということはスペースをなんとかすれば良いので無理やりこんなこともできます。

```bash
# スペースをエスケープします
$ echo "" | awk -v HOGE2="$( echo $HOGE2 | sed -e 's/ /\ /g' )" '{ print HOGE2 }'
8B18647B82634FB38B47000E4CA82BF8 7C8497FA8E384CDF9575CBFC1E3354AD
```
できるはできるけど可読性が著しく落ちますね。

さて、改行付きのshell変数はawk内で受け取れるでしょうか。

```bash
# ダブルクォーテーションなしで取り込みます。
$ echo "" | awk '{ print '$HOGE2' }'
awk: コマンドライン:1: { print 8B18647B82634FB38B47000E4CA82BF8
awk: コマンドライン:1:                                         ^ 予期しない改行または文字列終端です

# おやおやエラーを出しました。これはprintが行末なのに } が無いとエラーになったのでしょう。

# shell変数をダブルクォーテーションで囲みます。
$ echo "" | awk '{ print '"$HOGE2"' }'
8
# こちらは頑張って最初の文字を数字として解釈したのでしょう。エラーを調べると、

$ echo $?
0
# とエラーを出していません。

# さてawk内で文字列として取り込みます。
$ echo "" | awk '{ print "'$HOGE2'" }'
awk: コマンドライン:1: { print "8B18647B82634FB38B47000E4CA82BF8
awk: コマンドライン:1:         ^ 文字列が終端されていません
awk: コマンドライン:1: { print "8B18647B82634FB38B47000E4CA82BF8
awk: コマンドライン:1:         ^ syntax error

# 行が終端されていないのでエラーとなりました。

$ echo $?
1

# エラーでした。

# shellもawkもダブルクォーテーションで囲みます。
$ echo "" | awk '{ print "'"$HOGE2"'" }'
awk: コマンドライン:1: { print "8B18647B82634FB38B47000E4CA82BF8
awk: コマンドライン:1:         ^ 文字列が終端されていません
awk: コマンドライン:1: { print "8B18647B82634FB38B47000E4CA82BF8
awk: コマンドライン:1:         ^ syntax error

# こちらも終端なしでエラーです。
```

ということで改行ありの変数をawk内で受け取れないです。変態的に受け取ることは受け取れます。

```bash
$ echo "" | awk '{print "'$( echo $HOGE2 | sed -e 's/ /-/g' )'"}'
8B18647B82634FB38B47000E4CA82BF8-7C8497FA8E384CDF9575CBFC1E3354AD

# あるいは
$ echo "" | awk '{print "'$( echo "$HOGE2" | sed -z 's/\n/_/' )'"}'
8B18647B82634FB38B47000E4CA82BF8_7C8497FA8E384CDF9575CBFC1E3354AD
```
（`-`とか`_`とかをどうしても` `にできなかった。シェル芸の闇にはまってきた...。誰か教えてもらえませんか？）


# 結論
shell変数に改行がないのであれば
`awk '{print "'$HOGE2'"}'`
あるいは
`awk '{print "'"$HOGE2"'"}'`
とすれば受け取れます。

shell変数に改行があれば
`awk -v HOGE="$HOGE" '{print HOGE}'`
とすると改行展開された文字列として受け取ります。
また、
`awk -v HOGE="'$HOGE'" '{print HOGE}'`
とすれば改行付きの文字列も`''`で囲まれた一つの変数として受け取れます。

実験はこのバージョンではこうでしたということですので、他の環境ではそうならないかもしれません。ご注意あれ。

どなたかの役に立てれば嬉しいです。
（-vの後のクォーテーションの扱いは謎だー。誰かコメントいただけると嬉しいです。）