Đợi một quá trình kết thúc


147

Có tính năng dựng sẵn nào trong Bash để chờ quá trình kết thúc không?

Các waitlệnh chỉ cho phép một để chờ tiến trình con đến cuối. Tôi muốn biết liệu có cách nào để chờ bất kỳ quá trình nào kết thúc hay không trước khi tiếp tục bất kỳ kịch bản nào.

Một cách cơ học để làm điều này là như sau nhưng tôi muốn biết liệu có tính năng dựng sẵn nào trong Bash không.

while ps -p `cat $PID_FILE` > /dev/null; do sleep 1; done

4
Hãy để tôi đưa ra hai cảnh báo : 1. Như được chỉ ra dưới đây bởi mp3headsey , "kill -0" không phải lúc nào cũng hoạt động cho POSIX. 2. Có lẽ bạn cũng muốn đảm bảo rằng quy trình không phải là zombie, mà gần như là một quá trình chấm dứt. Xem bình luận của mp3 Scratcheycủa tôi để biết chi tiết.
teika kazura

2
Một lưu ý khác ( ban đầu được chỉ ra bởi ks1322 bên dưới): Sử dụng PID khác với quy trình con không mạnh mẽ. Nếu bạn muốn một cách an toàn, hãy sử dụng IPC.
teika kazura

Câu trả lời:


138

Chờ đợi cho bất kỳ quá trình để kết thúc

Linux:

tail --pid=$pid -f /dev/null

Darwin (yêu cầu $pidcó tệp mở):

lsof -p $pid +r 1 &>/dev/null

Với thời gian chờ (giây)

Linux:

timeout $timeout tail --pid=$pid -f /dev/null

Darwin (yêu cầu $pidcó tệp mở):

lsof -p $pid +r 1m%s -t | grep -qm1 $(date -v+${timeout}S +%s 2>/dev/null || echo INF)

42
Ai có thể biết rằng tailsẽ làm điều này.
ctrl-alt-delor

8
tailhoạt động dưới mui xe bằng cách bỏ phiếu với kill(pid, SIG_0)một quy trình (được phát hiện bằng cách sử dụng strace).
Att Righ

2
Lưu ý rằng lsof sử dụng bỏ phiếu, đó +r 1là thời gian chờ, cá nhân tôi đang tìm kiếm một giải pháp cho MacOS không sử dụng bỏ phiếu.
Alexander Mill

1
Thủ thuật này thất bại cho zombie. Nó ổn cho các quá trình bạn không thể giết; tailcó dòng kill (pid, 0) != 0 && errno != EPERM.
teika kazura

2
@AlexanderMills, nếu bạn có thể chịu đựng được hệ thống macOS của bạn sẽ không ngủ khi lệnh thực thi, caffeinate -w $pidsẽ thực hiện thủ thuật.
zneak

83

Không có nội dung nào. Sử dụng kill -0trong một vòng lặp cho một giải pháp khả thi:

anywait(){

    for pid in "$@"; do
        while kill -0 "$pid"; do
            sleep 0.5
        done
    done
}

Hoặc như một oneliner đơn giản hơn để sử dụng một lần dễ dàng:

while kill -0 PIDS 2> /dev/null; do sleep 1; done;

Theo ghi nhận của một số nhà bình luận, nếu bạn muốn đợi các quy trình mà bạn không có đặc quyền gửi tín hiệu đến, bạn đã tìm một số cách khác để phát hiện nếu quy trình đang chạy để thay thế kill -0 $pidcuộc gọi. Trên Linux, test -d "/proc/$pid"hoạt động, trên các hệ thống khác mà bạn có thể phải sử dụng pgrep(nếu có) hoặc đại loại như thế ps | grep "^$pid ".


2
Thận trọng : Điều này không phải lúc nào cũng hoạt động, như được chỉ ra dưới đây bởi mp3 Scratchey . Xem bình luận đó và của tôi để biết chi tiết.
teika kazura

2
Thận trọng 2 (Về zombie): Nhận xét tiếp theo của Teddy ở trên vẫn chưa đủ, vì chúng có thể là zombie. Xem câu trả lời của tôi dưới đây cho một giải pháp Linux.
teika kazura

4
Không giải pháp này có nguy cơ một điều kiện chủng tộc? Trong khi ngủ sleep 0.5, quá trình có $pidthể chết và quá trình khác có thể được tạo ra với cùng $pid. Và cuối cùng chúng ta sẽ chờ đợi 2 quá trình khác nhau (hoặc thậm chí nhiều hơn) với cùng một quy trình $pid.
ks1322

2
@ ks1322 Có, mã này thực sự có một điều kiện chủng tộc trong đó.
Teddy

