Bash組み込みコマンド vs 外部コマンド:パフォーマンス比較

テスト、算術演算、文字列処理にはシェル組み込みコマンドを使用し、外部コマンドはバッチ処理することでBashスクリプトを高速化します。

Bash組み込みコマンド vs 外部コマンド:パフォーマンス比較

Bashスクリプトが遅く感じられる場合、その原因は多くの場合、数千もの外部プロセスを起動するループにあります。Bash組み込みコマンドと外部コマンドの選択は実用的なパフォーマンス上の判断です。単純な作業にはシェル自身の機能を使い、外部ツールはより適切に処理できるジョブのために残しておきましょう。

このガイドでは、組み込みコマンドが役立つ場面、外部コマンドが依然として有効な場面、そして最も一般的なプロセス生成の落とし穴を回避する方法を説明します。

Bashにおけるコマンド実行の理解

Bashがコマンドを解釈するとき、エイリアス、シェルキーワード、関数、組み込みコマンド、そしてPATHを通じて見つかるコマンドの順に解決します。この解決順序が重要なのは、現在のシェル内で処理されるものは別のプログラムを起動する必要がないからです。

1. 組み込みコマンド

Bash組み込みコマンドは、Bashシェル実行可能ファイル自体に直接実装された関数です。これらはオペレーティングシステムのfork()およびexec()システムコールを呼び出す必要がありません。実行は既存のシェルプロセス内で完全に行われるため、組み込みコマンドは優れたパフォーマンス、最小限のオーバーヘッド、そしてシェル変数や状態への即時アクセスを提供します。

組み込みコマンドの主な特徴:

  • 速度: 最速の実行パス。
  • オーバーヘッド: 新しいプロセスが作成されないため、ほぼゼロのオーバーヘッド。
  • 環境: 現在のシェル環境に直接作用します。

2. 外部コマンド

外部コマンドは独立した実行可能ファイル(多くの場合/bin/usr/binなどのディレクトリに配置)です。Bashが外部コマンドを実行するとき、以下の手順が必要です:

  1. fork()で新しい子プロセスを作成する。
  2. その子プロセス内で外部プログラムをexec()する。
  3. 子プロセスが完了するのを待つ。

このオーバーヘッドは1回の実行では些細なものですが、ループや高頻度の操作では急速に蓄積され、外部コマンドを組み込みコマンドよりも大幅に遅くします。

パフォーマンス対決:組み込みコマンドの実力

パフォーマンスの違いを示すために、Bashが組み込みと外部の両方の選択肢を提供する一般的なタスクを考えてみましょう。

例1:文字列操作と長さの計算

変数の長さを計算することは、古典的なパフォーマンステストケースです。

コマンドタイプ コマンド 説明
組み込み ${#variable} 長さを求めるパラメータ展開。非常に高速。
外部 expr length "$variable" 外部のexprユーティリティを呼び出す。低速。

パフォーマンスのヒント: 長さの計算には、expr lengthwc -cへのパイプではなく、常にパラメータ展開(${#var})を使用してください。

例2:文字列置換

変数内の部分文字列を置換することも一般的な操作です。

コマンドタイプ コマンド 説明
組み込み ${variable//pattern/replacement} パラメータ展開による置換。高速。
外部 sed 's/pattern/replacement/g' 外部のsedユーティリティを呼び出す。低速。

コード比較例:

TEXT="hello world hello"

# 組み込み(高速)
NEW_TEXT_1=${TEXT//hello/goodbye}

# 外部(低速)
NEW_TEXT_2=$(echo "$TEXT" | sed 's/hello/goodbye/g')

例3:ループと反復

反復処理を行う場合、ループ内部で使用されるコマンドが非常に重要です。

コマンドタイプ コマンド 説明
組み込み read 入力を効率的に行ごとに読み取るために使用。
外部 grepawkcut ループ内でデータを外部ツールにパイプすると、繰り返しプロセスが作成される。

while readのアンチパターン vs 組み込みコマンド:

よくある遅いパターンは、ループ内でファイルの内容を外部コマンドにパイプすることです:

# 低速:すべての行に対して'grep'を起動
while read LINE; do
    echo "Processing: $LINE" | grep "important"
done < input.txt

最適化戦略: 可能であれば、Bash組み込みコマンドまたは内部リダイレクトを使用して、ループ内での外部コマンドの使用を避けてください。

パフォーマンス向上のための主要なBash組み込みコマンド

これらの組み込みコマンドを同等の外部コマンドよりも優先することで、スクリプトの速度が大幅に向上します:

タスクカテゴリ 組み込みコマンド 外部代替(低速)
算術演算 (( expression )) exprbc
ファイルテスト [[ ... ]] または Bash組み込みの [ ... ] 外部の /usr/bin/test または /usr/bin/[
文字列操作 ${var/pat/rep}${#var} sedawkexpr
ループ/ファイル読み取り read grepawksed(反復的に使用する場合)
シェルコードの読み込み source または . filename 別のスクリプトを子プロセスとして実行

算術演算の例

組み込み(高速):

COUNTER=0
(( COUNTER++ ))
if (( COUNTER > 10 )); then echo "Done"; fi

外部(低速):

COUNTER=$(expr "$COUNTER" + 1)
if [ "$COUNTER" -gt 10 ]; then echo "Done"; fi

外部コマンドが必要な場合

基本的な操作では組み込みコマンドをデフォルトの選択とすべきですが、Bashがネイティブまたは効率的に処理できないタスクには外部ユーティリティが不可欠です。以下の場合、外部コマンドを使用する必要があります:

  1. 高度なテキスト処理: awksedperl などのツールが提供する複雑なパターンマッチング、複数行操作、特定のフォーマット。
  2. システムユーティリティ: OSと深く連携するコマンド(lspsfindmount、ネットワーキングツール(curlping)など)。
  3. 外部ファイル: Bashのリダイレクトでは扱いにくい複雑な形式のファイルの読み書き。

外部コマンド使用のベストプラクティス

外部コマンドをどうしても使用しなければならない場合は、呼び出し回数を最小限に抑えるようにしてください。ループ内で外部コマンドを実行する代わりに、単一の外部呼び出しでデータのバッチ全体を処理するようにロジックを再構築します。

非効率: 1000個のファイルを個別にstatで処理する。

効率的: findstatを組み合わせた1回の呼び出し、または単一のawkスクリプトを使用して、必要なすべてのメタデータを一度に収集する。

まとめ

Bashのパフォーマンス最適化は、不必要なプロセス生成を避けることから始まります。算術演算、テスト、単純な文字列操作にはデフォルトで組み込みコマンドを使用してください。外部ツールが適切なツールである場合は、1行や1ファイルごとではなく、バッチに対して1回実行します。

  • デフォルトで組み込みコマンドを使用: 算術演算((( )))、文字列操作(${...})、テスト([[ ]])には、常にシェル組み込みコマンドを選択してください。
  • ループ内のI/Oを避ける: 多数の小さな呼び出しではなく、単一の外部コマンド呼び出しを使用してバッチ処理を行うようにループをリファクタリングしてください。
  • パラメータ展開を使用: 文字列の長さにはwcexprよりも${#var}を優先してください。
  • トレードオフを認識: 必要な機能がBashで利用できない、または扱いにくい場合は、外部ユーティリティを使用してください。