Linux: bashシェルにおいて数値計算を行ういくつかの方法


ちょっとLinuxで数値計算を行う必要があったので、調べた結果を簡単にまとめました。

###### 実施環境：

```Linux:Linux
[testuser@testhost ~]$ uname -a
Linux testhost 4.18.0-147.8.1.el8_1.x86_64 #1 SMP Thu Apr 9 13:49:54 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
[testuser@testhost ~]$ echo $SHELL
/bin/bash
```

以下、今回調べた方法と主な活用場面です。

|番号|方法|活用場面|
|:-|:-|:-|
|1|letコマンド|簡単な計算をして結果を変数に代入したい時|
|2|((変数=計算式))|簡単な計算をして結果を変数に代入したい時|
|3|$((計算式))|簡単な計算をして結果をそのまま使用したい時|
|4|exprコマンド|シェルに左右されず簡単な計算をしたい時|
|5|bcコマンド|複雑な計算をしたい時|
|6|awkコマンド|表形式の数値を計算したい時|

# 1. letコマンド

bashのコマンドの1つであるletコマンドを使用する方法です。
計算式は以下のように記載します。
計算式中や=の前後にスペースを入れてはいけません。

```Linux:Linux
let 変数=計算式
```

計算結果は指定した変数に代入される形で取得されます。
計算に使用できる数値は整数だけで、小数は使用できません。
以下、実行例です。

```Linux:Linux
[testuser@testhost ~]$ NUM=
[testuser@testhost ~]$ echo $NUM

[testuser@testhost ~]$ let NUM=1+1
[testuser@testhost ~]$ echo $NUM
2
[testuser@testhost ~]$ let NUM = 1 + 1
bash: let: =: syntax error: operand expected (error token is "=")
[testuser@testhost ~]$ let NUM=7-5
[testuser@testhost ~]$ echo $NUM
2
[testuser@testhost ~]$ let NUM=3*8
[testuser@testhost ~]$ echo $NUM
24
[testuser@testhost ~]$ let NUM=20/3
[testuser@testhost ~]$ echo $NUM
6
[testuser@testhost ~]$ let NUM=20%3
[testuser@testhost ~]$ echo $NUM
2
[testuser@testhost ~]$ let NUM=1+2*3
[testuser@testhost ~]$ echo $NUM
7
[testuser@testhost ~]$ let NUM=(1+2)*3
[testuser@testhost ~]$ echo $NUM
9
[testuser@testhost ~]$ let NUM=3+-5
[testuser@testhost ~]$ echo $NUM
-2
[testuser@testhost ~]$ let NUM=1.1+0.1
bash: let: NUM=1.1+0.1: syntax error: invalid arithmetic operator (error token is ".1+0.1")
[testuser@testhost ~]$ NUM1=15
[testuser@testhost ~]$ NUM2=3
[testuser@testhost ~]$ echo $NUM1
15
[testuser@testhost ~]$ echo $NUM2
3
[testuser@testhost ~]$ let NUM=NUM1/NUM2
[testuser@testhost ~]$ echo $NUM
5
[testuser@testhost ~]$ CALC="3-2"
[testuser@testhost ~]$ echo $CALC
3-2
[testuser@testhost ~]$ let NUM=CALC
[testuser@testhost ~]$ echo $NUM
1
```

あまり複雑な計算はできませんが、簡単な計算なら問題ありません。
ただ次の2の方法のほうが記載が簡潔なので、こちらの方法はあまり使用しないかもしれません。

# 2. ((変数=計算式))

1と同じく、bash内の機能を使用する方法です。
計算式は以下のように記載します。
計算式中や=の前後についてスペースの有無は問いません。

```Linux:Linux
((変数 = 計算式))
```

基本的にはletコマンドとほとんど同じです。
以下、実行例です。