4
Không phải các PID thường được tạo liên tục? Sự giống nhau của số đếm bao quanh trong một giây là gì?
esmirusha

53

Tôi thấy "kill -0" không hoạt động nếu tiến trình được sở hữu bởi root (hoặc khác), vì vậy tôi đã sử dụng pgrep và đưa ra:

while pgrep -u root process_name > /dev/null; do sleep 1; done

Điều này sẽ có nhược điểm là có thể phù hợp với quá trình zombie.


2
Quan sát tốt. Trong POSIX, cuộc gọi hệ thống kill(pid, sig=0)thất bại nếu quá trình người gọi không có đặc quyền để giết. Do đó / bin / kill -0 và "kill -0" (bash tích hợp) không thành công trong cùng điều kiện.
teika kazura

31

Vòng lặp bash script này kết thúc nếu quá trình không tồn tại hoặc đó là zombie.

PID=<pid to watch>
while s=`ps -p $PID -o s=` && [[ "$s" && "$s" != 'Z' ]]; do
    sleep 1
done

EDIT : Kịch bản trên được đưa ra dưới đây bởi Rockallite . Cảm ơn!

Câu trả lời gốc của tôi dưới đây hoạt động cho Linux, dựa vào procfstức là /proc/. Tôi không biết tính di động của nó:

while [[ ( -d /proc/$PID ) && ( -z `grep zombie /proc/$PID/status` ) ]]; do
    sleep 1
done

Nó không giới hạn ở shell, nhưng bản thân OS không có lệnh gọi hệ thống để xem kết thúc quá trình không phải là con.


1
Đẹp một. Mặc dù tôi phải bao quanh grep /proc/$PID/statusvới dấu ngoặc kép ( bash: test: argument expected)
Griddo

Hum ... chỉ cần thử lại và nó đã hoạt động. Tôi đoán lần trước tôi đã làm gì đó sai.
Griddo

7
Hoặcwhile s=`ps -p $PID -o s=` && [[ "$s" && "$s" != 'Z' ]]; do sleep 1; done
Rockallite

1
Thật không may, điều này không hoạt động trong BusyBox - trong đó ps, không -phoặc không s=được hỗ trợ
ZimbiX

14

FreeBSD và Solaris có tiện dụng này pwait(1) ích , chính xác, những gì bạn muốn.

Tôi tin rằng, các hệ điều hành hiện đại khác cũng có các lệnh gọi hệ thống cần thiết (ví dụ MacOS, thực hiện BSD kqueue), nhưng không phải tất cả đều có sẵn từ dòng lệnh.


2
> BSD and Solaris: Kiểm tra ba BSD lớn xuất hiện trong tâm trí; cả OpenBSD và NetBSD đều không có chức năng này (trong trang man của họ), chỉ FreeBSD mới có, vì bạn có thể dễ dàng kiểm tra trên man.openbsd.org .
benaryorg

Có vẻ như bạn đúng. Mea culpa ... Tuy nhiên, tất cả đều thực hiện kqueue, do đó, việc biên dịch FreeBSD pwait(1)sẽ là chuyện nhỏ. Tại sao các tính năng khác của BSD không nhập được tính năng này thoát khỏi tôi ...
Mikhail T.

1
plink me@oracle box -pw redacted "pwait 6998";email -b -s "It's done" etcchỉ cho phép tôi về nhà bây giờ thay vì hàng giờ kể từ bây giờ.
zzxyz

11

Từ trang bash

   wait [n ...]
          Wait for each specified process and return its termination  status
          Each  n  may be a process ID or a job specification; if a
          job spec is given, all processes  in  that  job's  pipeline  are
          waited  for.  If n is not given, all currently active child processes
          are waited for, and the return  status  is  zero.   If  n
          specifies  a  non-existent  process or job, the return status is
          127.  Otherwise, the return status is the  exit  status  of  the
          last process or job waited for.

56
Điều đó đúng nhưng nó chỉ có thể chờ đợi con của lớp vỏ hiện tại. Bạn không thể chờ đợi bất kỳ quá trình.
gumik

@gumik: "Nếu n không được cung cấp, tất cả các quy trình con đang hoạt động đang chờ" . Điều này hoạt động hoàn hảo .. waitkhông có đối số sẽ chặn quá trình cho đến khi bất kỳ quá trình con kết thúc. Thành thật mà nói, tôi không thấy bất kỳ điểm nào để chờ đợi bất kỳ quy trình nào vì luôn có các quy trình hệ thống đang diễn ra.
coderofsalvation

1
@coderofsalvation (ngủ 10 & ngủ 3 & chờ) mất 10 giây để quay lại: chờ mà không có đối số sẽ chặn cho đến khi TẤT CẢ các quá trình con kết thúc. OP muốn được thông báo khi quá trình con đầu tiên (hoặc được đề cử) kết thúc.
android.weasel

