rcmdnk's blog

[アディダス] adidas adizero Bash IV

Bashでの数の取り扱いについて今更ながら知ったことがあったのでまとめ。 特にスクリプトなどで数値をインクリメントしたいときなどにどうするか、について。

expr

恐らく最初に最初に覚える方法。 というか純粋なBourn Shell (sh)だと下のletや二重括弧記号が使えないのでこれが基本。 今やshもほとんどbashへのリンクになったりしてるので 問題ないかもしれませんが、移植性を高くしたければexprにしておくのが無難。

$ a=1
$ a=`expr $a + 1`
$ echo $a
2

と言った感じ。 扱える数値は整数のみ。exprに与える式は演算子の両側に必ずスペースが必要です。

exprで四則演算などに文字列を渡すとエラーになります。

$ expr a + 1
expr: not a decimal number: 'a'

掛け算(*)については、下についてもほとんどの場合共通ですが、 アスタリスク特殊文字なのでエスケープが必要です。 また、括弧を使った優先順位の指定も出来ますが、これもエスケープが必要。

$ expr \( 3 + 2 \) \* 4
20

余談ですが、bashなどだとこの様な評価結果を渡すのにはバックスラッシュ` よりは

$ a=$(expr $a + 1)

と言った形が使えるので、こちらのが便利な事が多いです(見やすい、ネスト出来る、等) 1

さらにexprは文字列を比較してマッチした数等を返す事も出来ます 2

let

letは文字列として与えられた式を評価して変数に入れます。

$ eq="1+1"
echo $eq
1+1
$ let a=$eq
$ echo $a
2

直接

$ a=1
$ let a=($a + 1)
$ echo $a
2

とわたしてもOK。letで与える場合は"でも(で囲っても同じ結果です。 これらで囲まない場合はBashの代入規則通りスペース無しで書かないといけません。

また、letで評価される中身に関しては、文字が含まれると勝手に変数として 変換されるので、

$ let a=(a + 1)

としても同じ結果になります。

演算部を含めて文字列として与えてるので、四則演算を含めた変数をつくって

$ a="2+"
$ b="3"
$ let c=$a$b
$ echo $c
5

と言った感じに使うことも出来ます。

さらに、letでは変数をインクリメント演算子(++)を使って直接インクリメントすることが出来ます。

$ let a=$a+1

$ let a++

は同じこと。

また、全ての変数を数値として宣言(declare -e)しておくとletしなくても 計算する様になります。

$ declare -i a b c
$ a=1
$ b=2
$ c=$a+$b
$ echo "$a+$b=$c"
2+3=5

これも整数のみ。

二重括弧

$(())とドル記号+二重括弧の中に式書く事で letを使って渡すことと同じ事が出来ます。

$ a=1
$ b=2
$ c=$((a+b))

これも二重括弧の中はドル記号あっても無くても同じ。 この場合は直接

$ echo $((a+b))

みたいなことも出来るのでletよりも扱いやすいと思います。

分かりやすいので、今まではこれを使って

$ a=$((a+1))

みたいのを使っていました。

++も使えるのですが、

$ $((++a))
bash: 2: command not found

と単純に書くと中身はインクリメントされるのですが、それをそのままコマンドとして 実行する様な形になってエラーになってしまうので。

$ a=$((++a))

とすればインクリメントされてきますが、

$ a=$((a++))

としてしまうと、右辺を$aと評価した後右辺におけるaをインクリメントして 右辺の評価値(すなわち元々の$a)を再びaに入れるので、結局一生そのままです。

なので文字数も一緒なので+1しておいた方が分かりやすいかと。

コロン+二重括弧

シェルスクリプトを書いてる時に、if文等で何もしない行を組み込みたいときなど、 何も書かないとエラーになってしまいますが、:を書くと、 何も実行しないがエラーにならなくなります。

if [ 1 -eq 1 ];then
  # echo 処理をコメントアウトしたら中身が無くなる。
fi

のように、中身が無くなると

syntax error near unexpected token `fi'

と言ったエラーが出ます。これを避けるためには

if [ 1 -eq 1 ];then
  # echo 処理をコメントアウトしたら中身が無くなる。
  :
fi

の様に:を加えて上げるとエラーを避けられます。 :は何もせずにtrueを返すコマンドで、一応コマンドを実行してる事にはなるので、 エラーを避けられます。

:の使い方としては処理のない所に取り敢えず置いとくもの、という認識だったんですが、 実は:の右側にコマンドを書いておくことが出来て、 コマンドの内容が場合によっては実行されます。

これを使って、

$ : $((a++))
$ echo $a
2

と書くことが出来ます。 :の右側に必ずスペースが必要です。

さらに短縮

と、色々見ているとさらに短縮できることが 3