```Linux:Linux
[testuser@testhost ~]$ NUM=
[testuser@testhost ~]$ echo $NUM

[testuser@testhost ~]$ ((NUM = 1 + 1))
[testuser@testhost ~]$ echo $NUM
2
[testuser@testhost ~]$ ((NUM=1+1))
[testuser@testhost ~]$ echo $NUM
2
[testuser@testhost ~]$ ((NUM = 7 - 5))
[testuser@testhost ~]$ echo $NUM
2
[testuser@testhost ~]$ ((NUM = 3 * 8))
[testuser@testhost ~]$ echo $NUM
24
[testuser@testhost ~]$ ((NUM = 20 / 3))
[testuser@testhost ~]$ echo $NUM
6
[testuser@testhost ~]$ ((NUM = 20 % 3))
[testuser@testhost ~]$ echo $NUM
2
[testuser@testhost ~]$ ((NUM = 1 + 2 * 3))
[testuser@testhost ~]$ echo $NUM
7
[testuser@testhost ~]$ ((NUM = ( 1 + 2 ) * 3))
[testuser@testhost ~]$ echo $NUM
9
[testuser@testhost ~]$ ((NUM = 3 + -5))
[testuser@testhost ~]$ echo $NUM
-2
[testuser@testhost ~]$ ((NUM = 1.1 + 0.1))
bash: ((: NUM = 1.1 + 0.1: syntax error: invalid arithmetic operator (error token is ".1 + 0.1")
[testuser@testhost ~]$ NUM1=15
[testuser@testhost ~]$ NUM2=3
[testuser@testhost ~]$ echo $NUM1
15
[testuser@testhost ~]$ echo $NUM2
3
[testuser@testhost ~]$ ((NUM = NUM1 / NUM2))
[testuser@testhost ~]$ echo $NUM
5
[testuser@testhost ~]$ CALC="3 - 2"
[testuser@testhost ~]$ echo $CALC
3 - 2
[testuser@testhost ~]$ ((NUM = CALC))
[testuser@testhost ~]$ echo $NUM
1
```

こちらも1の方法と同様複雑な計算はできませんが、
簡単な計算なら問題なく実行できます。

# 3. $((計算式))

1や2と同じく、bash内の機能を使用する方法です。
計算式は以下のように記載します。
計算式中についてスペースの有無は問いません。

```Linux:Linux
$((計算式))
```

基本的な点は前の2つの方法とほとんど同じですが、計算結果を変数を介さず取得できます。
以下、実行例です。
計算結果はechoコマンドで標準出力に出力しています。

```Linux:Linux
[testuser@testhost ~]$ echo $((1 + 1))
2
[testuser@testhost ~]$ echo $((1+1))
2
[testuser@testhost ~]$ echo $((7 - 5))
2
[testuser@testhost ~]$ echo $((3 * 8))
24
[testuser@testhost ~]$ echo $((20 / 3))
6
[testuser@testhost ~]$ echo $((20 % 3))
2
[testuser@testhost ~]$ echo $((1 + 2 * 3))
7
[testuser@testhost ~]$ echo $((( 1 + 2 ) * 3))
9
[testuser@testhost ~]$ echo $((3 + -5))
-2
[testuser@testhost ~]$ echo $((1.1 + 0.1))
bash: 1.1 + 0.1: syntax error: invalid arithmetic operator (error token is ".1 + 0.1")
[testuser@testhost ~]$ NUM1=15
[testuser@testhost ~]$ NUM2=3
[testuser@testhost ~]$ echo $NUM1
15
[testuser@testhost ~]$ echo $NUM2
3
[testuser@testhost ~]$ echo $((NUM1 / NUM2))
5
[testuser@testhost ~]$ CALC="3 - 2"
[testuser@testhost ~]$ echo $CALC
3 - 2
[testuser@testhost ~]$ echo $((CALC))
1
```

この方法も1や2と同様複雑な計算はできませんが、変数を介さず直接結果を取得できるため便利です。
また、上記の例ではechoコマンドを使用していますが、以下のように他にも様々なコマンドで使用できます。

```Linux:Linux
[testuser@testhost ~]$ ls test$((3 - 2)).log
test1.log
```

# 4. exprコマンド

exprコマンドを使用する方法です。
このコマンドはbashの組み込みコマンドではありません。
計算式は以下のように記載します。
計算式は数値や演算子をスペースで区切る必要があります。