Cũng không hoạt động nếu quá trình không được nền hoặc tiền cảnh (trên Solaris, Linux hoặc Cygwin). Ví dụ. sleep 1000 ctrl-z wait [sleep pid]trả lại ngay lập tức
zzxyz

6

Tất cả các giải pháp này đều được thử nghiệm trong Ubuntu 14.04:

Giải pháp 1 (bằng cách sử dụng lệnh ps): Chỉ cần thêm vào câu trả lời của Pierz, tôi sẽ đề xuất:

while ps axg | grep -vw grep | grep -w process_name > /dev/null; do sleep 1; done

Trong trường hợp này, grep -vw grepđảm bảo rằng grep chỉ khớp với process_name và không phải chính grep. Nó có lợi thế là hỗ trợ các trường hợp trong đó process_name không ở cuối dòng tại ps axg.

Giải pháp 2 (bằng cách sử dụng lệnh trên và tên quy trình):

while [[ $(awk '$12=="process_name" {print $0}' <(top -n 1 -b)) ]]; do sleep 1; done

Thay thế process_name bằng tên quy trình xuất hiện trongtop -n 1 -b . Hãy giữ dấu ngoặc kép.

Để xem danh sách các quy trình mà bạn chờ đợi chúng kết thúc, bạn có thể chạy:

while : ; do p=$(awk '$12=="process_name" {print $0}' <(top -n 1 -b)); [[ $b ]] || break; echo $p; sleep 1; done

Giải pháp 3 (bằng cách sử dụng lệnh trên và ID tiến trình):

while [[ $(awk '$1=="process_id" {print $0}' <(top -n 1 -b)) ]]; do sleep 1; done

Thay thế process_idbằng ID tiến trình của chương trình của bạn.


4
Downvote: grep -v grepđường ống dài là một phản hạt lớn và điều này giả định rằng bạn không có các quy trình không liên quan có cùng tên. Nếu bạn biết các PID thay vào đó, điều này có thể được điều chỉnh theo một giải pháp hoạt động đúng.
tripleee

Cảm ơn tripleee cho ý kiến. Tôi đã thêm cờ -wđể tránh vấn đề ở grep -v grep một mức độ nào đó . Tôi cũng đã thêm hai giải pháp dựa trên nhận xét của bạn.
Saeid BK

5

Được rồi, có vẻ như câu trả lời là - không, không có công cụ tích hợp.

Sau khi thiết lập /proc/sys/kernel/yama/ptrace_scopeđể 0, người ta có thể sử dụng stracechương trình. Các công tắc tiếp theo có thể được sử dụng để làm cho nó im lặng, để nó thực sự chờ đợi một cách thụ động:

strace -qqe '' -p <PID>

1
Đẹp quá Dường như không thể gắn vào một PID nhất định từ hai nơi khác nhau (tôi lấy Operation not permittedví dụ về bước thứ hai); bạn có thể xác nhận điều đó?
eudoxos

@eudoxos Vâng, trang web của ptrace nói: (...)"tracee" always means "(one) thread"(và tôi xác nhận lỗi bạn đề cập). Để cho nhiều quá trình chờ theo cách này, bạn phải tạo một chuỗi.
emu

2

Giải pháp chặn

Sử dụng waitvòng lặp để chờ kết thúc tất cả các quy trình:

function anywait()
{

    for pid in "$@"
    do
        wait $pid
        echo "Process $pid terminated"
    done
    echo 'All processes terminated'
}

Chức năng này sẽ thoát ngay lập tức, khi tất cả các quy trình đã được chấm dứt. Đây là giải pháp hiệu quả nhất.

Giải pháp không chặn

Sử dụng kill -0trong một vòng lặp, để chờ kết thúc tất cả các quy trình + làm bất cứ điều gì giữa các kiểm tra:

function anywait_w_status()
{
    for pid in "$@"
    do
        while kill -0 "$pid"
        do
            echo "Process $pid still running..."
            sleep 1
        done
    done
    echo 'All processes terminated'
}

Thời gian phản ứng giảm theo sleepthời gian, vì phải ngăn chặn việc sử dụng CPU cao.

Một cách sử dụng thực tế:

Chờ kết thúc tất cả các quy trình + thông báo cho người dùng về tất cả các PID đang chạy.