How to use double or single bracket, parentheses, curly braces

$ a=1
$ ((a++))
$ echo $a
2

これだけで良い様です。 元々二重括弧内に式を書くとそれが評価され、 それを$を前に付けることで中身の結果を取り出すような形なんだと思いますが、 これだけを実行してもOK、とのこと。

上のコロンに引き続き、この辺、きっちり何がどうなってるのか よく理解できて無いので適当に 別途書きなぐってみましたが、 取り敢えず、

$ ((a++))

がシェルスクリプトでの最も簡単なインクリメントの書き方だ、と言うことで。

小数を取り扱いたい時

上の方法は全てシェルの組み込みの物ですが、整数しか扱えません。

小数を扱いたい時はbcや他のプログラムを使います。 bcはそのまま起動するとインタラクティブに計算出来る様に立ち上がりますが、 式をパイプで渡してあげるとその式の結果を返してくれます。 但し、bcではscaleと言う変数で精度が定められていて、 初期値は0です。従って乗除するとき、そのままだと、

$ bc
scale
0
5/2
2

となります。 途中でscaleを変えれば

scale=2
5/2
2.50

の様に変更できますが、 scaleの初期値を簡単に変更するには-lオプションを使うのが便利で、 これを使うとscaleが20になります。 -lオプションは本来数学ライブラリを追加するオプションで、 これにより三角関数(s()等)や指数関数(e())を使える様になります。

bcはbashrc等で

alias bc="bc -l"

とエイリアスしておくと便利です。

これを使って

$ a=$(echo 5 / 2 | bc)
$ echo $a
2.50000000000000000000

とすれば小数をシェルスクリプト内で扱えます。

これをこんな感じで

calcBC.sh
1
2
3
4
5
6
7
8
9
10
#!/bin/bash
eq=$@
eq=$(echo $eq|sed "s/e+\([0-9]*\)/*10^\1/g")
eq=$(echo $eq|sed "s/e\([0-9]*\)/*10^\1/g")
eq=$(echo $eq|sed "s/e\(-[0-9]*\)/*10^\1/g")
eq=$(echo $eq|sed "s/E+\([0-9]*\)/*10^\1/g")
eq=$(echo $eq|sed "s/E\([0-9]*\)/*10^\1/g")
eq=$(echo $eq|sed "s/E\(-[0-9]*\)/*10^\1/g")
eq=$(echo $eq|sed "s/\*\*/^/g")
echo $eq|bc -l

スクリプトにしておけば

$ calcBC.sh 5 / 2
2.50000000000000000000

の様にexprコマンド的に使えます。 最初にごにょごにょしてるのは bcでは3*10^3みたいな^使った指数表記は使えますが、 eを底とした浮遊小数点表記3e10が使えないので、 上のスクリプトでは3e10等を変換するためです。 他の言語のアウトプットでeを使った表記がよくあるので そのまま使えるようにするために。 **も使えないので^にしています。

勿論bcでなくても計算出来るコマンドなら何でも良い訳で perlなんかを使って

こんなのを.bashrcに書いておけば calcと言うコマンドでexpr的に使えます。 bcと違って逆に^が使えないので逆に変換してます。

素直にperlのスクリプトを作っても良いわけですが、 簡単に.bashrcに書いて関数にして使いたかったのでこんな感じに。

実は両者とも随分昔に随分面倒な事を数値や演算子を一つ一つ見て、 みたいなことをしたのを書いて使ってたんですが、 改めて見たら多分こんな感じで素直にやる方が良いのかと。

ということで、以上です 4

Sponsored Links
  1. これをするとexprの移植性が高いのが優位だ、という点が消えますが。。。

  2. exprは文字列の評価(::matchしているかどうか、等)も行えて

    $ expr "abcdefg" : "abc"
    3
    

    と、マッチした場合は文字数を出力。 但し先頭からマッチしてないと駄目で

    $ expr "abcdefg" : "bcd"
    0
    

    と途中を見ると失敗します。

    また、評価する側に\(\)を含むとその中身を抜き出せます。

    $ expr "abcdefg" : "..\(..\)..."
    cd
    

    sedだとか他のコマンドが強力なので敢えてexprでやることは無いと思いますが こういうのもある、と言うことで。

  3. ちょっと余談でこのページを見つけるときに別の怪しげなサイトが。。。

    怪しげなのに良く検索に引っかかってしまうサイト: プログラム問答 ja.softuses.com](/blog/2013/11/13/computer-bash-colon/)

  4. 最後の余談として、 最初に挙げたバッシュの画像なんですが、 Bashという名前の物があったもので。 多分、一般名ではなくて商品名としてのもの?

Sponsored Links

« Octopressのgenerate_onlyをモット便利に Bashでのコロンコマンドや二重括弧について »

}