1. 概要

Linuxコマンドラインを使用する場合、通常はフォアグラウンドでLinuxコマンドを実行します。 ただし、状況によっては、バックグラウンドで複数のコマンドを実行する必要があります。

このチュートリアルでは、次の2つのアプローチを使用して、バックグラウンドジョブとして複数のコマンドを実行する方法を説明します。

  • 複数のコマンドを単一のバックグラウンドジョブとして実行する
  • 複数のバックグラウンドジョブで複数のコマンドを実行する

このチュートリアルでは、Bashシェルに焦点を当てます。

2. 1つのジョブとして複数のコマンドを実行する

通常、コマンドが相互に関連している場合、複数のコマンドを1つのジョブとして実行する必要があります。

次の3つの手順で、複数のコマンドを1つのジョブとして開始できます。

  1. コマンドの組み合わせ– 条件付きロジックの要件に応じて、「;」、「&&」、または「||」を使用してコマンドを連結できます。 、 例えば: cmd1; cmd2 && cmd3 || cmd4
  2. コマンドのグループ化– 組み合わせたコマンドを「{}」または「()」でグループ化できます。 、 例えば: (cmd1; cmd2 && cmd3 || cmd4)
  3. コマンドグループをジョブとしてバックグラウンドに送信する–追加する場合オペレーターコマンドグループの後、シェルはコマンドグループをバックグラウンドジョブとして実行します。次に例を示します。 (cmd1; cmd2 && cmd3 || cmd4)&

次に、複数のコマンドを単一のバックグラウンドジョブとして実行する方法の例を見てみましょう。

高価な計算からの数値に対して奇数-偶数チェックを実行し、結果を出力するとします。 コストのかかる計算には時間がかかる場合があるため、コマンドをバックグラウンドジョブとして実行する必要があります。 したがって、上記の3つの手順を適用して、コマンドを作成できます。

$ ( echo "Calculating the number..."; sleep 8; \
    NUM=$RANDOM; [ $((NUM%2)) -eq 0 ] && echo "$NUM: even" || echo "$NUM: odd" ) &

上記のコマンドでは、 sleep コマンドを使用して、数値計算プロセスをシミュレートしています。 それでは、小さなデモを通じて、ジョブとしてどのように実行されているかを見てみましょう。

デモが示すように、コマンドを3回開始しました。 コマンドを起動するたびに、コマンドグループはバックグラウンドジョブとして実行されていました。 jobs コマンドを使用して、ジョブのステータスを監視できます。

3. 出力の制御

複数のコマンドを単一のバックグラウンドジョブとして起動する方法を学びました。 デモを確認すると、ジョブからのすべての出力が端末に出力されていることがわかります。 それの訳は &演算子によって開始されたバックグラウンドプロセスは、シェルからstdoutとstderrを継承します。

実際には、ジョブの出力が現在の端末を台無しにしないように、ジョブの出力をファイルにリダイレクトしたいことがよくあります。 また、ジョブの結果やジョブ実行のログを効率的に確認できます。 これを実現するために、コマンドグループの出力をファイルにリダイレクトできます。

$ ( echo "Calculating the number..."; sleep 8; \
    NUM=$RANDOM; [ $((NUM%2)) -eq 0 ] && echo "$NUM: even" || echo "$NUM: odd" ) > result.txt &
[1] 40030
$ 
[1]+  Done                    ( echo "Calculating the number..."; sleep 8; ... ) > result.txt
$ cat result.txt 
Calculating the number...
30027: odd

したがって、ジョブの出力はファイルに送られます result.txt。 現在の端末には、ジョブのPIDと完了した通知のみが表示されます。

ジョブのPIDは重要な情報です。 通常、それを抑制したくはありません。 ただし、コマンドの起動後に出力を表示したくない場合は、非表示にすることができます。

$ { ( echo "Calculating the number..."; sleep 8; NUM=$RANDOM; \
    [ $((NUM%2)) -eq 0 ] && echo "$NUM: even" || echo "$NUM: odd" ) > result.txt & } 2>/dev/null
$
$ jobs
[1]+  Running                 ( echo "Calculating the number..."; sleep 8; ... ) > result.txt &
$
[1]+  Done                    ( echo "Calculating the number..."; sleep 8; ... ) > result.txt
$ cat result.txt 
Calculating the number...
7667: odd

上記の出力が示すように、 stderrを/dev/ nullにリダイレクトして、「&」演算子の出力を抑制しました。これは、stderrにPID情報が書き込まれるためです。

上記の出力を詳しく見ると、ジョブの完了通知がまだ印刷されていることがわかります。 完了したジョブの通知は、シェルの機能です。 IOリダイレクトでは制御できません。

完了通知も非表示にする場合は、グループを「()」に変更して、サブシェルでジョブを開始する必要があります。 ただし、副作用として、ジョブの制御が失われます。 したがって、これはお勧めしません。

$ ( ( echo "Calculating the number..."; sleep 8; NUM=$RANDOM; \
     [ $((NUM%2)) -eq 0 ] && echo "$NUM: even" || echo "$NUM: odd" ) > result.txt & )
