การฆ่าเชลล์สคริปต์ทำงานในพื้นหลัง


12

ฉันได้เขียนเชลล์สคริปต์เพื่อตรวจสอบไดเรกทอรีโดยใช้ยูทิลิตี้ inotifywait ของ inotifyt-tools ฉันต้องการให้สคริปต์ทำงานอย่างต่อเนื่องในพื้นหลัง แต่ฉันยังต้องการที่จะหยุดมันเมื่อต้องการ

เพื่อให้มันทำงานอย่างต่อเนื่องฉันใช้while true; แบบนี้:

while true;
do #a set of commands that use the inotifywait utility
end

ฉันได้บันทึกไว้ในไฟล์/binและทำให้สามารถใช้งานได้ เพื่อให้มันทำงานในพื้นหลังฉันใช้nohup <script-name> &และปิดสถานี

ฉันไม่รู้ว่าฉันจะหยุดสคริปต์นี้ได้อย่างไร ฉันได้ดูคำตอบที่นี่และเป็นคำถามที่เกี่ยวข้องอย่างใกล้ชิดมากที่นี่

อัปเดต 1: บนพื้นฐานของคำตอบของ @InfectedRoot ด้านล่างฉันสามารถแก้ปัญหาของฉันโดยใช้กลยุทธ์ต่อไปนี้ ใช้ครั้งแรก

ps -aux | grep script_name

และใช้sudo kill -9 <pid>เพื่อฆ่ากระบวนการ จากนั้นฉันต้องpgrep inotifywaitและใช้sudo kill -9 <pid>อีกครั้งสำหรับรหัสที่ส่งคืน

ใช้งานได้ แต่ฉันคิดว่านี่เป็นวิธีที่ยุ่งฉันกำลังมองหาคำตอบที่ดีกว่า

UPDATE 2: คำตอบประกอบด้วยฆ่า 2 กระบวนการ สิ่งนี้มีความสำคัญเนื่องจากการรันสคริปต์บนบรรทัดคำสั่งจะเริ่มต้นกระบวนการ 2 กระบวนการคือ 1 ตัวสคริปต์และ 2 ซึ่งเป็นกระบวนการที่ไม่ได้ระบุ



2
การลบ-9ตัวเลือกออกจากkillและใช้ก็killจะทำให้เกิดความยุ่งเหยิง
Sree

1
ไชโย เพื่อให้ได้ '$$' ลงในช่องเงินสด Pun อีกครั้งฉันอยากจะเน้นว่า-9ตัวเลือกจะมีทั้งหมดมากกว่าkillที่นี่ : P
syntaxerror

เพื่อความสมบูรณ์: มีวิธีการที่สะอาดในการฆ่ากระบวนการทั้งสองในคราวเดียว: แทนที่จะฆ่ากระบวนการแยกกันคุณสามารถฆ่ากลุ่มกระบวนการของสคริปต์เพื่อฆ่ามันในคราวเดียว pgid เหมือนกับ pid ของกระบวนการเชลล์สคริปต์และkillคุณใช้คำนำหน้า pgid ด้วยเครื่องหมายลบ: kill -- -<pgid>,, kill -9 -<pgid>และอื่น ๆ
cg909

คำตอบ:


6

เพื่อปรับปรุงใช้killallและรวมคำสั่งด้วย:

ps -aux | grep script_name
killall script_name inotifywait

หรือทำทุกอย่างในบรรทัดเดียว:

killall `ps -aux | grep script_name | grep -v grep | awk '{ print $1 }'` && killall inotifywait

โซลูชันด้านบนของคุณใช้งานได้ แต่ไม่ใช่คำสั่งบรรทัดเดียว โปรดตรวจสอบได้ ฉันได้รับข้อผิดพลาด: ไม่มีกระบวนการ
light94

@ light94 Error: no processควรหมายความว่าคุณได้ฆ่ามันไปแล้ว คุณสามารถทดสอบได้โดยเปิดโปรแกรมอื่นสองโปรแกรมเช่นVLC & Geanyและลองกับพวกเขาแทน
cremefraiche

