web-dev-qa-db-ja.com

bashスクリプトでジョブコントロールを使用できないのはなぜですか?

this answer to another question では、

スクリプトではジョブ制御がありません(そしてそれをオンにしようとするのはばかげています)

これを聞いたのはこれが初めてであり、ジョブ制御(第7章)のbash.infoセクションを調べて、これらの主張のいずれについても言及していません。 [更新:manページは少し良くなり、「典型的な」使用、デフォルト設定、および端末I/Oについて言及していますが、実際にジョブを実行する理由はありません制御はスクリプトでは特に不適切です。]

それでは、スクリプトベースのジョブ制御が機能しないのはなぜですか。また、それが悪い習慣(別名「愚かな」)になっているのはなぜですか。

編集:問題のスクリプトはバックグラウンドプロセスを開始し、2番目のバックグラウンドプロセスを開始してから、最初のプロセスをフォアグラウンドに戻して、通常のターミナルI/O(直接実行する場合と同様)。これは、スクリプトの外部からリダイレクトできます。バックグラウンドプロセスに対してそれを行うことはできません。

他の質問の 受け入れられた回答 で述べたように、ジョブ制御を試行せずにその特定の問題を解決する他のスクリプトが存在します。いいね。そして、lambastedスクリプトはハードコードされたジョブ番号を使用します—明らかに悪いです。しかし、私はジョブコントロールが根本的に破滅的なアプローチであるかどうかを理解しようとしています。まだそれは多分それのようですうまくいくかもしれません...

48
system PAUSE

彼が意味したことはジョブコントロールはデフォルトで非インタラクティブモードでオフになっています(つまり、スクリプト内)。

bash manページから:

JOB CONTROL
       Job  control refers to the ability to selectively stop (suspend)
       the execution of processes and continue (resume) their execution at a
       later point.
       A user typically employs this facility via an interactive interface
       supplied jointly by the system’s terminal driver and bash.

そして

   set [--abefhkmnptuvxBCHP] [-o option] [arg ...]
      ...
      -m      Monitor mode.  Job control is enabled.  This option is on by
              default for interactive shells on systems that support it (see
              JOB CONTROL above).  Background processes run in a separate
              process group and a line containing their exit status  is
              printed  upon  their completion.

彼が「愚かである」と言ったとき、彼はそれだけでなく、

  1. ジョブ制御meantは、主にインタラクティブな制御を容易にするためです(これに対して、スクリプトはpidを直接操作できます)。
  2. 私は彼の最初の答えを引用します、...スクリプトで以前に他のジョブを開始しなかったという事実に依存しています。これは悪い仮定です。これはまったく正しいです。