$ jobs

上記のコマンドが示すように、ジョブを「()」グループでラップする場合は、stderrをリダイレクトする必要はありません。 これは、ジョブが現在のシェルで開始されておらず、現在のシェルのstderrも使用していないためです。

さらに、jobsコマンドは何も出力しないことがわかります。 つまり、現在のシェルはこのジョブについて認識していません。 したがって、ジョブを監視または制御することはできません。 ジョブが終了したかどうかもわからないため、result.txtを確認する適切なタイミングもわかりません。

4. 複数のコマンドを複数のジョブとして実行する

私たちもできます複数のコマンドを異なるジョブとして実行して、それらを並行して実行できるようにします。 

これを実現するために、「 ”演算子をバックグラウンドに送信するコマンドまたはコマンドグループに追加します。次に例を示します。

cmd1 & cmd2 & (cmd3; cmd4) &

上記の例では、3つのジョブを開始し、それらを並行して実行します。

  • Job1: cmd1
  • Job2: cmd2
  • Job3:(cmd3; cmd4)

具体的な例は、それをすばやく理解するのに役立ちます。

$ date & (sleep 5; echo "cmd2 done") & (sleep 3; echo "cmd3 done") & 
[1] 41012
[2] 41013
[3] 41014
Sun Sep 13 01:31:57 PM CEST 2020
$ jobs
[1]   Done                    date
[2]-  Running                 ( sleep 5; echo "cmd2 done" ) &
[3]+  Running                 ( sleep 3; echo "cmd3 done" ) &
$ cmd3 done
cmd2 done
[2]-  Done                    ( sleep 5; echo "cmd2 done" )
[3]+  Done                    ( sleep 3; echo "cmd3 done" )

上記の出力が示すように、3つのバックグラウンドジョブを開始し、それらを並行して実行します。

5. バックグラウンドジョブの完了を待っています

これまで、コマンドをバックグラウンドジョブとして開始する方法を見てきました。 また、 jobs コマンドを使用して、ジョブのステータスレポートを取得できます。

時々、特にシェルスクリプトを書くとき、バックグラウンドジョブとしていくつかの時間のかかるコマンドを開始し、ジョブの結果に依存するいくつかのさらなる計算を実行したい場合があります。

つまり、これらのジョブが完了するのを待ってから、追加のコマンドを実行する必要があります。

この場合、 jobs コマンドはあまり役に立ちません。スクリプトで、ジョブステータスを確認し、出力を解析して、必要なすべてのジョブが終了したかどうかを判断するのは効率的ではないためです。

それでは、waitコマンドを紹介します。

waitはシェルの組み込みコマンドです。 PIDをwaitコマンドに渡し、それらのプロセスの完了を待機するように要求できます。

例を通してwaitコマンドを理解しましょう。

$ cat await-jobs.sh
#!/bin/bash
JOB1_RESULT="/tmp/job1.result"
JOB2_RESULT="/tmp/job2.result"

rm -f $JOB1_RESULT $JOB2_RESULT

(sleep 5; echo $RANDOM > "$JOB1_RESULT") &
PID_JOB1=$!
echo "job1 started with PID $PID_JOB1"

(sleep 3; echo $RANDOM > "$JOB2_RESULT") &
PID_JOB2=$!
echo "job2 started with PID $PID_JOB2"

echo "Await two jobs' completion..."
wait $PID_JOB1 $PID_JOB2

awk 'NR==FNR{ r1=$1;printf "job1 result: %d\n", r1; next }
        { r2=$1;printf "job2 result: %d\n", r2 }
     END{printf "The Sum: %d\n", r1+r2}' $JOB1_RESULT $JOB2_RESULT

上記のawait-jobs.shスクリプトは、2つのバックグラウンドジョブを開始します。 各ジョブはランダムな番号を生成し、それをファイルに書き込みます。

しばらく実行されるジョブをシミュレートするために、sleepコマンドを使用して数秒間スリープしました。

言及する価値がありますシェル特殊変数$! 最後のプロセスのPIDを教えてくれます 。 ここでは、この変数を使用してバックグラウンドジョブのPIDを取得できます。

次に、 wait コマンドを使用して、両方のジョブの完了を待機しました。

2つのジョブが終了すると、結果が結果ファイルに書き込まれます。 awk コマンドを使用して結果ファイルを読み取り、各結果をそれらの合計と一緒に出力します。

デモを通じてスクリプトがどのように機能するかを見てみましょう。

6. 結論

この記事では、複数のコマンドを単一のバックグラウンドジョブとして実行する方法を示しました。 さらに、ジョブの出力を制御する方法についても説明しました。

さらに、複数のコマンドを複数のバックグラウンドジョブとして起動し、それらを並行して実行させる手法についても説明しました。

最後に、waitコマンドを使用してバックグラウンドジョブの完了を待機する方法を示しました。 これは、バックグラウンドのジョブ関連のシェルスクリプトを作成するときに役立つテクニックです。