เฮ้ @cremefraiche ตามที่ earler กล่าวว่าโค้ดของคุณใช้งานได้ แต่ฉันได้ค้นหาวิธีแก้ปัญหาแบบอื่นและวิธีแก้ปัญหาที่พบบ่อยที่สุดที่ฉันเห็นคือใช้ kill <pid> เนื่องจากสคริปต์ของฉันไม่ได้สิ้นสุดในลักษณะนั้นฉันกังวลเล็กน้อยหากรหัสของฉันไม่เหมาะสมหรือไม่ คุณช่วยแนะนำฉันได้ไหม
light94

คุณสามารถประหยัดgrep -v grepด้วยการเปลี่ยนเข้าสู่grep script_name grep -e "[s]cript_name"
nmichaels

3

แสดงรายการงานพื้นหลังที่ใช้

# jobs

จากนั้นเลือกจำนวนงานก่อนหน้าและเรียกใช้

ตัวอย่าง

# fg 1 

ป้อนคำอธิบายรูปภาพที่นี่

จะนำไปสู่เบื้องหน้า

จากนั้นฆ่าโดยใช้ CTRL + C หรือวิธีที่ง่ายกว่าในการค้นหา PID ของสคริปต์ที่ใช้

ps -aux | grep script_name

ป้อนคำอธิบายรูปภาพที่นี่

จากนั้นฆ่าโดยใช้ pid

sudo kill -9 pid_number_here

2
ตัวเลือกแรกไม่ถูกต้องสำหรับเคสของฉันเพราะฉันปิดเชลล์หลังจากระบุสคริปต์และฉันคิดว่าคำสั่ง jobs ใช้งานได้กับเชลล์ตัวเดียวกันเท่านั้น นอกจากนี้ฉันลองตัวเลือกที่สองและมันจะฆ่ากระบวนการ แต่เมื่อฉันทำ pgrep ไม่ทราบว่าฉันยังคงเห็นว่ามันเป็นกระบวนการที่นั่น
light94

งานจะให้รายการของงานแม้ว่าคุณจะปิดเชลล์ กระบวนการยังคงได้รับมันจะมี
Babin Lonston

2
ฉันลองแล้ว แต่มันก็ไม่ได้
light94

@ light94 คุณพูดถูก มันเกิดขึ้นบ่อยครั้งที่jobsคำสั่งนั้นแสดงผลงานที่รันอยู่ในเชลล์เท่านั้น เมื่อฉันปิดหน้าต่างเทอร์มินัลวิธีเดียวที่จะเข้าถึงพวกเขาคือผ่านps(ดูด้านบนสำหรับสิ่งนั้น) ดังนั้นแน่นอนว่าคุณไม่ได้ทำ
ไวยากรณ์

2

คุณสามารถใช้ps+ grepหรือpgrepเพื่อรับชื่อกระบวนการ/ pid ; ใช้ภายหลังkillall/ pkillเพื่อฆ่าชื่อกระบวนการหรือใช้killเพื่อฆ่า pid ทั้งหมดต่อไปนี้ควรทำงาน

killall $(ps aux | grep script_name | grep -v grep | awk '{ print $1 }') && killall inotifywait
(ps -ef | grep script_name | grep -v grep | awk '{ print $1 }' | xargs killall) && killall inotifywait
(ps -ef | grep script_name | grep -v grep | awk '{ print $2 }' | xargs kill) && killall inotifywait
(pgrep -x script_name | xargs kill) && pkill -x inotifywait
pkill -x script_name && pkill -x inotifywait

สิ่งที่สำคัญที่สุดคือคุณควรทำให้แน่ใจว่าคุณฆ่ากระบวนการที่แน่นอนที่คุณหวังจะฆ่าเท่านั้น

pkill/ pgrepจับคู่รูปแบบแทนที่จะเป็นชื่อที่ถูกต้องดังนั้นจึงเป็นอันตราย ที่นี่-xจะถูกเพิ่มเพื่อให้ตรงกับชื่อที่แน่นอน

นอกจากนี้เมื่อใช้pgrep/ pkillคุณอาจต้อง

  • -fเพื่อให้ตรงกับบรรทัดคำสั่งแบบเต็ม (เช่นเดียวกับps aux)
  • -a เพื่อพิมพ์ชื่อกระบวนการ

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

script_name ทำงานอยู่หรือไม่ มันใช้งานได้สำหรับฉัน (เดเบียนเจสซี)
Hongxu Chen

