非同期ジョブを実行し、bashから終了コードと出力を取得します。

非同期ジョブを実行し、bashから終了コードと出力を取得します。

複数のbashコマンドを非同期で実行する必要があり、コマンドが完了したら終了コードと出力に基づいてアクションを実行する必要があります。実際のユースケースでは、これらのタスクがどれだけ実行されるかを予測することはできません。

この問題を解決するために、私は次のアルゴリズムを使用しました。

For each task to be run:
    Run the task asynchronously;
    Append the task to the list of running tasks.
End For.

While there still are tasks in the list of running tasks:
    For each task in the list of running tasks:
        If the task has ended:
            Retrieve the task's exit code and output;
            Remove the task from the list of running tasks.
        End If.
    End For
End While.

これにより、次のbashスクリプトが提供されます。

  1 #!/bin/bash
  2 
  3 # bg.sh
  4 
  5 # Executing commands asynchronously, retrieving their exit codes and outputs upon completion.
  6 
  7 asynch_cmds=
  8 
  9 echo -e "Asynchronous commands:\nPID    FD"
 10 
 11 for i in {1..10}; do
 12         exec {fd}< <(sleep $(( i * 2 )) && echo $RANDOM && exit $i) # Dummy asynchronous task, standard output's stream is redirected to the current shell
 13         asynch_cmds+="$!:$fd " # Append the task's PID and FD to the list of running tasks
 14         
 15         echo "$!        $fd"
 16 done    
 17 
 18 echo -e "\nExit codes and outputs:\nPID       FD      EXIT    OUTPUT"
 19 
 20 while [[ ${#asynch_cmds} -gt 0 ]]; do # While the list of running tasks isn't empty
 21         
 22         for asynch_cmd in $asynch_cmds; do # For each to in thhe list
 23                 
 24                 pid=${asynch_cmd%:*} # Task's PID
 25                 fd=${asynch_cmd#*:} # Task's FD
 26                 
 27                 if ! kill -0 $pid 2>/dev/null; then # If the task ended
 28                         
 29                         wait $pid # Retrieving the task's exit code
 30                         echo -n "$pid   $fd     $?      "
 31                         
 32                         echo "$(cat <&$fd)" # Retrieving the task's output
 33                         
 34                         asynch_cmds=${asynch_cmds/$asynch_cmd /} # Removing the task from the list
 35                 fi
 36         done
 37 done

出力には、wait各ジョブの終了コードを取得しようとして失敗したと表示されます。最後の実行以外:

Asynchronous commands:
PID     FD
4348    10
4349    11
4351    12
4353    13
4355    14
4357    15
4359    16
4361    17
4363    18
4365    19

Exit codes and outputs:
PID     FD  EXIT OUTPUT
./bg.sh: line 29: wait: pid 4348 is not a child of this shell
4348    10  127  16010
./bg.sh: line 29: wait: pid 4349 is not a child of this shell
4349    11  127  8341
./bg.sh: line 29: wait: pid 4351 is not a child of this shell
4351    12  127  13814
./bg.sh: line 29: wait: pid 4353 is not a child of this shell
4353    13  127  3775
./bg.sh: line 29: wait: pid 4355 is not a child of this shell
4355    14  127  2309
./bg.sh: line 29: wait: pid 4357 is not a child of this shell
4357    15  127  32203
./bg.sh: line 29: wait: pid 4359 is not a child of this shell
4359    16  127  5907
./bg.sh: line 29: wait: pid 4361 is not a child of this shell
4361    17  127  31849
./bg.sh: line 29: wait: pid 4363 is not a child of this shell
4363    18  127  28920
4365    19  10   28810

is not a child of this shellコマンドの出力は完全に検索されますが、このエラーがどこで発生するのか理解できません。wait最後のコマンドの終了コードを非同期的に実行するには、私が何か間違っているようです。

このエラーがどこで発生するのかを知っている人はいますか?この問題に対する私の解決策に欠陥がありますか?それともbashの動作を誤解していますか?理解しにくい行動wait

PS:この質問をSuper Userに投稿しましたが、もう一度考えてみると、Unix&Linux Stack Exchangeに適している可能性があります。

ベストアンサー1

これはバグ/制限事項です。 bashは、値が$!他の変数に格納されているかどうかにかかわらず、最後のプロセスが置き換えられるまで待つことを許可します。

より簡単なテストケース:

$ cat script
exec 7< <(sleep .2); pid7=$!
exec 8< <(sleep .2); pid8=$!
echo $pid7 $pid8
echo $(pgrep -P $$)
wait $pid7
wait $pid8

$ bash script
6030 6031
6030 6031
/tmp/sho: line 9: wait: pid 6030 is not a child of this shell

pgrep -P実際にそれが殻の子であることを発見し、実際にそれを収穫していることstraceを示したにもかかわらず。bash

ただし、とにかく$!PIDを最後のプロセス交換に設定することは文書化されていない機能であり(以前のバージョンでは使用されていません)、いくつかの制限があります。


これは、bashが変数last_procsub_childの最後のプロセス置換のみを追跡するために発生します。waitpidを探す場所は次のとおりです。

-- jobs.c --
/* Return the pipeline that PID belongs to.  Note that the pipeline
   doesn't have to belong to a job.  Must be called with SIGCHLD blocked.
   If JOBP is non-null, return the index of the job containing PID.  */
static PROCESS *
find_pipeline (pid, alive_only, jobp)
     pid_t pid;
     int alive_only;
     int *jobp;         /* index into jobs list or NO_JOB */
{
     ...
  /* Now look in the last process substitution pipeline, since that sets $! */
  if (last_procsub_child)
    {

ただし、新しいproc substが作成されると破棄されます。

-- subst.c --
static char *
process_substitute (string, open_for_read_in_child)
     char *string;
     int open_for_read_in_child;
{
   ...
      if (last_procsub_child)
        discard_last_procsub_child ();

おすすめ記事