並列シェルコマンドキュー

2026/11/14 2:13

並列シェルコマンドキュー

RSS: https://news.ycombinator.com/rss

要約▶

日本語訳:

改善は不要です;既存の要約は主要ポイントを完全に網羅している上で、正確かつ明確です。

本文

Bash で並列実行キューを制限する実装方法

Bash の処理速度向上を図る一方で、並列度(コンカレンシー)を抑制したいというユースケースにおいて、スレッドプールのような仕組みを作成する方法について解説します。

本手法は Bash と Zsh に対応し、堅牢性と互換性を確保するためには一定の工夫が必要ですが、以下のコードをスクリプトや設定ファイル(

.bashrc
/
.zshrc
)に追加することで利用可能です。

コード概要

以下は、シェルコマンドを実行するための**命令列(キュー)**を作成する関数の実装です。 事前にすべてのコマンドを用意する必要はなく、時間をかけて順次キューに追加していくことができます。

# シェルのコマンドキューを開始します。
# @param {number} $1 一度に行う最大のジョブ数 (並列度)
queue_start() {
  # xargs による並列性の限界として有効な値であることを検証する
  if [[ ! ${1:-} =~ ^[1-9][0-9]*$ ]]; then
    echo "queue_start: size must be a positive integer, got '${1:-}'" >&2
    return 1
  fi


  # 継承または以前のコマンドキューから受け継がれたファイルディスクリプター(FD)を閉じる
  if [[ -n ${_queue_fd:-} ]]; then
    eval "exec ${_queue_fd}>&-"
    unset _queue_fd _queue_pid
  fi


  # zsh のリダイレクト上限である 3 から 9 までの範囲で、使用されていない最初の FD を探す
  local fd=3
  while { : <&"${fd}"; } 2>/dev/null; do fd=$(( fd + 1 )); done
  if [[ ${fd} -gt 9 ]]; then
    echo "queue_start: no free fd from 3 to 9" >&2
    return 1
  fi


  # プライベートな一時ディレクトリに FIFO キューファイルを作成する
  local dir
  dir=$(mktemp -d "${TMPDIR:-/tmp}/queue.XXXXXX")
  mkfifo "${dir}/queue"


  # コマンドを実行するシェルを見つける(現在のシェル)
  local shell
  shell=$(ps -o comm= -p "$")
  shell=${shell#-}


  # xargs をバックグラウンドで開始し、キューファイルから NULL で区切られたコマンドを読み取る。
  # ジョブメッセージはジョブ制御をオフにし、ブロックの標準エラー出力を捨てて隠す。xargs は、
  # ファイルが開かれるまでの間は、キューファイルの fd コピーを通じて本当の標準エラー出力を受け取る。
  eval "exec ${fd}>&2"
  local opts=$-
  set +m
  {
    # shellcheck disable=SC2016
    xargs -0 -n 1 -P "$1" "${shell}" -c '
      # コマンドの出力はストリーミングではなくキャプチャされるため、コマンドの出力が適切に分離される
      out=$(eval -- "$1" </dev/null 2>&1); rc=$?
      [ -z "${out}" ] || printf "%s\n" "${out}"
      # xargs を止めるような 255 というコードになるのを防ぐため、非ゼロのエラーコードをすべて 1 に変換する
      [ "${rc}" -eq 0 ] || { printf "exit %d: %s\n" "${rc}" "$1" >&2; exit 1;
    }' _ <"${dir}/queue" 2>&"${fd}" &
    _queue_pid=$!
    disown || :
  } 2>/dev/null
  if [[ ${opts} == *m* ]]; then set -m; fi


  # xargs が読み書きのために開くのを待機しながら、キューファイルを書き込み用に開く。
  # 両端が開かれている状態であれば、ファイルを安全に削除できる。
  eval "exec ${fd}>\"\${dir}/queue\""
  _queue_fd=${fd}
  rm -rf "${dir}"


  # この PID の子プロセスを終了するための exit ハンドラを登録する。これにより zsh は bash と同様に振る舞うようにする。
  setopt localoptions posixtraps 2>/dev/null || :
  trap _queue_stop EXIT
  if [[ $- != *i* ]]; then
    trap 'exit 129' HUP
    trap 'exit 130' INT
    trap 'exit 143' TERM
  fi
}


# シェルのコマンドキューにコマンドをキューします。
# @param {string} $1 実行するコマンド
queue_submit() {
  if [[ -z ${_queue_fd:-} ]]; then
    echo "queue_submit: no command queue started in this shell" >&2
    return 1
  fi
  # キューファイルにコマンドを書き込む(キューがいっぱいだった場合はブロック)。
  # xargs が終了してしまった場合でも、このシェルのスクリプトが殺されるのではなく printf プロセスが殺されるようになっています。
  if [[ $- == *i* ]]; then
    # 対話型シェルは TERM/HUP/INT トラップを保持しないため、キューが満杯になったことがこれらのトラップが遅延することを避けるために、
    # foreground でコマンドを書き込む(対話型シェルの場合は foreground で行う必要がある)。
    env printf '%s\0' "$1" >&"${_queue_fd}"
  else
    # キューが満杯になっても TERM/HUP/INT トラップが遅延しないように、バックグラウンドでコマンドを書き込む。
    env printf '%s\0' "$1" >&"${_queue_fd}" &
    wait "$!"
  fi || {
    echo "queue_submit: couldn't queue the command; the command queue may have stopped" >&2
    return 1
  }
}


# キューファイルを閉じ、xargs が実行中のジョブを完了するのを待つ。その後 xargs が終了するのを待ちます。
queue_wait() {
  if [[ -z ${_queue_fd:-} ]]; then
    echo "queue_wait: no command queue started in this shell" >&2
    return 1
  fi
  eval "exec ${_queue_fd}>&-"
  while kill -0 "${_queue_pid}" 2>/dev/null; do sleep 0.1; done
  unset _queue_fd _queue_pid
}


# 指定された PID 以下のすべてのプロセスを停止します。
# @param {number=} $1 停止すべき子孫を持つ親の PID、デフォルトは現在のシェルの PID
_queue_stop() {
  # subshell の場合は $ が親プロセスの PID であるため、子の PPID からこのシェルの PID を取得し、
  # デフォルトとしてこのシェルの PID を採用する。
  local parent=${1:-$(exec sh -c 'echo "${PPID}"')}
  local pid
  for pid in $(exec pgrep -P "${parent}"); do
    # プロセスを一時停止させ、新しい子プロセスを開始できなくする。
    kill -STOP "${pid}" 2>/dev/null || continue
    # プロセスの子孫を終了させ、その後プロセス自体を終了させる。
    _queue_stop "${pid}"
    kill -TERM "${pid}" 2>/dev/null || :
    # 終了信号に対応できるように、プロセスを再開する。
    kill -CONT "${pid}" 2>/dev/null || :
  done
}


# exit ですべてのバックグラウンドプロセスを停止する。それらは Ctrl-C を無視し、TERM シグナルも受け取らないためです。
if [[ $- != *i* ]]; then
  trap _queue_stop EXIT
  trap 'exit 129' HUP
  trap 'exit 130' INT
  trap 'exit 143' TERM
fi

動作仕組みの要約

  • queue_start
    : キュー(FIFO ファイルで管理)を初期化し、実行コンポーネント(
    xargs -P
    で並列度を制御)を開始します。
  • queue_submit
    : コマンドをキューに追加します。
  • queue_wait
    : キューが空になるまで待機し、リソースのクリーンアップを行います。呼び出しは任意ですが、終了時に推奨されます。

使用例

大規模な Web リクエストの実行(Postman Echo)

最大 3 並列で 5 件のリクエストを実行します。

$ queue_start 3
$ for n in 1 2 3 4 5; do
    queue_submit "set -o pipefail; \
      curl -sS --fail-with-body --max-time 10 -X POST -H 'Content-Type: application/json' \
        -d '{\"request\": ${n}}' \
        https://postman-echo.com/post | \
      jq -c .json"
  done
$ queue_wait
{"request":1}
{"request":2}
{"request":3}
{"request":4}
{"request":5}

並列性の確認

最大 2 並列で指定された順序に従って実行されることを示します。コマンド 2 が完了するまで、コマンド 3 は開始されません。

$ queue_start 2
$ queue_submit 'sleep 1; echo first'
$ queue_submit 'echo second'
$ queue_submit 'echo third'
$ queue_wait
second
third
first

サブシェルによる継承と隔離

サブシェルではそれぞれ独立したコマンドキューが機能します。

# 共通のキューから両方のシェルへコマンドを送信
( queue_start 2 ) &
queue_submit 'echo "main shell job"'
wait
sleep 0.1 # 子プロセスが終了するのを待つ

# 別々のキューで動作確認
first=$!
(
  queue_start 2
  for n in 1 2 3 4; do queue_submit "sleep 1; echo first queue, job ${n}"; done
  queue_wait
) & wait "${first}"

second=$!
(
  queue_start 1
  for n in 1 2; do queue_submit "sleep 1; echo second queue, job ${n}"; done
  queue_wait
) & wait "${second}"

# 出力結果:各キューが独立して動作していることが確認できる
second queue, job 1
first queue, job 1
first queue, job 2
second queue, job 2
first queue, job 3
first queue, job 4

動的なコマンド生成(出力の各行をキューに追加)

他のコマンドの実行結果を、それぞれ個別のコマンドとしてキューへ投入できます。

$ queue_start 2
$ while IFS= read -r line; do
    queue_submit "${line}"
  done < <(printf 'echo "from command output, line %d"\n' 1 2 3)
$ queue_wait
from command output, line 1
from command output, line 2
from command output, line 3

ファイルの各行をキューに追加

テキストファイルから読み込んだ各行をコマンドとして実行します。

$ cat commands.txt
echo "cmd 1"
echo "cmd 2"
echo "cmd 3"

$ queue_start 2
$ while IFS= read -r line; do
    queue_submit "${line}"
  done < commands.txt
$ queue_wait

既知の問題点と限界

Bash/Zsh で実装する以上、以下の制限や課題があります。Python など他の言語を使う方が適しているケースも多いため注意してください。

  • キューの単一性: シェルには一つのコマンドキューしか保持できません。複雑なマルチキューを実現するには多くの追加コードが必要ですが、通常は不要です(サブシェルで隔離する場合は可能)。
  • 出力の分離保証がない: 意図的にストリーミングを行いません。複数のコマンドが同じ出力を生成する場合、混在して表示される可能性があります(
    PIPE_BUF
    より大きな出力は特に注意)。
  • エラーハンドリングの簡略化:
    queue_wait
    はジョブ失敗の詳細なステータス値を返しません。エラー検知には一時ファイルや追加の FD が必要となり複雑化します。
  • プロセス終了時の遅延:
    queue_wait
    は開始後のプロセスも待機しますが、書き込み FD が継承されるため、すべてのコピーが閉じるまでキューの終端が見えない場合があります。
  • 非選択的なプロセス停止:
    _queue_stop
    は子孫を indiscriminately(非選択的に)停止するため、
    queue_start
    以前に開始されたバックグラウンドジョブも終了してしまいます
    。

なぜ単に
xargs -P
を使わないのか?

事前に処理すべきコマンドのリストが既にある場合は、以下の方法でシンプルに並列実行可能です。

# コマンドをヌル文字区切りで出力し、xargs に渡す
$ printf 'echo "from command output, line %d"\0' 1 2 3 | \
    xargs -0 -n 1 -P 2 bash -c

# またはテキストファイルから読み取る
$ tr '\n' '\0' < commands.txt | \
    xargs -0 -n 1 -P 2 bash -c

より高度な並列処理を必要とする場合は、macOS では標準搭載されていませんが

GNU parallel
が推奨されます。

$ printf 'echo "from command output, line %d"\n' 1 2 3 | \
    parallel -j 2

同じ日のほかのニュース

一覧に戻る →

2026/11/14 6:05

FAA が全米の地上停止を発表

## 日本語翻訳: 航空交通管理システム指令センター(ATCSCC)は、2013 年 11 月 26 日 13:20 に発効し、14:04 まで続く「国内地面停止勧告第 111-DCC」を発令しました。これは、米国連邦航空指令センターによる例外的な安全事情に基づいており、本命令によりすべての空港は地上停止を維持し、離陸便は一切許可されません。署名のタイムスタンプは 26/11/13 20:54 と記載されていますが、運用上の停止は指定された終了時刻まで、直ちにすべての離陸便に適用されます。国際線入国便は自動的に取消されるものではなく、当局によってケースバイケースで対応されます。したがって、航空会社と空港運営者は、この特定の緊急期間中に旅客の安全を確保するため、即座にスケジュールの調整を行う必要があります。

2026/11/14 3:40

カリフォルニアからテキサスへの輸送中に NVIDIA ハードウェア 1140 万ドルが消失

## Japanese Translation: 要約: 高価な NVIDIA コンピュータ機器 6 パレット、額面価値 1,140 万ドルが、8 月 29 日にトラックが商品を引き取った後、オークランドからフォートワースへの輸送中に行方不明になった。その車両は食品運輸会社のロゴを掲げており、捜査員はその車両を長時間の停車を経て追跡したが、サンバーナディノ郡(南カリフォルニア)で行方をたてなくなった。オークランド警察は 2026 年 9 月 13 日付けの被害届に基づき強盗事件として捜査を進めているが、1,140 万ドルの評価額や特定の貨物・所有者・送り主・運送事業者・運転者の特定は行っておらず、逮捕者は発表されず、回収も確認されていない。NVIDIA はコメント要請に回答していない。本件は、他の輸送から盗まれたデータセンター用貨物の総額でほぼ 1 億 1,100 万ドルという広範な報告のさなかであり、別の事件として嫌疑者が確立されたトラック事業所のアイデンティティを悪用したとされる 1,200 万ドルの捜査も進められているが、当局はその事件を本件とは関連付けられていない。公衆からの問い合わせはオークランド警察 (510) 238-3728 まで。本件は物流セキュリティにおけるリスクの高まりと、高価な電子機器を対象としたアイデンティティ関連の貨物盗難に対する強化対策の緊急性を示している。

2026/11/14 7:39

Claude メーティンズ

## Japanese Translation: Anthropic は、エンタープライズ向け AI の大規模アップデートを実施し、「Claude」がビデオ会議に能動的な参加者として加わることを可能にしました。これは、受動的な文字起こしからリアルタイム協調作業への大きな転換であり、AI が通話に参加して議論を追跡したりタスクを管理したり、ドキュメントの共同作成(計画の要約や承認済み予算データの取得など)を即時に行う仕組みです。ユーザーは通話中にドキュメントやホワイトボードを共同で構築でき、生成されたすべての資料は claude.ai 内の専用ページに安全に保存されます。現状の統合では Google Meet がサポートされていますが、Gong、Otter、Fireflies.ai など他のパートナーへの対応も急速に拡大しています。 ただし、組織はこの機能と厳格なセキュリティ制御をバランスさせる必要があります。通話への参加には明示的なユーザー同意が必要であり、現在では Zero Data Retention(ZDR)構成、HIPAA 準拠設定、顧客管理暗号化鍵(CMEK)などは利用できない状態です。また、デフォルトでは外部ゲストが入場した場合に Claude は通話に参加しませんが、管理者はこの動作を上書きできます。ベータフェーズでは Team および Enterprise プランにおいて、2027 年 2 月までアーティファクトの生成が無料であり、ノートや文字起こしも無料で利用可能ですが、連携ツールの過度な使用は特定のプランの制限に影響する可能性があります。