シェルスクリプトでカウンターの書き方

スポンサーリンク

シェルスクリプトの繰り返し(ループ)処理で、カウンターをつけて指定回数分のループをさせることが多いと思います。その時の書き方について、時間計測とともに確認してみました。

システム環境

$ 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 ###
カウントアップした
カウントアップした
カウントアップした