こうした疑問は、bashの算術機能が歴史的な経緯で複数の書き方を持っているために起きる。外部コマンドの
expr、bashビルトインの let、そして算術展開の $(( )) は、同じ整数演算を異なるアプローチで実現するが、選択を誤ると set -e 環境でスクリプトが意図せず終了するなどの罠にはまる。本記事では、現代のbashスクリプトで推奨される算術設計の指針を整理し、カウンター設計・閾値チェック・浮動小数点計算まで、実務で即使える実装パターンを解説する。
実行環境:RHEL 9.4 / Rocky Linux 9.4 / Ubuntu 22.04 LTS(bash 5.1.x)で動作確認済み。
この記事のポイント
・整数演算は $(( )) が第一選択 — 外部プロセス不要でスクリプト内で完結する
・if (( val > threshold )) の形で閾値チェックが直感的に書ける
・set -e 環境では let の終了コードの罠に要注意(結果が0で異常終了する)
・浮動小数点が必要なときだけ bc を呼び出す設計にする
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜbashには複数の算術書き方があるのか
Linuxシェルスクリプトを学び始めると、次の4種類の算術表現に出会う。| 書き方 | 種別 | 使うべき場面 |
|---|---|---|
expr $a + $b |
外部コマンド(POSIX) | POSIX sh 互換が必要なとき(今は非推奨) |
$[ $a + $b ] |
旧bash構文(廃止済み) | 絶対に使わない |
let "count += 1" |
bashビルトイン | 後述の罠があるため $(( )) を優先する |
$(( a + b )) |
bash算術展開 | 整数演算の第一選択 |
expr は外部プロセスを起動するため遅く、乗算の * はクォートが必要(expr $a \* $b)と扱いが煩わしい。現代のbashスクリプトでは $(( )) 一本に絞るのが設計の基本方針だ。
$(( )) で整数演算を設計する
1. 基本の四則演算と代入
$(( )) の中はbashの算術式として評価され、変数の $ 記号を省略できる。
#!/bin/bash a=10 b=3 echo $(( a + b )) # 13 echo $(( a - b )) # 7 echo $(( a * b )) # 30 echo $(( a / b )) # 3 (整数除算。小数は切り捨て) echo $(( a % b )) # 1 (余り) # 結果を変数に代入する result=$(( a * b + 5 )) echo "result=$result" # result=35
[user@rocky94 ~]$ bash calc_sample.sh 13 7 30 3 1 result=35
2. カウンター設計 — ループと組み合わせる
シェルスクリプトでよく使うカウンターは$(( count + 1 )) または (( count++ )) で実装する。
#!/bin/bash # ファイル処理の進捗カウンター total=$(ls /var/log/*.log 2>/dev/null | wc -l) count=0 for logfile in /var/log/*.log; do (( count++ )) echo "[$count/$total] 処理中: $logfile" # ここで実際の処理を行う done echo "完了: ${count}件処理"
(( count++ )) と set -e の関係:(( count++ )) は count が 0 のときに終了コード1を返す(算術式の値が0=偽)。set -e 環境では次のように書く。
#!/bin/bash set -euo pipefail count=0 # NG: count が 0 のとき (( count++ )) は終了コード1を返し、set -e で終了する # (( count++ )) ← 初回実行でスクリプトが終了してしまう # OK: || true で終了コードを無害化する (( count++ )) || true # または算術展開形式を使う(こちらは終了コードに影響しない) count=$(( count + 1 ))
3. 剰余を使ったバッチ処理の実装
100件ごとに処理の区切りを挿入するパターンは剰余(%)で実現する。
#!/bin/bash count=0 while IFS= read -r line; do (( count++ )) || true process_line "$line" if (( count % 100 == 0 )); then echo "[$count件処理済み] $(date '+%H:%M:%S')" fi done < /var/data/input.csv
条件判定に使う (( )) テスト構文
1. if文との組み合わせ
if (( 算術式 )); then の形で、整数比較をC言語スタイルで書ける。数値に -gt や -lt を使う [ ] 形式より直感的だ。
#!/bin/bash score=85 # (( )) スタイル(推奨) if (( score >= 80 )); then echo "合格" fi # [ ] スタイル(POSIXだが読みにくい) if [ "$score" -ge 80 ]; then echo "合格" fi
2. ディスク使用量の閾値チェック実装パターン
実務でよく使うのがディスク・CPU・メモリの閾値チェックだ。df コマンドの出力を整数として取得し、(( )) で比較する。
#!/bin/bash # /var パーティションの使用率が85%超でアラート THRESHOLD=85 MOUNT=/var usage=$(df -P "$MOUNT" | awk 'NR==2 {gsub(/%/,""); print $5}') if (( usage > THRESHOLD )); then echo "[ALERT] ${MOUNT} のディスク使用率が ${usage}% に達しました(閾値: ${THRESHOLD}%)" # メール送信やWebhook通知をここに追加 exit 1 fi echo "[OK] ${MOUNT} ディスク使用率: ${usage}%"
[user@web-server01 ~]$ bash disk_check.sh [OK] /var ディスク使用率: 72% [user@web-server01 ~]$ df -P /var Filesystem 1024-blocks Used Available Capacity Mounted on /dev/mapper/vg0-var 10485760 7540224 2945536 72% /var
3. ポーリングのタイムアウト設計
サービス起動を待つスクリプトで、試行回数を制限するカウンター設計の例を示す。#!/bin/bash MAX_RETRY=30 INTERVAL=2 count=0 while ! systemctl is-active --quiet httpd; do (( count++ )) || true if (( count >= MAX_RETRY )); then echo "ERROR: httpd が ${MAX_RETRY} 回以内に起動しませんでした" >&2 exit 1 fi echo "待機中... ($count/${MAX_RETRY})" sleep "$INTERVAL" done echo "httpd 起動確認(${count}回試行)"
算術計算の設計パターンを含むシェルスクリプトの実践的な書き方をさらに体系的に学びたい方は、シェルスクリプト実践講座(Linux Master Pro)もご覧ください。現場で使える設計パターンをハンズオンで習得できます。
浮動小数点計算は bc に任せる設計
bashの算術展開は整数演算のみで、小数点以下は切り捨てられる。パーセンテージや平均値など小数が必要な場合は、外部コマンドbc を使う。
1. bc の基本的な使い方
# scale で小数点以下の桁数を指定する $ echo "scale=2; 10 / 3" | bc 3.33 $ echo "scale=4; 22 / 7" | bc 3.1428
2. シェルスクリプトへの組み込みパターン
処理成功率の計算など、スクリプト内で浮動小数点が必要な場合の定石を示す。#!/bin/bash total=357 success=298 # 整数演算だと小数が消える echo "整数除算: $(( success * 100 / total ))%" # 83% # bc で小数点2桁まで rate=$(echo "scale=2; $success * 100 / $total" | bc) echo "処理成功率: ${rate}%" # 83.47%
bc は外部プロセスを起動するためループ内での多用は避ける。ループ外で一括計算するか、整数演算で代替できないか先に検討する。
set -e 環境でのlet・(( ))の罠と対処
1. let の罠 — 結果が0のとき終了コード1を返す
let コマンドは演算結果が数値の0のとき終了コード1を返す。set -e と組み合わせると予期しない場所でスクリプトが終了する。
#!/bin/bash set -e # NG: count が 0 になったときスクリプトが終了する let count=0 # 終了コード1 → set -e でスクリプト終了 # OK パターン1: || true で無害化 let count=0 || true # OK パターン2: $(( )) を使う(終了コードは常に0) count=$(( 0 ))
2. (( )) テスト自体の終了コード
(( 式 )) は式が0(偽)のとき終了コード1を返す。if 文の条件式として使う分には問題ないが、単独でコマンドとして呼ぶと set -e に影響する。
#!/bin/bash set -e x=0 # NG: (( x )) は x=0 のとき終了コード1 → set -e でスクリプト終了 # (( x )) # OK: if 文の条件として使う(終了コードは if が吸収する) if (( x == 0 )); then echo "x は 0 です" fi # OK: 単独呼び出しには || true を添える (( x == 0 )) || true echo "ここまで到達"
よくある算術エラーとトラブルシュート
変数が空(未初期化)のとき未初期化の変数を算術演算に使うと、0として扱われる。
set -u 環境では ${count:-0} でデフォルト値を設定する。
#!/bin/bash set -euo pipefail # NG: set -u 環境で未初期化変数を使うとエラー # echo $(( count + 1 )) # bash: count: unbound variable # OK: デフォルト値 0 を設定する count=${count:-0} count=$(( count + 1 )) echo "count=$count" # count=1
df の出力に含まれる % 記号などをそのまま演算式に渡すと構文エラーになる。awk や sed で数値部分だけを取り出してから使う。
# NG: "72%" という文字列は算術式に使えない raw="72%" # echo $(( raw + 1 )) # bash: 72%: value too great for base # OK: % を除去してから使う num=${raw%\%} # パラメータ展開で末尾の % を除去 echo $(( num + 1 )) # 73
本記事のまとめ
| やりたいこと | 推奨する書き方 |
|---|---|
| 整数演算の結果を変数に代入 | result=$(( a + b )) |
| 変数をインクリメント | count=$(( count + 1 )) |
| 整数インクリメント(set -e に注意) | (( count++ )) || true |
| if 文で整数比較 | if (( count > 10 )); then |
| 剰余でN件ごとの区切り判定 | if (( count % 100 == 0 )); then |
| 浮動小数点演算 | echo "scale=2; $a / $b" | bc |
| 未初期化変数のデフォルト値設定 | count=${count:-0} |
| 文字列から数値部分だけ取り出す | ${var%\%}(末尾の%を除去) |
$(( )) で統一し、set -e 環境での let の罠と (( )) の終了コードを押さえておけば、数値を扱うシェルスクリプトのほとんどのケースに対応できる。浮動小数点が必要なときだけ bc を呼び出す設計にすることで、パフォーマンスと可読性を両立させよう。
シェルスクリプト講座を見る >>
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:シェルスクリプトでtarを使った増分バックアップを実装する方法|フルと差分の組み合わせと世代管理設計
- この記事の属するカテゴリ:シェルスクリプトへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます