如何使用 nohup 将输出重定向到文件并将 pid 打印到屏幕上

如何使用 nohup 将输出重定向到文件并将 pid 打印到屏幕上

我用它nohup来运行一个脚本并将其输出重定向到一个文件。我运行的命令看起来有点像这样:

nohup script.sh > output.log

这工作正常,但我现在想要的是当我执行命令时还能够将正在运行的进程的 PID 打印script.sh到控制台nohup

这主要是为了方便终止进程。运行jobs不会显示任何内容。

如果有人能告诉我如何实现这一点,我将不胜感激。使用 启动脚本nohup,将输出重定向到文件,然后将 PID 打印到屏幕上。

答案1

作业和 P(G)ID

nohup script.sh > output.log

[…]

这主要是为了方便终止进程。运行jobs不会显示任何内容。

您使用的命令同步运行。直到退出后, nohupshell 才会运行下一个命令(例如) 。即使异步运行某些命令并退出,这些命令也不会被视为当前 shell 中的作业。在执行期间在另一个 shell 中调用无法向您显示脚本,因为每个 shell 都有自己的作业列表。jobsscript.shscript.shjobsscript.sh

nohup首先异步运行:

nohup script.sh > output.log &

如果启用了作业控制(在交互式 Bash 中,默认情况下启用,除非操作系统不支持它),您将看到类似

[3] 31421

31421您要查找的 PID 在哪里。然后jobs(和jobs -ljobs -p) 将起作用。无论是否启用作业控制,PID 都将作为 可用$!。调用另一个异步进程或bg将更改 的值$!,因此请考虑立即保存 PID:

nohup script.sh > output.log &
pid="$!"

shell 将知道 的 PID nohup,而不一定知道 的PID script.sh;但很可能nohup将用 替换自身script.sh而不创建新的 PID。(我认为这种行为对于 来说并不是严格要求的nohup;但常见的实现仍然这样做)。在您的情况下,这意味着kill "$pid"将发送将在启动之前SIGTERM传送到,或将在 启动之后传送到 。nohupscript.shscript.sh

SIGTERM是默认信号。您可以选择SIGINTkill -s INT "$pid"。在此答案中,我将坚持SIGTERM只避免重复-s INT

请注意,向脚本发送信号不会自动将其发送给其子脚本。

如果启用了作业控制,Bash 将在单独的进程组中运行每个命令(可能是管道)。 在您的例子中,进程组 ID (PGID) 将是 的 PID 。 终止整个进程组的正确方法是向组nohup发送:SIGTERM

kill -- "-$pid"

-PID 指示kill寻址组之前,而不仅仅是一个进程(然后--是必须的)。这将script.sh在脚本本身退出后杀死 的后代(排除故意使用另一个 PGID 运行的后代,如果有的话)。但是,这并不像您希望的那样强大。可能发生您想要杀死的进程组早已消失,并且相同的编号被重新用于某个新组的情况。在这种情况下,您可能会无意中处理错误的组。

在 Bash 中kill 内置接受作业规范。kill %3(其中取自3或的输出)将发送到正确的进程组。作业规范可能看起来像或或(空格转义或加引号,例如);因此此命令:[3][3] 31421jobsSIGTERM%3%nohup%nohup script.sh'%nohup script.sh'

kill '%nohup script.sh'

会发出找到合适工作的信号。我的意思是:除非合适的工作已经消失,而有其他合适的工作;但是应该知道您运行了什么。如果 jobspec 与零个或多个作业匹配,则不会处理任何作业。

在某些情况下,这种方法也可能不完美。父进程(此处:shell)SIGCHLD在其子进程(此处nohup:)改变状态时接收消息。这样,Bash 就会知道nohup(或任何替代它的进程)是否退出;然后它认为工作已经完成。孙子进程及其存在无关紧要。如果作为另一个 PIDnohup生成并退出(不常见)或者如果替换,异步运行某些内容并退出,则该工作将正式完成,从现在开始将不处理任何内容(再次:除非有其他工作匹配),即使该工作的进程组中仍有孙子进程。script.shscript.shnohupkill '%nohup script.sh'

