我用它nohup
来运行一个脚本并将其输出重定向到一个文件。我运行的命令看起来有点像这样:
nohup script.sh > output.log
这工作正常,但我现在想要的是当我执行命令时还能够将正在运行的进程的 PID 打印script.sh
到控制台nohup
。
这主要是为了方便终止进程。运行jobs
不会显示任何内容。
如果有人能告诉我如何实现这一点,我将不胜感激。使用 启动脚本nohup
,将输出重定向到文件,然后将 PID 打印到屏幕上。
答案1
作业和 P(G)ID
nohup script.sh > output.log
[…]
这主要是为了方便终止进程。运行
jobs
不会显示任何内容。
您使用的命令同步运行。直到退出后, nohup
shell 才会运行下一个命令(例如) 。即使异步运行某些命令并退出,这些命令也不会被视为当前 shell 中的作业。在执行期间在另一个 shell 中调用无法向您显示脚本,因为每个 shell 都有自己的作业列表。jobs
script.sh
script.sh
jobs
script.sh
nohup
首先异步运行:
nohup script.sh > output.log &
如果启用了作业控制(在交互式 Bash 中,默认情况下启用,除非操作系统不支持它),您将看到类似
[3] 31421
31421
您要查找的 PID 在哪里。然后jobs
(和jobs -l
,jobs -p
) 将起作用。无论是否启用作业控制,PID 都将作为 可用$!
。调用另一个异步进程或bg
将更改 的值$!
,因此请考虑立即保存 PID:
nohup script.sh > output.log &
pid="$!"
shell 将知道 的 PID nohup
,而不一定知道 的PID script.sh
;但很可能nohup
将用 替换自身script.sh
而不创建新的 PID。(我认为这种行为对于 来说并不是严格要求的nohup
;但常见的实现仍然这样做)。在您的情况下,这意味着kill "$pid"
将发送将在启动之前SIGTERM
传送到,或将在 启动之后传送到 。nohup
script.sh
script.sh
SIGTERM
是默认信号。您可以选择SIGINT
:kill -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] 31421
jobs
SIGTERM
%3
%nohup
%nohup script.sh
'%nohup script.sh'
kill '%nohup script.sh'
会发出找到合适工作的信号。我的意思是:除非合适的工作已经消失,而有其他合适的工作;但是你应该知道您运行了什么。如果 jobspec 与零个或多个作业匹配,则不会处理任何作业。
在某些情况下,这种方法也可能不完美。父进程(此处:shell)SIGCHLD
在其子进程(此处nohup
:)改变状态时接收消息。这样,Bash 就会知道nohup
(或任何替代它的进程)是否退出;然后它认为工作已经完成。孙子进程及其存在无关紧要。如果作为另一个 PIDnohup
生成并退出(不常见)或者如果替换,异步运行某些内容并退出,则该工作将正式完成,从现在开始将不处理任何内容(再次:除非有其他工作匹配),即使该工作的进程组中仍有孙子进程。script.sh
script.sh
nohup
kill '%nohup script.sh'
如果禁用作业控制,则发出 jobspec 信号(例如kill %3
)将只针对进程(例如nohup
,或script.sh
替换nohup
),而不是进程组。
一个相当可靠的方法是:
启用作业控制。如果正在终止则不需要
script.sh
独自的就足够了(例如,脚本不会生成长时间运行的子进程)。默认情况下,交互式 shell 中启用作业控制。在非交互式 Bash 中(例如,在即将调用的另一个脚本中nohup script.sh &
),您可以通过调用来启用它set -m
(但随后向外部脚本的进程组发出信号,该进程组将不会向其作业发送信号)。使用
nohup
它将自身替换为要运行的任何内容(例如script.sh
)。您nohup
最有可能以这种方式工作。Make
script.sh
等待其子进程(通过同步运行命令;通过wait
ing 执行其作业)。如果仅终止进程就足够了,则不需要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 并让您的普通用户拥有它;然后您(作为普通用户)可以将进程放入控制组中。
这是一项高级功能,在您的情况下很可能有点过度,不方便临时使用;因此我就不详细说明了。
其他可能性
我用
tmux
尽我所能(另一种方法是screen
)。如果我是你,我会首先考虑在专用窗格中的交互式 shell 中运行script.sh
(不使用; 如果需要,使用重定向)。我的 shell 是 Bash。所选窗格中的 Bash 将在前台的单独进程组中运行。通常+会使终端(此处:)发送到前台进程组;如果我想要,我会使用它。非常方便。+并且整个作业控制将正常工作,即使在重新连接并重新附加到 之后也是如此。nohup
script.sh
Ctrlctmux
SIGINT
Ctrlztmux
任务后台处理程序
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