[〜#〜]更新[〜#〜]

あなたのコメントへの回答:はい、bashスクリプトでジョブコントロールを使用するのを止められる人は誰もいません-強制的に無効化するための難しいケースはありませんset -m(つまり、必要に応じて、スクリプトからのジョブ制御は機能します。)最後に、特にスクリプトでは、常に猫のスキンを作成する方法は複数ありますが、いくつかの方法はより移植性が高く、より多くの信頼性が高く、エラーケースの処理、出力の解析などをより簡単にします。

あなたの特定の状況は、lhunath(および他のユーザー)が「ベストプラクティス」と見なす方法とは異なる方法を保証する場合としない場合があります。

42
vladr

bgおよびfgを使用したジョブ制御は、対話型シェルでのみ役立ちます。ただし、&waitと組み合わせて使用​​すると、スクリプトでも役立ちます。

マルチプロセッサシステムでバックグラウンドジョブを生成すると、スクリプトのパフォーマンスが大幅に向上します。 CPUごとに少なくとも1つのコンパイラーを開始するビルドスクリプト、またはImageMagickツールを並列に使用してイメージを処理する場合など。

次の例では、最大8つの並列gccを実行して、配列内のすべてのソースファイルをコンパイルします。

#!bash
...
for ((i = 0, end=${#sourcefiles[@]}; i < end;)); do
    for ((cpu_num = 0; cpu_num < 8; cpu_num++, i++)); do
        if ((i < end)); then gcc ${sourcefiles[$i]} & fi
    done
    wait
done

これについては「愚かな」ことは何もありません。ただし、スクリプトが続行する前にすべてのバックグラウンドジョブを待機する wait コマンドが必要です。最後のバックグラウンドジョブのPIDは$!変数に格納されるため、wait ${!}も使用できます。 Nice コマンドにも注意してください。

時々そのようなコードはメイクファイルで役に立ちます:

buildall:
    for cpp_file in *.cpp; do gcc -c $$cpp_file & done; wait

これにより、make -jよりも細かく制御できます。

&;のような行末記号であることに注意してください(command&ではなくcommand&;と記述してください)。

お役に立てれば。

31

ジョブ制御は、対話型シェルを実行している場合、つまりstdinとstdoutが端末デバイス(Linuxでは/ dev/pts/*)に接続されていることがわかっている場合にのみ役立ちます。次に、前景に何か、背景に何か他のものを置くことは理にかなっています。

一方、スクリプトにはそのような保証はありません。スクリプトを実行可能にして、端末を接続せずに実行できます。この場合、フォアグラウンドまたはバックグラウンドプロセスを使用しても意味がありません。

ただし、他のコマンドをバックグラウンドで非対話的に実行し(コマンドラインに「&」を追加)、$!を使用してそれらのPIDをキャプチャできます。次に、killを使用して、それらを強制終了または一時停止します(ターミナルでCtrl-CまたはCtrl-Zをシミュレートし、シェルはインタラクティブでした)。 waitの代わりにfgを使用して、バックグラウンドプロセスが完了するのを待つこともできます。

7
Juliano

スクリプトでジョブ制御をオンにして、SIGCHLDにトラップを設定すると便利です。マニュアルのJOB CONTROLセクションには次のように書かれています。

シェルは、ジョブの状態が変化するとすぐに学習します。通常、bashは、他の出力を中断しないように、ジョブのステータスの変更を報告する前に、プロンプトを出力する直前まで待機します。 set builtinコマンドの-bオプションが有効になっている場合、bashはそのような変更をすぐに報告します。 SIGCHLDのトラップは、終了する子ごとに実行されます。

(強調は私のものです)

例として、次のスクリプトを見てください。

dualbus@debian:~$ cat children.bash 
#!/bin/bash

set -m
count=0 limit=3
trap 'counter && { job & }' CHLD
job() {
  local amount=$((RANDOM % 8))
  echo "sleeping $amount seconds"
  sleep "$amount"
}
counter() {
  ((count++ < limit))
}
counter && { job & }
wait
dualbus@debian:~$ chmod +x children.bash 
dualbus@debian:~$ ./children.bash 
sleeping 6 seconds
sleeping 0 seconds
sleeping 7 seconds

注:bash 4.3以降、CHLDトラップが壊れているようです

ただし、bash 4.3では、「wait -n」を使用して同じことを実現できます。

dualbus@debian:~$ cat waitn.bash 
#!/home/dualbus/local/bin/bash

count=0 limit=3
trap 'kill "$pid"; exit' INT
job() {
  local amount=$((RANDOM % 8))
  echo "sleeping $amount seconds"
  sleep "$amount"
}
for ((i=0; i<limit; i++)); do
  ((i>0)) && wait -n; job & pid=$!
done
dualbus@debian:~$ chmod +x waitn.bash 
dualbus@debian:~$ ./waitn.bash 
sleeping 3 seconds
sleeping 0 seconds
sleeping 5 seconds

これをより移植性の高い方法で、つまりCHLDを使用せずに、または-n待機する他の方法があると主張することもできます。

dualbus@debian:~$ cat portable.sh 
#!/bin/sh

count=0 limit=3
trap 'counter && { brand; job & }; wait' USR1
unset RANDOM; rseed=123459876$$
brand() {
  [ "$rseed" -eq 0 ] && rseed=123459876
  h=$((rseed / 127773))
  l=$((rseed % 127773))
  rseed=$((16807 * l - 2836 * h))
  RANDOM=$((rseed & 32767))
}
job() {
  amount=$((RANDOM % 8))
  echo "sleeping $amount seconds"
  sleep "$amount"
  kill -USR1 "$$"
}
counter() {
  [ "$count" -lt "$limit" ]; ret=$?
  count=$((count+1))
  return "$ret"
}
counter && { brand; job & }
wait
dualbus@debian:~$ chmod +x portable.sh 
dualbus@debian:~$ ./portable.sh 
sleeping 2 seconds
sleeping 5 seconds
sleeping 6 seconds

したがって、結論として、set -mはnotがスクリプトで便利です。スクリプトにもたらす唯一の興味深い機能はSIGCHLDと連携できることです。また、同じことをより短くする(wait -n)か、よりポータブルにする(シグナルを自分で送信する)には、他の方法もあります。

あなたが言うように、Bashはジョブ制御をサポートします。シェルスクリプトの作成では、bashがあるという事実に頼ることはできないが、これまではジョブコントロールがなかったバニラボーンシェル(sh)があると仮定することがよくあります。

あなたが本当のボーンシェルに正直に制限されているシステムを想像するのに、私は最近プレッシャーをかけています。ほとんどのシステムの/bin/shbashにリンクされます。それでも可能です。あなたができることの1つは、指定する代わりに

#!/bin/sh

できるよ:

#!/bin/bash

それとあなたのドキュメントは、あなたのスクリプトがbashを必要としていることを明確にするでしょう。

2
Peter

おそらくo/tですが、長時間実行されているジョブでサーバーにSSHで接続するときにNohupを頻繁に使用するため、ログアウトしてもジョブは完了します。

インタラクティブなマスターシェルからの停止と開始、およびバックグラウンドプロセスの生成が混乱しているのではないでしょうか。 waitコマンドを使用すると、多くのことをスポーンし、それらがすべて完了するのを待つことができます。前述のように、私は常にNohupを使用しています。これはこれよりも複雑で、十分に使用されていません-shもこのモードをサポートしています。マニュアルをご覧ください。

あなたも持っています

kill -STOP pid

次のように、現在実行中のSudoを一時停止したい場合は、頻繁にそうします。

kill -STOP $$

しかし、編集者からシェルにジャンプした場合、あなたはそれを甘やかすでしょう-それはすべてそこに座っているだけです。

入力の危険性があるため、ニーモニック-KILLなどを使用する傾向があります。

kill - 9 pid # note the space

昔は、マシンを初期化するため、マシンをダウンさせることがありました。

0
Ghoti

ジョブはbashスクリプトで機能します

しかし、あなたは...のような生成されたスタッフを監視する必要があります:

ls -1 /usr/share/doc/ | while read -r doc ; do ... done

|の両側でジョブのコンテキストが異なります

これをバイパスすることは、whileの代わりにforを使用している可能性があります。

for `ls -1 /usr/share/doc` ; do ... done

これは、スクリプトでジョブを使用する方法を示す必要があります...コメント付きのメモは...であると記載されています。

    #!/bin/bash


for i in `seq 7` ; do ( sleep 100 ) &  done

jobs

while [ `jobs | wc -l` -ne 0 ] ; do

    for jobnr in `jobs | awk '{print $1}' | cut -d\[ -f2- |cut -d\] -f1` ; do
        kill %$jobnr
    done
    #this is REALLY ODD ... but while won't exit without this ... dunno why
    jobs >/dev/null 2>/dev/null
done

sleep 1
jobs
0
THESorcerer