シェルスクリプトでカウントアップするときの方法

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

環境

$ 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 ###
### カウントアップした ###
### カウントアップした ###
### カウントアップした ###
タイトルとURLをコピーしました