mã trả lại đáng tin cậy của quá trình nền


12

Giả sử đoạn mã bash sau đây:

foo > logfile 2>&1 &
foo_pid=$!

while ps -p$foo_pid
do
    ping -c 1 localhost
done

wait $foo_pid

if [[ $? == 0 ]]
then
    echo "foo success"
fi

Có an toàn không khi cho rằng $?thực sự có chứa mã trả về foovà không phải mã trả về ping? Nếu câu trả lời cho câu hỏi đó là: "Bạn không thể cho rằng." sau đó làm thế nào tôi có thể sửa đổi đoạn mã này để chắc chắn rằng $?luôn chứa mã trả về foo?

Câu trả lời:


12

Với bash, bạn sẽ có sự đảm bảo đó trừ khi bạn bắt đầu một công việc nền khác (và hãy cẩn thận rằng các công việc nền có thể được bắt đầu với& mà còn với coprocvà thay thế quy trình) giữa foo &wait.

POSIX yêu cầu shell phải ghi nhớ trạng thái thoát của ít nhất 25 công việc sau khi chúng biến mất , nhưng còn bashnhớ nhiều hơn thế.

Bây giờ, nếu bạn làm:

foo & pid=$!
...
bar &
wait "$pid"

Bạn không có gì đảm bảo rằng barsẽ không được trả tiền giống như foo(nếu foođã chấm dứt theo thời gianbar bắt đầu), do đó, mặc dù điều đó không thể xảy ra, wait "$pid"có thể cung cấp cho bạn trạng thái thoát bar.

Bạn có thể sao chép nó bằng:

bash -c '(exit 12; foo) & pid=$!
         while : bar & [ "$pid" != "$!" ]; do :;done
         wait "$pid"; echo "$?"'

mà cuối cùng sẽ cung cấp cho bạn 0 thay vì 12.

Để tránh vấn đề, một cách sẽ là viết nó như sau:

{
  foo_pid=$!

  while ps -p "$foo_pid"
  do
      ping -c 1 localhost
  done

  bar &
  ...

  read <&3 ret
  if [ "$ret" = 0 ]; then
    echo foo was sucessful.
  fi
} 3< <(foo > logfile 2>&1; echo "$?")

4

Có, bạn có thể dựa vào wait "$!"để có được trạng thái của một công việc nền. Khi chạy dưới dạng tập lệnh, bash không tự động thu thập các công việc nền đã hoàn thành. Do đó, nếu bạn chạy wait, nó sẽ thu thập công việc tại thời điểm waitđược gọi.

Bạn có thể kiểm tra điều này với một tập lệnh đơn giản:

#!/bin/bash
sh -c 'sleep 1; exit 22' &
sleep 5
echo "FG: $?"
wait %1
echo "BG: $?"

Sẽ xuất ra:

FG: 0
BG: 22

Phần quan trọng của tuyên bố đó là phần mở đầu, "khi chạy dưới dạng tập lệnh". Khi tương tác, waitkhông hoạt động. Quá trình được thu thập và trạng thái thoát bị loại bỏ ngay trước khi dấu nhắc được hiển thị (theo mặc định).
Patrick

Tôi vừa thử nó trên bash 4.2.37, 4.1.2 và 3.2.48. Tất cả chúng hoạt động giống hệt nhau (bản sao / dán mã theo câu trả lời của tôi). Các wait %1thất bại với "không có việc làm như vậy" là quá trình nền được thu thập ngay sau khi "ngủ 5" hoàn tất.
Patrick

À, xin lỗi, tôi hiểu rồi. Tôi đã bỏ lỡ %1vị trí của bạn $!.
Stéphane Chazelas

Lưu ý rằng bash -c '(sleep 1;exit 5) & sleep 2; wait %1; echo $?'(vì vậy cũng không tương tác) không nhận được trạng thái thoát của công việc chết đó. Âm thanh như một con bọ.
Stéphane Chazelas

0

Tôi tin rằng giả định của bạn là chính xác. Đây là một trích xuất man bashliên quan đến việc chờ đợi các quá trình nền.

Nếu n chỉ định một quy trình hoặc công việc không tồn tại, trạng thái trả về là 127. Mặt khác, trạng thái trả về là trạng thái thoát của quy trình hoặc công việc cuối cùng được chờ đợi.

Vì vậy, có lẽ bạn nên kiểm tra 127

Có một câu hỏi tương tự với một câu trả lời hoàn toàn khác có thể giúp ích.

Tập lệnh Bash chờ xử lý và nhận mã trả về

chỉnh sửa 1

Lấy cảm hứng từ những bình luận và câu trả lời của @ Stephane, tôi đã mở rộng kịch bản của anh ấy. Tôi có thể bắt đầu khoảng 34 quy trình nền trước khi nó bắt đầu lỏng lẻo.

giật lùi

$ cat tback 
plist=()
elist=()
slist=([1]=12 [2]=15 [3]=17 [4]=19 [5]=21 [6]=23)
count=30

#start background tasksto monitor
for i in 1 2 3 4
do
  #echo pid $i ${plist[$i]} ${slist[$i]}
  (echo $BASHPID-${slist[$i]} running; exit ${slist[$i]}) & 
  plist[$i]=$!
done

echo starting $count background echos to test history
for i in `eval echo {1..$count}`
do
  echo -n "." &
  elist[$i]=$! 
done
# wait for each background echo to complete
for i in `eval echo {1..$count}`
do
  wait ${elist[$i]}
  echo -n $? 
done
echo ""
# Now wait for each monitored process and check return status with expected
failed=0
for i in 1 2 3 4
do
  wait ${plist[$i]}
  rv=$?
  echo " pid ${plist[$i]} returns $rv should be ${slist[$i]}"
  if [[ $rv != ${slist[$i]} ]] 
  then
    failed=1
  fi
done

wait
echo "Complete $failed"
if [[ $failed = "1" ]]
then
  echo Failed
else
  echo Success
fi
exit $failed
$ 

trên hệ thống của tôi sản xuất

$ bash tback
14553-12 running
14554-15 running
14555-17 running
starting 30 background echos to test history
14556-19 running
..............................000000000000000000000000000000
 pid 14553 returns 12 should be 12
 pid 14554 returns 15 should be 15
 pid 14555 returns 17 should be 17
 pid 14556 returns 19 should be 19
Complete 0
Success

1
Không, hãy xem nhận xét của tôi để trả lời câu trả lời và tự mình thửbash -c '(exit 12) & sleep 1; wait "$!"; echo "$?"'
Stéphane Chazelas

Tôi chưa bao giờ thấy bashtheo dõi lỏng lẻo (ngay cả sau khi bắt đầu hàng ngàn công việc), ví dụ của tôi là chứng minh rằng pid được sử dụng lại, đó có thể là những gì bạn quan sát thấy trong trường hợp của bạn.
Stéphane Chazelas
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.