function anywait_w_status2()
{
    while true
    do
        alive_pids=()
        for pid in "$@"
        do
            kill -0 "$pid" 2>/dev/null \
                && alive_pids+="$pid "
        done

        if [ ${#alive_pids[@]} -eq 0 ]
        then
            break
        fi

        echo "Process(es) still running... ${alive_pids[@]}"
        sleep 1
    done
    echo 'All processes terminated'
}

Ghi chú

Các hàm này nhận được các PID thông qua các đối số bằng $@mảng BASH.


2

Có cùng một vấn đề, tôi đã giải quyết vấn đề giết quá trình và sau đó chờ cho mỗi quá trình kết thúc bằng hệ thống tệp PROC:

while [ -e /proc/${pid} ]; do sleep 0.1; done

bỏ phiếu là rất tệ, bạn có thể nhận được một chuyến thăm từ cảnh sát :)
Alexander Mills

2

Không có tính năng dựng sẵn để chờ bất kỳ quá trình nào kết thúc.

Bạn có thể gửi kill -0tới bất kỳ PID nào được tìm thấy, vì vậy bạn không bị bối rối bởi zombie và những thứ vẫn sẽ hiển thị ps(trong khi vẫn truy xuất danh sách PID bằng cách sử dụng ps).


1

Sử dụng inotifywait để theo dõi một số tệp bị đóng, khi quá trình của bạn kết thúc. Ví dụ (trên Linux):

yourproc >logfile.log & disown
inotifywait -q -e close logfile.log

-e chỉ định sự kiện phải chờ, -q có nghĩa là đầu ra tối thiểu chỉ khi kết thúc. Trong trường hợp này, nó sẽ là:

logfile.log CLOSE_WRITE,CLOSE

Một lệnh chờ duy nhất có thể được sử dụng để chờ nhiều quá trình:

yourproc1 >logfile1.log & disown
yourproc2 >logfile2.log & disown
yourproc3 >logfile3.log & disown
inotifywait -q -e close logfile1.log logfile2.log logfile3.log

Chuỗi đầu ra của inotifywait sẽ cho bạn biết, quá trình nào đã kết thúc. Điều này chỉ hoạt động với các tệp 'thực', không hoạt động với / Proc /


0

Trên một hệ thống như OSX, bạn có thể không có pgrep, vì vậy bạn có thể dùng thử thẩm định này, khi tìm kiếm các quy trình theo tên:

while ps axg | grep process_name$ > /dev/null; do sleep 1; done

Các $biểu tượng ở phần cuối của tên này để thỏa mãn quá trình trận đấu grep chỉ process_name đến cuối dòng trong ps đầu ra và bản thân nó không.


Kinh khủng: Có thể có nhiều quy trình với tên đó ở đâu đó trong dòng lệnh , bao gồm grep của riêng bạn. Thay vì chuyển hướng đến /dev/null, -qnên được sử dụng với grep. Một ví dụ khác của quy trình có thể đã bắt đầu trong khi vòng lặp của bạn đang ngủ và bạn sẽ không bao giờ biết ...
Mikhail T.

Tôi không chắc tại sao bạn lại chọn câu trả lời này là 'Kinh khủng' - vì một cách tiếp cận tương tự đã được đề xuất bởi những người khác? Trong khi -qđề xuất là hợp lệ, như tôi đã đề cập trong câu trả lời của mình, cụ thể là chấm dứt $có nghĩa là grep sẽ không khớp với tên "ở đâu đó trong dòng lệnh" và cũng không khớp với chính nó. Bạn đã thực sự thử nó trên OSX chưa?
Pierz

0

Giải pháp của Rauno Palosaari cho Timeout in Seconds Darwin, là một giải pháp tuyệt vời cho một hệ điều hành giống như UNIX không có GNU tail(nó không dành riêng cho Darwin). Nhưng, tùy thuộc vào độ tuổi của hệ điều hành giống UNIX, dòng lệnh được cung cấp phức tạp hơn mức cần thiết và có thể thất bại:

lsof -p $pid +r 1m%s -t | grep -qm1 $(date -v+${timeout}S +%s 2>/dev/null || echo INF)

Trên ít nhất một UNIX cũ, lsofđối số +r 1m%skhông thành công (ngay cả đối với siêu người dùng):

lsof: can't read kernel name list.

Đây m%slà một đặc điểm kỹ thuật định dạng đầu ra. Một bộ xử lý hậu đơn giản hơn không yêu cầu nó. Ví dụ: lệnh sau đợi trên PID 5959 trong tối đa năm giây:

lsof -p 5959 +r 1 | awk '/^=/ { if (T++ >= 5) { exit 1 } }'

Trong ví dụ này, nếu PID 5959 thoát ra theo ý mình trước khi hết năm giây, ${?}0. Nếu không ${?}trả lại1 sau năm giây.

Có thể đáng lưu ý rằng trong +r 1, 1khoảng thời gian thăm dò ý kiến ​​(tính bằng giây), vì vậy nó có thể được thay đổi cho phù hợp với tình huống.

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.