여러 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
.
추신: 이 질문을 Super User에 게시했지만 다시 생각해보면 Unix & Linux Stack Exchange에 더 적합할 수도 있습니다.
답변1
이것은 버그/제한 사항입니다. bash는 's 값을 $!
다른 변수에 저장하는지 여부에 관계없이 마지막 프로세스가 교체될 때까지만 기다리는 것을 허용합니다.
더 간단한 테스트 사례:
$ 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
변수의 마지막 프로세스 대체만 추적하기 때문에 발생합니다. wait
pid를 찾는 곳 은 다음과 같습니다 .
-- 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 ();
답변2
이것이 내가 생각해 낸 것입니다.
run
먼저, 귀하의 경우에는 완전히 다른 더미 스크립트입니다.
#!/bin/bash
sleep $1;
exit $2
다음으로 스크립트는 작업을 백그라운드에 bg
배치 하고 적절하게 리디렉션합니다.run
#!/bin/bash
echo $$
( ( touch $$.running; "$@" > $$.out 2>$$.err ; echo $? > $$.exitcode ) & )
마지막으로 driver
모든 것을 제어하는 스크립트입니다. 이것은 물론 다른 두 스크립트가 아니라 실제로 실행할 스크립트입니다. 내부 의견이 도움이 될 것이지만 테스트해 본 결과 제대로 작동하는 것 같습니다.
#!/bin/bash
# first run all commands via "bg"
./bg ./run 10 0
./bg ./run 5 5
./bg ./run 2 2
./bg ./run 0 0
# ... and so on
while :
do
shopt -s nullglob
for i in *.exitcode
do
j=$(basename $i .exitcode)
# now process $j.out, $j.err, $j.exitcode however you want; most
# importantly, *move* at least the exitcode file out of this directory
echo $j had exit code of `cat $i`
rm $j.*
done
shopt -u nullglob
ls *.running >/dev/null 2>&1 || exit
sleep 1
done