ใช่มันกำลังทำงานอยู่ และโซลูชันของคุณเกือบจะเหมือนกับ @cremefraiche โซลูชันด้านบนดังนั้นฉันคาดว่ามันจะทำงานในลักษณะเดียวกัน: /
light94

ใช่ฉันค่อนข้างสรุปวิธีการที่เป็นไปได้ที่จะทำโดยเฉพาะอย่างยิ่งการเพิ่ม/pgrep pkillหากยังคงผิดพลาดคุณอาจลองpgrepโดยไม่ใช้ -x โดยทั่วไปฉันไม่คิดว่าเป็นการดีที่จะฆ่ากระบวนการภายในหนึ่งบรรทัดคำสั่งโดยไม่ตรวจสอบว่ากระบวนการอื่นตรงกันหรือไม่
Hongxu Chen

1

อย่างที่คุณอาจบอกได้ว่ามีหลายวิธีในการทำเช่นนี้

เกี่ยวกับ "UPDATE # 2" ของคุณ - โดยทั่วไปการพูดการยุติกระบวนการใด ๆ ในลำดับชั้นของแม่ลูกจะโดยทั่วไปจะยุติกระบวนการที่เกี่ยวข้องทั้งหมด แต่มีข้อยกเว้นมากมายในเรื่องนี้ เป็นการดีที่คุณต้องการยุติ 'เด็ก' คนสุดท้ายในแผนผังกระบวนการจากนั้นผู้ปกครองของเด็กคนนี้ควรออกหากไม่มีงานอื่นให้ทำงาน แต่ถ้าคุณฆ่าผู้ปกครองสัญญาณควรถูกถ่ายทอดไปยังเด็ก ๆ เมื่อผู้ปกครองตายและเด็กควรออกเช่นกัน - แต่มีบางกรณีที่กระบวนการเด็กอาจเพิกเฉยต่อสัญญาณ (ผ่านกับดักหรือกลไกที่คล้ายกัน) และอาจดำเนินต่อไป เพื่อเรียกใช้จะได้รับการสืบทอดโดยกระบวนการ 'init' (หรือคล้ายกัน) แต่เรื่องของพฤติกรรมกระบวนการนี้อาจซับซ้อนและฉันจะปล่อยไว้ที่นั่น ...

วิธีหนึ่งที่ฉันชอบถ้าฉันไม่ต้องการใช้สคริปต์ควบคุม (อธิบายต่อไป) คือใช้ยูทิลิตี้ 'หน้าจอ' เพื่อเริ่มและจัดการกระบวนการ คำสั่ง 'หน้าจอ' เต็มไปด้วยคุณสมบัติและอาจใช้เวลาพอสมควร ฉันขอแนะนำให้คุณอ่านหน้า 'หน้าจอ' สำหรับคำอธิบายแบบเต็ม ตัวอย่างรวดเร็วในการเริ่มต้นกระบวนการในพื้นหลังจะเป็นคำสั่ง:

screen -d -m / path / to / program

นี่จะเป็นการเริ่ม "/ path / to / program" ภายในเซสชั่น 'screen'

คุณสามารถเห็นเซสชั่นการทำงานของคุณด้วยคำสั่ง:

หน้าจอ -ls

และเมื่อใดก็ตามที่คุณสามารถเชื่อมต่อกับโปรแกรมที่กำลังทำงานอีกครั้งด้วยคำสั่ง:

หน้าจอ -r

แล้วก็ตัดมันด้วย ^ C หรืออะไรก็ตาม

นอกจากความสวยงามของความสามารถในการเชื่อมต่อและตัดการเชื่อมต่อจากกระบวนการของคุณตามต้องการคือ 'หน้าจอ' จะจับ stdout ใด ๆ () ที่โปรแกรมของคุณอาจสร้างขึ้น


