シェルスクリプトの繰り返し(ループ)処理で、カウンターをつけて指定回数分のループをさせることが多いと思います。その時の書き方について、時間計測とともに確認してみました。
システム環境
$ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.1 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.1 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy
$ uname -r
5.15.0-47-generic
先に結論
カウントアップは、「COUNT=$(( COUNT + 1 ))」と書こう!!
カウントアップのシェルスクリプト
とりあえず、5種類のカウントアップで処理にかかる時間計測をしてみました。
$ vi counter.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
(( COUNT++ ))
done
}
function test5 () {
local COUNT=0 END_COUNT=9999
while [ "${COUNT}" -lt "${END_COUNT}" ] ; do
COUNT=$(( COUNT + 1 ))
done
}
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
$ chmod 755 counter.bash
結果的には、「test4」が、もっとも高速ですね。
$ ./counter.bash
### test1 ###
real 0m10.277s
user 0m7.939s
sys 0m3.157s
### test2 ###
real 0m0.060s
user 0m0.060s
sys 0m0.000s
### test3 ###
real 0m0.050s
user 0m0.050s
sys 0m0.000s
### test4 ### ・・・ これが早い(だがしかし、、、)
real 0m0.043s
user 0m0.043s
sys 0m0.000s
### test5 ###
real 0m0.053s
user 0m0.053s
sys 0m0.000s
文法的に正しいのは・・・?
shellcheckを使って、チェックをしてみます。
$ shellcheck counter.bash
In counter.bash line 6:
COUNT=$(expr "${COUNT}" + 1)
^--^ SC2003 (style): expr is antiquated.
Consider rewriting this using $((..)), ${} or [[ ]].
In counter.bash line 13:
let COUNT="${COUNT} + 1"
^----------------------^ SC2219 (style): Instead of 'let expr',
prefer (( expr )) .
In counter.bash line 20:
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〜test3」は良くないので、対象から外します。
シェルスクリプトの罠
処理速度では、「 (( COUNT++))」を良さそうに見えますが、シェルスクリプト内で「set -e」を使うと想定した動作になりません。
「set -e」はコマンドの実行結果がエラー(戻り値が 0 以外)のとき、シェルスクリプトを自動的に中断させる機能です。
$ cat counter-test4.bash
#!/bin/bash
set -e
function test4 () {
local COUNT=0 END_COUNT=3
while [ "${COUNT}" -lt "${END_COUNT}" ] ; do
(( COUNT++ ))
echo "カウントアップした"
done
}
echo "### test4 ###"
test4
echo
$ ./counter-test4.bash ・・・ 実行する
### test4 ###
$
$ cat counter-test5.bash
#!/bin/bash
set -e
function test5 () {
local COUNT=0 END_COUNT=3
while [ "${COUNT}" -lt "${END_COUNT}" ] ; do
COUNT=$(( COUNT + 1 ))
echo "カウントアップした"
done
}
echo "### test5 ###"
test5
$ ./counter-test5.bash ・・・ 実行する
### test5 ###
カウントアップした
カウントアップした
カウントアップした