Tại sao tôi không thể giết thời gian chờ được gọi từ tập lệnh Bash bằng tổ hợp phím?


11

[Chỉnh sửa: Điều này trông tương tự như một số câu hỏi khác hỏi làm thế nào để giết tất cả các quá trình sinh ra - tất cả các câu trả lời dường như là sử dụng pkill. Vì vậy, cốt lõi của câu hỏi của tôi có thể là: Có cách nào để truyền Ctrl-C / Z cho tất cả các quy trình được sinh ra bởi một tập lệnh không?]

Khi gọi một SoX recbằng timeoutlệnh từ coreutils (được thảo luận ở đây ), dường như không có cách nào để giết nó bằng tổ hợp phím một khi nó được gọi từ trong tập lệnh Bash.

Ví dụ:

timeout 10 rec test.wav

... có thể bị giết bằng Ctrl+ Choặc Ctrl+ Ztừ bash, nhưng không được gọi từ bên trong tập lệnh.

timeout 10 ping nowhere

... có thể bị giết bằng Ctrl+ Choặc Ctrl+ Ztừ bash và với Ctrl+ Zkhi nó chạy từ bên trong một tập lệnh.

Tôi có thể tìm ID tiến trình và giết nó theo cách đó, nhưng tại sao tôi không thể sử dụng tổ hợp phím tắt tiêu chuẩn? Và có cách nào để cấu trúc kịch bản của tôi để tôi có thể?


2
Ctrl + Z không giết tiến trình, chỉ tạm dừng chúng. Họ sẽ tiếp tục chạy nếu bạn cung cấp cho bgcác fglệnh. Dù sao, có một sự khác biệt giữa các ví dụ 1 và 3d của bạn?
terdon

Tôi không có timeouttrên hệ thống của mình nhưng giết chết sleepcác công việc cho dù nó được gõ trực tiếp trên dòng lệnh, có nguồn gốc, được thực thi hoặc được chuyển qua trình thông dịch một cách rõ ràng
Kevin

@terdon Cảm ơn, tôi đã làm rõ các ví dụ.
cuộc họp

Câu trả lời:


18

Các khóa tín hiệu như Ctrl+ Cgửi tín hiệu đến tất cả các quy trình trong nhóm quy trình nền trước .

Trong trường hợp điển hình, một nhóm quy trình là một đường ống dẫn. Ví dụ, trong head <somefile | sort, quá trình đang chạy headvà quá trình đang chạy sortnằm trong cùng một nhóm quy trình, như là trình bao, vì vậy tất cả chúng đều nhận được tín hiệu. Khi bạn chạy một công việc trong nền ( somecommand &), công việc đó nằm trong nhóm quy trình riêng của nó, vì vậy nhấn Ctrl+ Ckhông ảnh hưởng đến nó.

Các timeoutchương trình tự đặt mình trong nhóm quá trình riêng của mình. Từ mã nguồn:

/* Ensure we're in our own group so all subprocesses can be killed.
   Note we don't just put the child in a separate group as
   then we would need to worry about foreground and background groups
   and propagating signals between them.  */
setpgid (0, 0);

Khi thời gian chờ xảy ra, timeouttrải qua quá trình đơn giản là giết chết nhóm quy trình mà nó là thành viên. Vì nó đã đặt mình vào một nhóm quy trình riêng biệt, quy trình mẹ của nó sẽ không nằm trong nhóm. Sử dụng một nhóm quy trình ở đây đảm bảo rằng nếu ứng dụng con chuyển thành nhiều quy trình, tất cả các quy trình của nó sẽ nhận được tín hiệu.

Khi bạn chạy timeouttrực tiếp trên dòng lệnh và nhấn Ctrl+ C, SIGINT kết quả sẽ được nhận cả bởi timeoutvà bởi quy trình con, nhưng không phải bằng vỏ tương tác, đó là timeoutquy trình cha mẹ. Khi timeoutđược gọi từ một tập lệnh, chỉ vỏ chạy tập lệnh mới nhận được tín hiệu: timeoutkhông nhận được tập lệnh vì nó nằm trong một nhóm quy trình khác.

Bạn có thể đặt trình xử lý tín hiệu trong tập lệnh shell với trapnội trang. Thật không may, nó không đơn giản. Xem xét điều này:

#!/bin/sh
trap 'echo Interrupted at $(date)' INT
date
timeout 5 sleep 10
date

Nếu bạn nhấn Ctrl+ Csau 2 giây, thao tác này vẫn đợi đủ 5 giây, sau đó in thông báo gián đoạn. Đó là bởi vì shell không thể chạy mã bẫy trong khi công việc nền trước đang hoạt động.

Để khắc phục điều này, hãy chạy công việc trong nền. Trong bộ xử lý tín hiệu, gọi killđể chuyển tiếp tín hiệu đến timeoutnhóm quy trình.

#!/bin/sh
trap 'kill -INT -$pid' INT
timeout 5 sleep 10 &
pid=$!
wait $pid

Rất lén lút - làm việc rất đẹp! Bây giờ tôi thông minh hơn nhiều, merci!
cuộc họp

