เหตุใดฉันจึงไม่สามารถฆ่าการหมดเวลาที่เรียกจากสคริปต์ Bash ด้วยการกดแป้นได้


11

[แก้ไข: นี่คล้ายกับคำถามอื่น ๆ ที่ถามว่าจะฆ่ากระบวนการที่เกิดมาทั้งหมดได้อย่างไร - คำตอบทั้งหมดดูเหมือนจะใช้ pkill ดังนั้นหลักของคำถามของฉันอาจเป็น: มีวิธีการเผยแพร่ Ctrl-C / Z ไปยังกระบวนการทั้งหมดที่เกิดจากสคริปต์หรือไม่?]

เมื่อเรียกใช้ SoX recด้วยtimeoutคำสั่งจาก coreutils (กล่าวถึงที่นี่ ) ดูเหมือนจะไม่มีทางที่จะฆ่ามันด้วยการกดแป้นเมื่อมีการเรียกใช้จากภายในสคริปต์ Bash

ตัวอย่าง:

timeout 10 rec test.wav

... สามารถถูกฆ่าด้วยCtrl+ CหรือCtrl+ Zจากการทุบตี แต่ไม่ใช่เมื่อมันถูกเรียกจากภายในสคริปต์

timeout 10 ping nowhere

... สามารถถูกฆ่าด้วยCtrl+ CหรือCtrl+ Zจาก bash และด้วยCtrl+ Zเมื่อมันถูกเรียกใช้จากภายในสคริปต์

ฉันสามารถค้นหารหัสกระบวนการและฆ่าด้วยวิธีนั้น แต่ทำไมฉันไม่สามารถใช้การกดแป้นพิมพ์มาตรฐาน และมีวิธีการจัดโครงสร้างสคริปต์ของฉันเพื่อให้ฉันสามารถ?


2
Ctrl + Z ไม่ได้ฆ่ากระบวนการเพียงหยุดพวกเขาชั่วคราว พวกเขาจะยังคงทำงานถ้าคุณให้คำสั่งbgของ fgอย่างไรก็ตามมีความแตกต่างระหว่างตัวอย่างที่ 1 และ 3d ของคุณหรือไม่
terdon

ฉันไม่ได้มีtimeoutในระบบของฉัน แต่ฆ่าsleepผลงานไม่ว่าจะเป็นการพิมพ์โดยตรงบนบรรทัดคำสั่งให้มาดำเนินการหรือผ่านล่ามอย่างชัดเจน
เควิน

@terdon ขอบคุณฉันได้อธิบายตัวอย่าง
meetar

คำตอบ:


18

ปุ่มสัญญาณเช่นCtrl+ Cส่งสัญญาณไปยังกระบวนการทั้งหมดในเบื้องหน้ากลุ่มกระบวนการ

ในกรณีทั่วไปกลุ่มกระบวนการคือไพพ์ไลน์ ตัวอย่างเช่นในhead <somefile | sortกระบวนการทำงานheadและกระบวนการทำงานsortอยู่ในกลุ่มกระบวนการเดียวกันเช่นเดียวกับเปลือกดังนั้นพวกเขาทั้งหมดได้รับสัญญาณ เมื่อคุณเรียกใช้งานในพื้นหลัง ( somecommand &) งานนั้นอยู่ในกลุ่มกระบวนการของตัวเองดังนั้นการกดCtrl+ Cจะไม่ส่งผลกระทบต่อมัน

timeoutโปรแกรมที่ตัวเองอยู่ในกลุ่มกระบวนการของตัวเอง จากซอร์สโค้ด:

