我执行这个命令:
nohup bash /home/opc/TEST.sh >/dev/null 2>&1 $
并在其末尾TEST.sh
有,因此它会无限次重复。nohup bash /home/opc/TEST.sh >/dev/null 2>&1 $
过了一会儿,RAM 开始超载,ps -ef | grep TEST.sh
我得到的输出充满了“尾巴”,即每个周期的剩余部分nohup bash …
:
root 4312 4294 0 02:15 ? 00:00:00 bash /home/user1/TEST.sh $
root 4432 4312 0 02:50 ? 00:00:00 bash /home/user1/TEST.sh $
root 4594 4432 0 03:26 ? 00:00:00 bash /home/user1/TEST.sh $
root 4722 4594 0 04:01 ? 00:00:00 bash /home/user1/TEST.sh $
root 4796 4722 0 04:37 ? 00:00:00 bash /home/user1/TEST.sh $
root 4962 4830 0 05:05 pts/2 00:00:00 grep --color=auto TEST
我如何自动清理 RAM 和已执行nohup
脚本的“尾部”?也许在每次执行后包含一些参数来nohup
清理它。
这是完整的脚本,名为TEST.sh
:
#!/bin/bash
cd "$(dirname "$0")"
ffmpeg -re -i /home/opc/"123.mp4" -c copy -f flv rtmp://"output link"
nohup bash /home/opc/TEST.sh >/dev/null 2>&1 $
其目的是创建一个循环ffmpeg
流,该循环流无限次重复,直到整个流根据需要被终止。
答案1
您的脚本通过硬编码路径名调用自身。这是创建循环的糟糕方式。问题在于它为何糟糕(但可能还有更多原因)。
&
问题发生的原因是解释脚本的 shell 会等待这个 finalnohup bash …
退出。通常,shell 会等待命令,除非命令以&
.&
结尾,因为命令终止符/分隔符会使 shell 异步执行命令。换句话说,如果脚本中的最后一行是:
nohup bash … &
然后 shell 解释就此而言脚本不会等待新脚本bash
退出。它将继续;并且由于脚本中没有其他操作可做,因此它将退出。
$
命令中的尾部看起来很奇怪。$
在 shell 中,语法像 和其他一些语法一样特殊$var
;$(…)
只有$
一个词并不特殊。您的$
只是nohup
(的另一个参数参数在重定向之后并不重要),一个任意参数,它将成为 的参数bash
,最终它是脚本的第一个参数。您的脚本不使用$1
或$@
,$*
因此这个参数完全无关紧要。
也许您(或您$
从其处获得此信息的人)想要使用&
,但$
由于某些错误而出现 。 (我认为比没有nohup … &
更常见,因此如果您的代码是从某些资源中复制的,那么最初的想法可能是使用。)请注意,作为命令终止符/分隔符不是命令的参数。您可以在 的输出中看到 ,但如果使用它,您将看不到。nohup …
&
&
&
$
ps -ef
&
添加&
就足以“修复”你的脚本,但这不是最好的方法。这种方法确实可以防止bash
进程累积:每次bash
生成新进程后,旧进程bash
就会消亡。然而,这种轮换是不必要的。创建新进程的成本很高。使用现代计算机,人们通常可以承受一些资源的浪费;但在我看来,如果人们可以毫不费力地编写更优化、更优雅的代码,那么人们就应该这样做。
exec
脚本中nohup bash …
是最后一条命令。对于解释脚本的 shell 来说,没有其他操作。在这种情况下,您可以使用 来避免创建新进程exec
。在 Bash 中(以及在任何类似 POSIX 的 shell 中)exec something
,使 shell 用 替换自身something
。脚本的最后一行可以是:
exec nohup bash …
并且脚本的当前解释器将替换自身,nohup
而不是创建一个新进程然后退出。
注意nohup
,当它运行新的bash
(或其他)时,也会执行类似操作。您发布的输出中的 PIDps -ef
显示每个bash
都是前一个的子进程,尽管它们nohup
之间有子进程。您的nohup
进程在完成设置工作后,取代自身与 相同bash
,从现在起,父进程将(不是)bash
视为其子进程。在脚本中使用 将导致 和一次又一次地相互替换,仍然在一个 PID 下。对于您来说,这比重新创建和消亡的进程循环要好。bash
nohup
exec nohup bash …
bash
nohup
另外,注释exec … &
毫无意义。当前 shell 无法用某些东西替换自身,同时继续执行而无需等待。如果它设法执行exec
某些操作,那么它将不复存在,新的可执行文件将取代它作为进程。在我的测试中,在exec … &
wins中&
,即命令的行为就像exec
不存在一样(有点,可能有细微差别,我还没有彻底测试过)。
据我所知,在某些情况下bash
(至少是某些版本)可以隐式exec
执行最后一条命令,以避免创建新进程。我不知道为什么bash
在你的情况下没有这样做(也不知道我们是否应该首先期望它这样做)。没关系,你无论如何都不应该依赖这样的优化。如果你想要,bash
那么请明确exec
使用。exec
nohup
和>/dev/null 2>&1
您可以nohup
在这里了解具体内容:nohup
和disown
之间的区别&
。
nohup
设置了一些内容,它不需要保留,它的工作已经完成。它可以用您希望它运行的任何可执行文件替换自身(如上所述)。设置的内容仍然nohup
存在。除非有什么东西再次故意改变这些内容,否则不再运行的效果nohup
会影响可执行文件及其后代。
同样,如果您使用重定向运行脚本,则无需重新应用它们。
这意味着您实际上不需要在脚本的最后一行使用nohup
and 。如果您最初使用and>/dev/null 2>&1
运行脚本(例如从交互式 shell 中),那么它应该足够了。如果我是您,我会从脚本中删除and 。通常我会使用and启动脚本,但如果我选择不使用 and 启动脚本,那么脚本中的任何代码都不会覆盖我的选择。nohup
>/dev/null 2>&1
nohup
>/dev/null 2>&1
nohup
>/dev/null 2>&1
#!/bin/bash
和bash
您的脚本包含一个 shebang,它是#!/bin/bash
。每次运行它时,您仍会使用bash /path/to/the_script
。此方法明确运行bash
打开/path/to/the_script
并解释它。当bash
解释脚本时,shebang 只是一个注释。
如果您使脚本可执行(chmod +x /path/to/the_script
),那么您将能够“直接”以 的身份运行它/path/to/the_script
。内核将读取 shebang 并/bin/bash /path/to/the_script
为您执行。在此方法中,shebang 很重要(请参阅如果没有事情发生会发生什么)。存在细微差别,您可能(甚至必须) 坚持bash /path/to/the_script
。但是你确实使用了shebang,你可能想利用它。使脚本可执行并在没有前导bash
字的情况下调用它。
想象一下,有一天你将脚本移植到 Python(实际的脚本很简单,没有理由移植它,但一般来说,你可能出于某种原因想要移植脚本)。任何使用的代码bash /path/to/the_script
都必须修补为使用python
而不是bash
。通过正确使用 shebang(并在适当的时候从 更改为#!/bin/bash
左右#!/usr/bin/python
),你允许任何东西或任何人继续调用/path/to/the_script
。解释器属于实现,调用者不应该关心解释器应该是什么。shebang 的机制允许他们不关心。
此外,由于您的代码中没有使用 POSIX shell 以外的功能,因此 shebang 可能是#!/bin/sh
。sh
应该表现更好,因为它不会加载特定于 的功能bash
。即使您的操作系统符号sh
链接到也是如此bash
(Bash 会检测何时将其调用为sh
并跳过特定于 的步骤bash
)。
TEST.sh
这个名字具有误导性,因为其他一切都表明您想使用而不是 来TEST.sh
运行脚本。在 Linux 中,很少有工具关心“扩展”,整个操作系统并不关心。事实上,没有扩展的概念,您看到的只是 的子字符串,而是(完整的)文件名。(作为比较:在 DOS/Windows 世界中,扩展作为单独的实体开始bash
sh
.sh
TEST.sh
TEST.sh
沿着文件名;它们在操作系统级别仍然很重要。)
再次,假设您将脚本移植到 Python。您会更改名称吗?如果不这样做,那么它会产生误导(至少对人类而言)。如果您这样做,那么每段使用的代码TEST.sh
都需要修补TEST.py
。
解释器属于实现,调用者不必关心。因此,请根据可执行文件的功能来命名,而不是根据其内部结构来命名。命名脚本TEST
,利用 shebang(上面已详细说明),调用时不必关心解释器。
在您的操作系统中,某些可执行文件是脚本,您可能没有意识到,因为每个这样的脚本都不是foo.sh
也不是foo.py
,而是foo
。如果它被移植到另一个解释器,或者如果它被实现为二进制可执行文件,您(和您的操作系统的其余部分)都不会注意到。在命名脚本时采用这种良好做法。
cd
您的脚本中似乎没有使用相对路径。如果您没有重定向nohup
到/dev/null
,它将在当前工作目录中创建nohup.out
;但是您确实重定向了,因此即使在这里当前工作目录也无关紧要。
cd "$(dirname "$0")"
很可能是不需要的。
在 shell 中循环
考虑到以上所有因素,您可以使您的脚本(命名TEST
并设为可执行文件)变得简单,如下所示:
#!/bin/sh
ffmpeg -re -i /home/opc/"123.mp4" -c copy -f flv rtmp://"output link"
exec /home/opc/TEST
并使用以下命令调用它:
nohup /home/opc/TEST >/dev/null 2>&1
(&
如果需要,可以使用终止符)。这仍然不是在 shell 中创建循环的最佳方法。更好的方法是使用特定语法实现显式循环,例如while
:
#!/bin/sh
while :; do
ffmpeg -re -i /home/opc/"123.mp4" -c copy -f flv rtmp://"output link"
done
:
是一个始终成功的无操作,因此循环永远不会自行结束。现在没有理由exec
一次又一次地调用(或执行)脚本,同一个 shell 会ffmpeg
一次又一次地循环和调用。
循环播放ffmpeg
我不ffmpeg
知道我还没有测试过,但根据这个答案 -stream_loop -1
就是你制作循环所需的全部内容ffmpeg
。脚本可能是:
#!/bin/sh
exec ffmpeg -stream_loop -1 -re -i /home/opc/"123.mp4" -c copy -f flv rtmp://"output link"
(可能需要一些额外的标志,例如这里)。
现在我们不需要在 shell 中使用循环,ffmpeg
它本身会循环输入。我使用了exec
,因此 shell 会用 替换自身,而无需创建新进程。实际上,解释脚本的 shell 除了替换自身之外什么都不做。您也可以直接在 下ffmpeg
运行命令:ffmpeg
nohup
nohup ffmpeg -stream_loop -1 -re -i /home/opc/"123.mp4" -c copy -f flv rtmp://"output link" >/dev/null 2>&1
(&
如果需要,可以使用终止符)。将ffmpeg
命令保留在脚本中可能仍然是一个好主意,因为该命令很复杂、不明显,不是您想要重新输入的内容。