awkの数値コンテキストと文字列コンテキスト

awk の変数は数値なのか文字列なのかがよく分からず、そのあたりの挙動が気になったので調べてみた。
以下はgawk 4.0.2 で検証している。

# 数値と文字列の切り替え
以下の例では、数値の2と3が連結されて"23"という文字列となり、それに+4する際に文字列"23"が23という数値とみなされ、最終的に27と出力されている。

```bash
$ echo | awk '{print (2 3) + 4}'
27
```
このように、数値同士でも連結すると文字列になり、文字列は演算すると数値になる。

# 数値としての文字列の評価
文字列が数値として評価されるとき、

* 基本的には数字ではない最初の文字以降が無視された値とみなされる。
* "" (空文字列)は0とみなされる。
* よって、基本的には数字以外の文字で始まっている文字列は0とみなされる。
* ただし、空白文字で始まっている場合は、空白部分は飛ばして評価される。
* "+"と"-"については、先頭にある場合は符号とみなしてくれる。先頭にない場合、演算子としてはみなされない。
* 小数点は認識される
* eによる指数表記も認識される
* 先頭に"0"や"0x"がついている文字列でも、8進数や16進数の数とはみなされない。

```bash
$ echo | awk '{print "11k3" + 0}' # "11k3" は("k"までの数値なので)11とみなされる
11
$ echo | awk '{print "" + 0}'     # "" (空文字列)は0とみなされる
0
$ echo | awk '{print "k" + 0}'    # 数字以外の文字で始まっている文字列は基本的に0
0
$ echo | awk '{print "k3" + 0}'   # 数字以外の文字で始まっている文字列は基本的に0
0
$ echo | awk '{print "  3" + 0}'  # 空白から始まっている場合は空白は飛ばして評価される
3
$ echo | awk '{print "+1" + 0}'   # "+"と"-"については、先頭にある場合は符号とみなしてくれる。
1
$ echo | awk '{print "-1" + 0}'   # "+"と"-"については、先頭にある場合は符号とみなしてくれる。
-1
$ echo | awk '{print "1+3" + 0}'  # "+"は演算子としてはみなされないので、4ではなく1に。
1
$ echo | awk '{print "2.2" + 0}'  # 小数点は認識される
2.2
$ echo | awk '{print "1e3" + 0}'  # eによる指数表記も認識される
1000
$ echo | awk '{print "011" + 0}'  # 先頭に"0"がついている文字列でも、8進数の数とはみなされない。
11
$ echo | awk '{print "0x11" + 0}' # 先頭に"0x"がついている文字列でも、16進数の数とはみなされない。
0
```

# 文字列としての数値の評価

数値が文字列として評価されるとき、10進数の値に直してそれが文字列に変換されたものとみなされる。

```bash
$ echo | awk '{print 1e3 ""}'     # "1e3"とはならない
1000
$ echo | awk '{print 0x11 ""}'    # "0x11"とはならない
17
$ echo | awk '{print 1e3 0x11}'   # "1e30x11"とはならない
100017
$ echo | awk '{print 0.0 ""}'     # "0.0"とはならない
0
```

# 比較時の挙動

`==` で比較する場合、

* 文字列同士を比較する際は文字列として比較される
* 数値同士を比較する際は数値として比較される

```bash
$ echo | awk '{print ("0.0" == "0" ? "true" : "false")}'    # 文字列として異なるので偽
false
$ echo | awk '{print ("abc" == "abcd" ? "true" : "false")}' # 文字列として異なるので偽
false
$ echo | awk '{print (0.0 == 0 ? "true" : "false")}'        # 数値として等しいので真
true
```

ここまでは当然と思われる。ただ、文字列と数値を比較する場合、

* **数値の方が文字列に直されて比較される** 

```bash
$ echo | awk '{print ("0.0" == 0 ? "true" : "false")}'      # 文字列として"0.0"と"0"が比較され偽
false
$ echo | awk '{print ("1000" == 1e3 ? "true" : "false")}'   # 文字列として"1000"と"1000"が比較され真
true
```

(これはあまり知られていないのではないか) 

# 真偽判定時の挙動

数値は0なら偽、非0なら真と判定される (これはたぶんみんな知っている)

```bash
$ echo | awk '{print (0 ? "true" : "false")}'
false
$ echo | awk '{print (1 ? "true" : "false")}'
true
$ echo | awk '{print (0.0 ? "true" : "false")}'
false
```

文字列は空文字なら偽、空文字でなければ真と判定される (これはあまり知られていないのではないか。)  

```bash
$ echo | awk '{print ("" ? "true" : "false")}'
false
$ echo | awk '{print (" " ? "true" : "false")}'
true
$ echo | awk '{print ("0" ? "true" : "false")}'
true
```

# 変数が文字列になるか数値になるか

明示的に文字列を変数に代入した場合、それが数値として解釈できる文字列でも、文字列として扱われる。

```bash
# 以下でsは文字列として扱われており、空白文字ではないのでtrueとなる
$ echo  | awk '{s="0"; print (s ? "true" : "false")}'   
true
```

では入力時の`$0`や`$1`などの変数はどうだろうか？
これは以下のように、"数値とみなせる文字列"の後ろに余計な文字列がついていたら文字列、そうでなければ数値となるようだ。

```bash
$ echo 0 | awk '{print ($0 ? "true" : "false")}'    # $0 は数値の0
false
$ echo 0.0 | awk '{print ($0 ? "true" : "false")}'  # $0 は数値の0
false
$ echo +0 | awk '{print ($0 ? "true" : "false")}'   # $0 は数値の0
false
$ echo 0e3 | awk '{print ($0 ? "true" : "false")}'  # $0 は数値の0
false
$ echo 0k | awk '{print ($0 ? "true" : "false")}'   # $0 は文字列の"0k"
true
```

変数の値が0であるとき、それ単独で条件判定文に使ってしまうと、
それが数値の0なのか、文字列の"0"なのかで結果が変わってくるので要注意である。
例えば空行以外を出力する処理として`awk '$0'` という処理を見かけたことがあるが、
これは0や0.0などと書かれてある行も空行と見なされ出力されない。

# 参考
* https://www.gnu.org/software/gawk/manual/html_node/Strings-And-Numbers.html
* https://www.gnu.org/software/gawk/manual/html_node/Truth-Values.html