/* 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);

เมื่อการหมดเวลาเกิดขึ้นให้ทำtimeoutตามขั้นตอนง่ายๆในการฆ่ากลุ่มกระบวนการที่เป็นสมาชิก เนื่องจากมันได้ทำให้ตัวเองอยู่ในกลุ่มกระบวนการที่แยกต่างหากกระบวนการแม่ของมันจะไม่อยู่ในกลุ่ม การใช้กลุ่มกระบวนการที่นี่ทำให้แน่ใจได้ว่าหากแอปพลิเคชันลูกแยกออกเป็นหลาย ๆ กระบวนการกระบวนการทั้งหมดจะรับสัญญาณ

เมื่อคุณเรียกใช้timeoutโดยตรงบนบรรทัดคำสั่งและกดCtrl+ CSIGINT ที่ได้รับจะได้รับทั้งจากtimeoutและโดยกระบวนการลูก แต่ไม่ใช่โดยเชลล์เชิงโต้ตอบซึ่งเป็นtimeoutกระบวนการหลักของ เมื่อtimeoutถูกเรียกจากสคริปต์เชลล์ที่รันสคริปต์เท่านั้นที่รับสัญญาณ: timeoutไม่รับเนื่องจากเชลล์อยู่ในกลุ่มกระบวนการที่แตกต่างกัน

คุณสามารถตั้งค่าตัวจัดการสัญญาณในเชลล์สคริปต์ด้วยtrapbuiltin น่าเสียดายที่มันไม่ง่ายอย่างนั้น พิจารณาสิ่งนี้:

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

หากคุณกดCtrl+ Cหลังจาก 2 วินาทีจะยังคงรอ 5 วินาทีเต็มจากนั้นพิมพ์ข้อความ“ ขัดจังหวะ” นั่นเป็นเพราะเชลล์ละเว้นการเรียกใช้โค้ดแทร็บในขณะที่งานพื้นหน้าแอ็คทีฟ

เพื่อแก้ไขปัญหานี้ให้รันงานในพื้นหลัง ในตัวจัดการสัญญาณโทรkillเพื่อถ่ายทอดสัญญาณไปยังtimeoutกลุ่มกระบวนการ

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

ส่อเสียดมาก - ทำงานอย่างสวยงาม! ตอนนี้ข้าฉลาดขึ้นมาก!
meetar

13

การสร้างคำตอบที่ยอดเยี่ยมจาก Gilles คำสั่ง timeout มีตัวเลือกที่ด้านหน้าหากใช้ CTRL + C จะออกจากคำสั่ง timeout

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

นี่เป็นคำตอบที่ดี - ง่ายกว่าคำตอบที่ยอมรับและทำงานได้ดีสำหรับฉันโดยใช้timeoutจาก coreutils ของ GNU
RichVel

1

โดยทั่วไปCtrl+ Cส่งSIGINTสัญญาณในขณะที่Ctrl+ Z ส่งSIGTSTPสัญญาณ

SIGTSTPเพียงแค่หยุดกระบวนการ a SIGCONTจะดำเนินการต่อ

สิ่งนี้ใช้ได้กับกระบวนการทำงานเบื้องหน้าที่แยกจากกันบนบรรทัดคำสั่ง

หากกระบวนการของคุณเป็นกระบวนการแบบเบื้องหลังคุณจะต้องส่งสัญญาณนั้นไปยังกระบวนการด้วยวิธีอื่น killจะทำเช่นนั้น ในทางทฤษฎีแล้วตัวดำเนินการ "-" - ในการฆ่านั้นควรส่งสัญญาณกระบวนการของเด็กด้วยเช่นกัน

สำหรับการอ่านเพิ่มเติม: Unix-Signals


สิ่งนี้เป็นจริงอย่างน้อยที่สุดถึง“ ไม่ค่อยได้ผลเท่าที่ควร” (ขึ้นอยู่กับความคาดหวังของคุณ - คุณควรอ่านข้อมูลเกี่ยวกับกลุ่มกระบวนการ) แต่ไม่เกี่ยวข้องอย่างสมบูรณ์ คำถามไม่ได้ทรยศความสับสนเกี่ยวกับ SIGINT และ SIGTSTP
Gilles 'SO- หยุดความชั่วร้าย'

0

การปรับปรุงคำตอบ @Gillesโดยการให้แนวทาง "ค้นหาและแทนที่" สำหรับสคริปต์ที่มีอยู่:

  1. เพิ่มตัวอย่างนี้ในตอนต้นของรหัสของคุณ:
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. เพิ่ม (หรือผสาน) โค้ดต่อไปนี้ลงในINTตัวจัดการของคุณ:
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. แทนที่timeoutคำสั่งด้วยmy_timeoutฟังก์ชันในสคริปต์ของคุณ

ตัวอย่าง

นี่คือตัวอย่างสคริปต์:

#!/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 
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.