```Linux:Linux
expr 計算式
```

計算結果は標準出力に出力されます。
基本的には1〜3の方法とあまり変わりませんが、*など一部の演算子はバックスラッシュ(\\)などでエスケープする必要があります。
以下、実行例です。

```Linux:Linux
[testuser@testhost ~]$ expr 1 + 1
2
[testuser@testhost ~]$ expr 1+1
1+1
[testuser@testhost ~]$ expr 7 - 5
2
[testuser@testhost ~]$ expr 3 \* 8
24
[testuser@testhost ~]$ expr 20 / 3
6
[testuser@testhost ~]$ expr 20 % 3
2
[testuser@testhost ~]$ expr 1 + 2 \* 3
7
[testuser@testhost ~]$ expr \( 1 + 2 \) \* 3
9
[testuser@testhost ~]$ expr 3 + -5
-2
[testuser@testhost ~]$ expr 1.1 + 0.1
expr: non-integer argument
[testuser@testhost ~]$ NUM1=15
[testuser@testhost ~]$ NUM2=3
[testuser@testhost ~]$ echo $NUM1
15
[testuser@testhost ~]$ echo $NUM2
3
[testuser@testhost ~]$ expr $NUM1 / $NUM2
5
[testuser@testhost ~]$ CALC="3 - 2"
[testuser@testhost ~]$ echo $CALC
3 - 2
[testuser@testhost ~]$ expr $CALC
1
```

複雑な計算はできず、一部演算子にエスケープが必要で、何より処理速度が1〜3の方法に比べ圧倒的に遅いため、正直あまり使い勝手のよい方法ではありません。
ただ、exprコマンドはbashの組み込みコマンドではないので、bash以外でも使用できるという利点があります。
ちなみに、本題からは外れますが、exprは計算をするコマンドというより「式を評価する」コマンドなので、実は数値計算以外でも使用できたりします。
以下では文字列"linux"の文字数を取得しています。

```Linux:Linux
[testuser@testhost ~]$ expr length linux
5
```

# 5. bcコマンド

bcコマンドを使用する方法です。
このコマンドもbashの組み込みコマンドではありません。
計算式は標準入力から取得します。
計算式中についてスペースの有無は問いません。
以下は、echoコマンドの標準出力をパイプして計算式を取得する方法です。

```Linux:Linux
echo 計算式 | bc
```

計算結果は標準出力に出力されます。
整数だけでなく小数も使用可能であり、オプションの指定によっては三角関数などの複雑な計算も可能です。
ただし、計算式の取得方法によっては、*など一部の演算子はバックスラッシュ(\\)などでエスケープする必要があります。
以下、実行例です。

```Linux:Linux
[testuser@testhost ~]$ echo 1 + 1 | bc
2
[testuser@testhost ~]$ echo 1+1 | bc
2
[testuser@testhost ~]$ echo 7 - 5 | bc
2
[testuser@testhost ~]$ echo 3 \* 8 | bc
24
[testuser@testhost ~]$ echo 20 / 3 | bc
6
[testuser@testhost ~]$ echo 20 % 3 | bc
2
[testuser@testhost ~]$ echo 1 + 2 \* 3 | bc
7
[testuser@testhost ~]$ echo \( 1 + 2 \) \* 3 | bc
9
[testuser@testhost ~]$ echo 3 + -5 | bc
-2
[testuser@testhost ~]$ echo 1.1 + 0.1 | bc
1.2
[testuser@testhost ~]$ NUM1=15
[testuser@testhost ~]$ NUM2=3
[testuser@testhost ~]$ echo $NUM1
15
[testuser@testhost ~]$ echo $NUM2
3
[testuser@testhost ~]$ echo $NUM1 / $NUM2 | bc
5
[testuser@testhost ~]$ CALC="3 - 2"
[testuser@testhost ~]$ echo $CALC
3 - 2
[testuser@testhost ~]$ echo $CALC | bc
1
```

計算式は標準入力から取得できれば良いので、次のような方法もあります。