แต่ความชอบส่วนตัวของฉันในเรื่องเหล่านี้คือการมีโปรแกรมควบคุมที่จัดการการเริ่มต้นและการหยุดกระบวนการ สิ่งนี้สามารถค่อนข้างซับซ้อนและต้องใช้สคริปต์ที่ซับซ้อนบางเนื้อหา และเช่นเดียวกับสคริปต์อื่น ๆ มีหลายวิธีที่ดีที่จะทำ ฉันได้รวมตัวอย่าง bash ของวิธีที่ฉันใช้เป็นประจำเพื่อเริ่มและหยุดแอปพลิเคชัน หากงานของคุณง่ายคุณสามารถแทรกลงในสคริปต์ควบคุมโดยตรงหรือคุณอาจให้สคริปต์ควบคุมนี้เรียกโปรแกรมภายนอกอื่น โปรดทราบว่าตัวอย่างนี้ไม่ครอบคลุมในแง่ของการจัดการกระบวนการ ฉันได้ละทิ้งความเป็นไปได้ของสถานการณ์ต่างๆเช่น: ตรวจสอบให้แน่ใจว่าสคริปต์ไม่ได้ทำงานอยู่เมื่อคุณใช้ตัวเลือก "เริ่มต้น" ตรวจสอบว่า PID ที่ทำงานอยู่เป็นกระบวนการที่คุณเริ่มต้นจริง ๆ (เช่นสคริปต์ของคุณไม่มี) ไม่ตายและกระบวนการอื่นเริ่มต้นด้วย PID เดียวกันและตรวจสอบว่าสคริปต์ตอบกลับ (ออก) จริง ๆ ในคำขอ 'ฆ่า' ครั้งแรก การตรวจสอบทั้งหมดนี้มีความซับซ้อนและฉันไม่ต้องการให้ตัวอย่างยาวเกินไปและซับซ้อน คุณอาจต้องการแก้ไขตัวอย่างเพื่อฝึกเชลล์สคริปต์ของคุณ

บันทึกรหัสต่อไปนี้ไปยังไฟล์ชื่อ "programctl" ทำให้สามารถเรียกใช้งานได้ด้วยคำสั่ง:

โปรแกรม chmod 755

จากนั้นแก้ไขไฟล์และเพิ่มรหัส / สคริปต์ของคุณในส่วนกรณีที่ขึ้นต้นด้วย "myscript"

เมื่อทุกอย่างเข้าที่สมมติว่า "programctl" อยู่ในไดเรกทอรีปัจจุบันคุณสามารถเริ่มต้นโปรแกรมด้วย:

./programctl เริ่มต้น

และหยุดมันด้วย:

./programctl หยุด

ไชโย

#!/bin/bash
# Description:  A wrapper script used to stop/start another script.

#--------------------------------------
# Define Global Environment Settings:
#--------------------------------------

# Name and location of a persistent PID file

PIDFILE="/tmp/tmpfile-$LOGNAME.txt"

#--------------------------------------
# Check command line option and run...
# Note that "myscript" should not
# provided by the user.
#--------------------------------------

case $1
in
    myscript)
        # This is where your script would go.
        # If this is a routine 'bash' shell script, you can enter
        # the script below as illustrated in the example.  
        # Or you could simply provide the path and parameters
        # to another script such as /dir/name/command -options

        # Example of an embedded script:

        while true
        do
            # do something over and over...
            sleep 1
        done

        # Example of an external script:

        /usr/local/bin/longrun -x
    ;;

    start)
        # Start your script in the background.
        # (Note that this is a recursive call to the wrapper
        #  itself that effectively runs your script located above.)
        $0 myscript &

        # Save the backgound job process number into a file.
        jobs -p > $PIDFILE

        # Disconnect the job from this shell.
        # (Note that 'disown' command is only in the 'bash' shell.)
        disown %1

        # Print a message indicating the script has been started
        echo "Script has been started..."
    ;;

    stop)
        # Read the process number into the variable called PID
        read PID < $PIDFILE

        # Remove the PIDFILE
        rm -f $PIDFILE

        # Send a 'terminate' signal to process
        kill $PID

        # Print a message indicating the script has been stopped
        echo "Script has been stopped..."
    ;;

    *)
        # Print a "usage" message in case no arguments are supplied
        echo "Usage: $0 start | stop"
    ;;
esac

ฉันลองใช้วิธี "หน้าจอ" ที่คุณแนะนำและใช้งานได้อย่างสวยงาม ฉันยังไม่ได้ลองวิธีที่สอง ขอบคุณสำหรับคำตอบ
light94
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.