13

Xây dựng trên câu trả lời tuyệt vời được cung cấp bởi Gilles. Lệnh hết thời gian có tùy chọn tiền cảnh, nếu được sử dụng thì CTRL + C sẽ thoát lệnh hết thời gian.

#!/bin/sh
trap 'echo caught interrupt and exiting;exit' INT
date
timeout --foreground 5 sleep 10
date

Đây là một câu trả lời tuyệt vời - đơn giản hơn câu trả lời được chấp nhận và hoạt động tốt với tôi, sử dụng timeouttừ GNU coreutils.
RichVel

1

Về cơ bản Ctrl+ Cgửi SIGINTtín hiệu, trong khi Ctrl+ Z gửi SIGTSTPtín hiệu.

SIGTSTPChỉ cần dừng quá trình, SIGCONTsẽ tiếp tục nó.

Điều này hoạt động trên quá trình tiền cảnh được rẽ nhánh trên dòng lệnh.

Nếu quy trình của bạn là một quy trình nền, bạn sẽ phải gửi tín hiệu đó đến các quy trình theo một cách khác. killsẽ làm điều đó. Về lý thuyết, một toán tử "-" - trên lệnh giết đó cũng sẽ báo hiệu các tiến trình con nhưng điều này hiếm khi hoạt động như mong đợi.

Để đọc thêm: Unix-Signals


Điều này là đúng, ít nhất là lên đến mức hiếm khi hoạt động như mong đợi (điều này phụ thuộc vào sự mong đợi của bạn - bạn nên đọc về các nhóm quy trình), nhưng hoàn toàn không liên quan. Câu hỏi không phản bội bất kỳ sự nhầm lẫn nào về SIGINT và SIGTSTP.
Gilles 'SO- ngừng trở nên xấu xa'

0

Cải thiện câu trả lời của @Gilles bằng cách cung cấp phương pháp "tìm và thay thế" cho các tập lệnh hiện có:

  1. Thêm đoạn mã này vào đầu mã của bạn:
declare -a timeout_pids
exec 21>&1; exec 22>&2 # backup file descriptors, see /superuser//a/1446738/187576
my_timeout(){
    local args tp ret
    args="$@"
    timeout $args &
    tp=$!
    echo "pid of timeout: $tp"
    timeout_pids+=($tp)
    wait $tp
    ret=$?
    count=${#timeout_pids[@]}
    for ((i = 0; i < count; i++)); do
        if [ "${timeout_pids[i]}" = "$tp" ] ; then
            unset 'timeout_pids[i]'
        fi
    done
    return $ret
}
  1. Thêm (hoặc hợp nhất) mã sau vào INTtrình xử lý của bạn :
pre_cleanup(){
    exec 1>&21; exec 2>&22 # restore file descriptors, see /superuser//a/1446738/187576
    echo "Executing pre-cleanup..."
    for i in "${timeout_pids[*]}"; do
        if [[ ! -z $i ]]; then
            #echo "Killing PID: $i"
            kill -INT -$i 2> /dev/null
        fi
    done
    exit
}

trap pre_cleanup INT
  1. Thay thế timeoutcác lệnh với my_timeoutchức năng trong kịch bản của bạn.

Thí dụ

Đây là một kịch bản ví dụ:

#!/bin/bash

# see "killing timeout": /unix//a/57692/65781
declare -a timeout_pids
exec 21>&1; exec 22>&2 # backup file descriptors, see /superuser//a/1446738/187576
my_timeout(){
    local args tp ret
    args="$@"
    timeout $args &
    tp=$!
    echo "pid of timeout: $tp"
    timeout_pids+=($tp)
    wait $tp
    ret=$?
    count=${#timeout_pids[@]}
    for ((i = 0; i < count; i++)); do
        if [ "${timeout_pids[i]}" = "$tp" ] ; then
            unset 'timeout_pids[i]'
        fi
    done
    return $ret
}

cleanup(){
    echo "-----------------------------------------"
    echo "Restoring previous routing table settings"
}

pre_cleanup(){
    exec 1>&21; exec 2>&22 # restore file descriptors, see /superuser//a/1446738/187576
    echo "Executing pre-cleanup..."
    for i in "${timeout_pids[*]}"; do
        if [[ ! -z $i ]]; then
            echo "Killing PID: $i"
            kill -INT -$i 2> /dev/null
        fi
    done
    exit
}

trap pre_cleanup INT
trap cleanup EXIT

echo "Executing 5 temporary timeouts..."
unreachable_ip="192.168.44.5"
my_timeout 1s ping -c 1 "$unreachable_ip" &> /dev/null 
my_timeout 1s ping -c 1 "$unreachable_ip" &> /dev/null 
my_timeout 1s ping -c 1 "$unreachable_ip" &> /dev/null 
my_timeout 1s ping -c 1 "$unreachable_ip" &> /dev/null 
my_timeout 1s ping -c 1 "$unreachable_ip" &> /dev/null 
echo "ctrl+c now to execute cleanup"
my_timeout 9s ping -c 1 "$unreachable_ip" &> /dev/null 
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.