```Linux:Linux
[testuser@testhost ~]$ bc <<EOF
> 1 + 1
> 3 * 8
> EOF
2
24
[testuser@testhost ~]$ cat test.list
1 + 1
3 * 8
[testuser@testhost ~]$ bc < test.list
2
24
```

処理速度という点ではやはり1〜3の方法には敵いませんが、他の方法に比べ圧倒的に複雑な計算が可能です。
ただ、標準入力から計算式を取得する必要があるなど、バッチでの使用を考えた場合は少々面倒です。
基本的には以下のように対話的に使用するコマンドです。

```Linux:Linux
[testuser@testhost ~]$ bc
bc 1.07.1
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006, 2008, 2012-2017 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
scale=20
a=3
a
3
b=2*sqrt(3)
b
3.46410161513775458704
for (i=1;i<=100;++i) {b=2/(1/a+1/b);a=sqrt(a*b)}
a
3.14159265358979323901
b
3.14159265358979323903
quit
```

# 6. awkコマンド

awkコマンドを使用する方法です。
このコマンドもbashの組み込みコマンドではありません。
計算式は以下のように記載します。
計算式中についてスペースの有無は問いません。

```Linux:Linux
awk '{print 計算式}'
```

計算結果は標準出力に出力されます。
そこまで複雑な計算はできませんが、地味に整数だけでなく小数も使用可能だったりします。
ただ、変数から数値を読み込んでの計算は少々面倒だったり、変数に代入した計算式の評価はできないなど、変数との相性はあまりよくありません。
なお、awkコマンドは読み込み元のテキストがないと正常に動作しないため、以下ではechoコマンドで空文字をパイプしています。
以下、実行例です。

```Linux:Linux
[testuser@testhost ~]$ echo | awk  '{print 1 + 1}'
2
[testuser@testhost ~]$ echo | awk  '{print 1+1}'
2
[testuser@testhost ~]$ echo | awk  '{print 7 - 5}'
2
[testuser@testhost ~]$ echo | awk  '{print 3 * 8}'
24
[testuser@testhost ~]$ echo | awk  '{print 20 / 3}'
6.66667
[testuser@testhost ~]$ echo | awk  '{print 20 % 3}'
2
[testuser@testhost ~]$ echo | awk  '{print 1 + 2 * 3}'
7
[testuser@testhost ~]$ echo | awk  '{print ( 1 + 2 ) * 3}'
9
[testuser@testhost ~]$ echo | awk  '{print 3 + -5}'
-2
[testuser@testhost ~]$ echo | awk  '{print 1.1 + 0.1}'
1.2
[testuser@testhost ~]$ NUM1=15
[testuser@testhost ~]$ NUM2=3
[testuser@testhost ~]$ echo $NUM1
15
[testuser@testhost ~]$ echo $NUM2
3
[testuser@testhost ~]$ echo | awk -v N1=$NUM1 -v N2=$NUM2 '{print N1 / N2}'
5
[testuser@testhost ~]$ CALC="3 - 2"
[testuser@testhost ~]$ echo $CALC
3 - 2
[testuser@testhost ~]$ echo | awk -v C="$CALC" '{print C}'
1 + 1
```

処理速度の点では他の方法よりも遅く、bcコマンドほど複雑な計算もできません。
扱いも少々面倒なため、通常の計算であれば他の手段の方が無難です。
この方法の最大の利点は、表形式で並んだ数字を一括で計算できるという点です。
例えば以下のようなことができます。

```Linux:Linux
[testuser@testhost ~]$ cat test.csv
id,total,used
1,100,32
2,521,186
3,999,894
[testuser@testhost ~]$ cat test.csv | awk -F , 'NR > 1 {print $3 / $2 * 100}'
32
35.7006
89.4895
```

上記ではcsvファイルから読み込んだ値を計算していますが、区切り文字を変えればその他の表形式にも対応できますし、freeコマンドのように結果が表形式で出力されるコマンドにも使用できます。
そういう意味で、他の方法とは一味違う活用ができるコマンドです。