杀死后台运行的 shell 脚本

杀死后台运行的 shell 脚本

我编写了一个 shell 脚本来使用 inotifyt-tools 的 inotifywait 实用程序来监视目录。我希望该脚本在后台连续运行,但我也希望能够在需要时停止它。

为了使其连续运行,我使用了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>返回的 id。

这可行,但我认为这是一种混乱的方法,我正在寻找更好的答案。

更新2: 答案包括杀死2个进程。这很重要,因为在命令行上运行脚本会启动 2 个进程,1脚本本身2、inotify进程

答案1

要改进,请使用killall以及组合命令:

ps -aux | grep script_name
killall script_name inotifywait

或者在一行中完成所有操作:

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

答案2

使用列出后台作业

# jobs

然后选择之前的作业号并运行

例子

# fg 1 

在此输入图像描述

将带到前台。

然后使用 CTRL+C 杀死它,或者使用更简单的方法找到脚本的 PID

ps -aux | grep script_name

在此输入图像描述

然后使用pid杀死

sudo kill -9 pid_number_here

答案3

正如您可能知道的那样,有很多方法可以做到这一点。

关于您的“更新#2”——一般来说,终止父子层次结构中的任何进程通常都会终止所有关联的进程。但也有很多例外。理想情况下,您希望终止进程树中的最后一个“子进程”,那么如果该子进程没有其他任务要运行,则该子进程的父进程应该退出。但是,如果您杀死了父进程,则当父进程死亡时,信号应该转发给子进程,并且子进程也应该退出 - 但在某些情况下,子进程可能会忽略该信号(通过陷阱或类似机制)并可能继续运行将由“init”进程(或类似进程)继承。但是进程行为的这个主题可能会变得复杂,我将其留在那里......

如果我不想使用控制脚本(如下所述),我喜欢的一种方法是使用“屏幕”实用程序来启动和管理进程。 “screen”命令功能丰富,可能需要一些时间才能掌握。我鼓励您阅读“屏幕”手册页以获得完整的解释。在后台启动进程的一个简单示例是命令:

屏幕-d -m /路径/到/程序

这将在“屏幕”会话内启动“/path/to/program”。

您可以使用以下命令查看正在运行的会话:

屏幕-ls

您可以随时使用以下命令重新连接到正在运行的程序:

屏幕-r

然后用 ^C 或其他什么方式终止它。

除了能够随意重新连接和断开进程的好处之外,“屏幕”还将捕获程序可能产生的任何 stdout() 。


但我个人在这些问题上的偏好是有一个控制程序来管理进程的启动和停止。这可能会变得有些复杂,并且需要一些可以说是复杂的脚本。与任何脚本一样,有许多好的方法可以实现它。我提供了一个我经常用来启动和停止应用程序的方法的 bash 示例。如果您的任务很简单,您可以将其直接插入到控制脚本中——或者您可以让该控制脚本调用另一个外部程序。请注意,该示例在管理流程方面绝不是全面的。我忽略了以下场景的可能性:确保当您使用“启动”选项时脚本尚未运行,验证正在运行的 PID 实际上是您启动的进程(例如您的脚本尚未终止并且另一个进程已使用相同的 PID 启动),并验证脚本是否在第一个“终止”请求时实际响应(退出)。进行所有这些检查可能会变得复杂,我不想让示例变得太长和复杂。您可能需要修改示例来练习 shell 脚本编写。

将以下代码保存到名为“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

答案4

您可以使用ps+greppgrep来获取进程姓名/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还可以打印进程名称。

相关内容