如果禁用作业控制,则发出 jobspec 信号(例如kill %3)将只针对进程(例如nohup,或script.sh替换nohup),而不是进程组。

一个相当可靠的方法是:

  • 启用作业控制。如果正在终止则不需要script.sh 独自的就足够了(例如,脚本不会生成长时间运行的子进程)。默认情况下,交互式 shell 中启用作业控制。在非交互式 Bash 中(例如,在即将调用的另一个脚本中nohup script.sh &),您可以通过调用来启用它set -m(但随后向外部脚本的进程组发出信号,该进程组将不会向其作业发送信号)。

  • 使用nohup它将自身替换为要运行的任何内容(例如script.sh)。您nohup最有可能以这种方式工作。

  • Makescript.sh等待其子进程(通过同步运行命令;通过waiting 执行其作业)。如果仅终止进程就足够了,则不需要script.sh

  • 使用kill内置的正确作业规范,例如kill '%nohup script.sh'。甚至kill %3比 更安全kill -- -31421。像 这样的作业规范%3可以重复使用,但只有在相关 shell 启动新作业时才能重复使用。如果您知道%3是正确的作业,并且您不在 shell 中启动另一个作业,那么kill %3将向正确作业中的进程发送信号,或者如果作业已经完成,它将失败。另一方面,即使您知道31421是正确的 P(G)ID,它也可能死掉,并且在您调用 之前该号码可能会被重复使用kill;然后您将(尝试)向一些随机进程发送信号。

所以,如果可以使用的话,jobspecs 比 PGIDs 更好。主要原因使用的方法nohup script.sh是让其script.sh在您断开连接或(在异步的情况下nohup)正常退出 shell 后继续存在。重新连接后,您将发现自己处于一个新 shell 中。旧 shell 的子级可能仍然存在,但它们不会成为新 shell 的作业。任何使用 jobspecs 的解决方案在新 shell 中都是无用的。使用 PID 或 PGID 仍然是一种选择,但有些缺陷,因为 ID 可能已被重用(请注意,pid由于变量已随旧 shell 消失,您应该将其内容存储在某处)。


Cgroups

一般来说,最可靠的方法是使用控制组 (cgroups)。您可以将nohup及其所有子组放入单独的控制组中,该控制组不会与任何其他组混淆。您需要 root 访问权限来设置自定义 cgroup 并让您的普通用户拥有它;然后您(作为普通用户)可以将进程放入控制组中。

这是一项高级功能,在您的情况下很可能有点过度,不方便临时使用;因此我就不详细说明了。


其他可能性

  1. 我用tmux尽我所能(另一种方法是screen)。如果我是你,我会首先考虑在专用窗格中的交互式 shell 中运行script.sh(不使用; 如果需要,使用重定向)。我的 shell 是 Bash。所选窗格中的 Bash 将在前台的单独进程组中运行。通常+会使终端(此处:)发送到前台进程组;如果我想要,我会使用它。非常方便。+并且整个作业控制将正常工作,即使在重新连接并重新附加到 之后也是如此。nohupscript.shCtrlctmuxSIGINTCtrlztmux

  2. 任务后台处理程序ts(以或 的名称tsp)管理和运行作业(它自己的作业,而不是 shell 的作业)。它可以代替nohup。注意:

    • 这是一个队列,因此(取决于以前的工作和您使用的选项)新的工作可能会被推迟。
    • 即使在重新连接后,所有 shell 都可以使用同一个队列。有时是优点,有时是缺点。

答案2

那简单吗

echo $$ >> running_scripts

这对我来说很好:

#nohup script.sh > output.log &
[1] 48449
#ps
    PID TTY          TIME CMD
  47859 pts/12   00:00:00 tcsh
  48449 pts/12   00:00:00 sh
  48457 pts/12   00:00:00 sleep
  48458 pts/12   00:00:00 ps
#cat list_of_running_scripts 
48449
#kill 48449
#ps
    PID TTY          TIME CMD
  47859 pts/12   00:00:00 tcsh
  48494 pts/12   00:00:00 ps
[1]  + Terminated                    script.sh > output.log

相关内容