シェルスクリプトの繰り返し(ループ)処理で、カウントアップをして指定回数分のループをさせることがあるかと思います。その時の式について書き方を、時間計測を交えて確認してみました。
環境
$ cat /etc/almalinux-release
AlmaLinux release 9.5 (Teal Serval)
$ uname -r
5.14.0-503.23.1.el9_5.x86_64
先に結論
カウントアップするときは、「((++COUNT))」か「「COUNT=$(( COUNT + 1 ))」を使う。
((++COUNT))
$ vi counter1.bash
#!/bin/bash
COUNT=0
END_COUNT=3
while [[ "${COUNT}" -lt "${END_COUNT}" ]] ; do
((++COUNT))
echo ${COUNT}
done
COUNT=$(( COUNT + 1))
$ vi counter2.bash
#!/bin/bash
COUNT=0
END_COUNT=3
while [[ "${COUNT}" -lt "${END_COUNT}" ]] ; do
COUNT=$(( COUNT + 1 ))
echo ${COUNT}
done
カウントアップのいろいろの書き方
カウントアップの書き方はいろいろありますが、それぞれの実行にかかる時間を計測してみます。
シェルスクリプト
$ vi count_jikan.bash
#!/bin/bash
# ファンクション設定
function test1 () {
local COUNT=0 END_COUNT=9999
while [ "${COUNT}" -lt "${END_COUNT}" ] ; do
COUNT=$(expr "${COUNT}" + 1)
done
}
function test2 () {
local COUNT=0 END_COUNT=9999
while [ "${COUNT}" -lt "${END_COUNT}" ] ; do
let COUNT="${COUNT} + 1"
done
}
function test3 () {
local COUNT=0 END_COUNT=9999
while [ "${COUNT}" -lt "${END_COUNT}" ] ; do
let COUNT++
done
}function test4 () {
local COUNT=0 END_COUNT=9999
while [ "${COUNT}" -lt "${END_COUNT}" ] ; do
let ++COUNT
done
}
function test5 () {
local COUNT=0 END_COUNT=9999
while [ "${COUNT}" -lt "${END_COUNT}" ] ; do
(( COUNT++ ))
done
}
function test6 () {
local COUNT=0 END_COUNT=9999
while [ "${COUNT}" -lt "${END_COUNT}" ] ; do
(( ++COUNT ))
done
}
function test7 () {
local COUNT=0 END_COUNT=9999
while [ "${COUNT}" -lt "${END_COUNT}" ] ; do
COUNT=$(( COUNT + 1 ))
done
}
# 実行
TIMEFORMAT='real %R'
echo
echo "### test1 ###"
time test1
echo
echo "### test2 ###"
time test2
echo
echo "### test3 ###"
time test3
echo
echo "### test4 ###"
time test4
echo
echo "### test5 ###"
time test5
echo
echo "### test6 ###"
time test6
echo
echo "### test7 ###"
time test7
echo
$ chmod 755 count_jikan.bash
実行結果
$ ./count_jikan.bash
### test1 ###
real 7.983
### test2 ###
real 0.054
### test3 ###
real 0.044
### test4 ###
real 0.044
### test5 ### ・・・ 早い
real 0.038
### test6 ### ・・・ 早い
real 0.038
### test7 ###
real 0.044
文法的に正しいのは、、、
shellcheckを使用して確認してみます。
$ shellcheck count_jikan.bash
In count_jikan.bash line 6:
COUNT=$(expr "${COUNT}" + 1)
^--^ SC2003 (style): expr is antiquated. Consider rewriting this using $((..)), ${} or [[ ]].
In count_jikan.bash line 12:
let COUNT="${COUNT} + 1"
^----------------------^ SC2219 (style): Instead of 'let expr', prefer (( expr )) .
In count_jikan.bash line 18:
let COUNT++
^---------^ SC2219 (style): Instead of 'let expr', prefer (( expr )) .
In count_jikan.bash line 24:
let ++COUNT
^---------^ SC2219 (style): Instead of 'let expr', prefer (( expr )) .
For more information:
https://www.shellcheck.net/wiki/SC2003 -- expr is antiquated. Consider rewr...
https://www.shellcheck.net/wiki/SC2219 -- Instead of 'let expr', prefer (( ...
文法的にも、「test1〜test4」は良くないので、対象から外します。
インクリメント演算子の使用時に気を付けること
処理速度で早い結果となった、「 (( COUNT++))」と「((++COUNT))」があります。
ただし、「 (( COUNT++))」の場合は、「set -e」を設定すると想定した動作になりません。
メモ
「set -e」はコマンドの実行結果がエラー(戻り値が 0 以外)のとき、シェルスクリプトを自動的に中断させる機能です。
「 ((COUNT++))」と「((++COUNT))」の戻り値
「((COUNT++))」と「((++COUNT))」はインクリメント演算子と呼ばれるもので、下記のような動作の違いがあります。
((COUNT++)) ・・・ 後で+1をする。
((++COUNT)) ・・・ 先に+1をする。
$ count=0
$ echo ${count} $((count++)) ${count}
0 0 1
$ count=0
$ echo ${count} $((++count)) ${count}
0 1 1
((COUNT++)は後で+1をするので、その時点での式の評価は「0」である。
すなわち、戻り値は「1(false)」となる。
$ ((count++))
$ echo $?
1
$ ((++count))
$ echo $?
0
シェルスクリプト (( COUNT ++ ))
$ vi count_test5.bash
#!/bin/bash
set -e
function test5 () {
local COUNT=0 END_COUNT=3
while [ "${COUNT}" -lt "${END_COUNT}" ] ; do
(( COUNT++ ))
echo "カウントアップした"
done
}
echo "### test4 ###"
test4
echo
実行 (( COUNT++ ))
「set -e」は戻り値が0以外は直ちに終了するので、カウントアップせずに終了してしまう。
$ ./count_test5.bash
### test4 ###
シェルスクリプト (( ++COUNT ))
vi count_test6.bash
#!/bin/bash
set -e
function test6 () {
local COUNT=0 END_COUNT=3
while [ "${COUNT}" -lt "${END_COUNT}" ] ; do
(( ++COUNT ))
echo "カウントアップした"
done
}
echo "### test6 ###"
test6
echo
実行 (( ++COUNT ))
$ ./count_test6.bash
### test6 ###
### カウントアップした ###
### カウントアップした ###
### カウントアップした ###
間違いが起こりにくい書き方 COUNT=$(( COUNT + 1 ))
インクリメント演算子は間違いを起こす場合があるので、多少の実行時間が増えるものの個人的には問題が発生しにくい書き方だと思います。
シェルスクリプト COUNT=$(( COUNT + 1))
$ vi count_test7.bash
#!/bin/bash
set -e
function test7 () {
local COUNT=0 END_COUNT=3
while [[ "${COUNT}" -lt "${END_COUNT}" ]] ; do
COUNT=$(( COUNT + 1 ))
echo "### カウントアップした ###"
done
}
echo "### test7 ###"
test7
echo
実行 COUNT=$(( COUNT + 1))
$ ./count_test7.bash
### test7 ###
### カウントアップした ###
### カウントアップした ###
### カウントアップした ###