如何防止递归调用自身的 shell 脚本留下累积并使用越来越多 RAM 的剩余部分?

如何防止递归调用自身的 shell 脚本留下累积并使用越来越多 RAM 的剩余部分?

我执行这个命令:

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 下。对于您来说,这比重新创建和消亡的进程循环要好。bashnohupexec nohup bash …bashnohup

另外,注释exec … &毫无意义。当前 shell 无法用某些东西替换自身,同时继续执行而无需等待。如果它设法执行exec某些操作,那么它将不复存在,新的可执行文件将取代它作为进程。在我的测试中,在exec … &wins中&,即命令的行为就像exec不存在一样(有点,可能有细微差别,我还没有彻底测试过)。

据我所知,在某些情况下bash(至少是某些版本)可以隐式exec执行最后一条命令,以避免创建新进程。我不知道为什么bash在你的情况下没有这样做(也不知道我们是否应该首先期望它这样做)。没关系,你无论如何都不应该依赖这样的优化。如果你想要,bash那么请明确exec使用。exec


nohup>/dev/null 2>&1

您可以nohup在这里了解具体内容:nohupdisown之间的区别&

nohup设置了一些内容,它不需要保留,它的工作已经完成。它可以用您希望它运行的任何可执行文件替换自身(如上所述)。设置的内容仍然nohup存在。除非有什么东西再次故意改变这些内容,否则不再运行的效果nohup会影响可执行文件及其后代。

同样,如果您使用重定向运行脚本,则无需重新应用它们。

这意味着您实际上不需要在脚本的最后一行使用nohupand 。如果您最初使用and>/dev/null 2>&1运行脚本(例如从交互式 shell 中),那么它应该足够了。如果我是您,我会从脚本中删除and 。通常我会使用and启动脚本,但如果我选择不使用 and 启动脚本,那么脚本中的任何代码都不会覆盖我的选择。nohup>/dev/null 2>&1nohup>/dev/null 2>&1nohup>/dev/null 2>&1


#!/bin/bashbash

您的脚本包含一个 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/shsh应该表现更好,因为它不会加载特定于 的功能bash。即使您的操作系统符号sh链接到也是如此bash(Bash 会检测何时将其调用为sh并跳过特定于 的步骤bash)。


TEST.sh

这个名字具有误导性,因为其他一切都表明您想使用而不是 来TEST.sh运行脚本。在 Linux 中,很少有工具关心“扩展”,整个操作系统并不关心。事实上,没有扩展的概念,您看到的只是 的子字符串,而是(完整的)文件名。(作为比较:在 DOS/Windows 世界中,扩展作为单独的实体开始bashsh.shTEST.shTEST.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运行命令:ffmpegnohup

nohup ffmpeg -stream_loop -1 -re -i /home/opc/"123.mp4" -c copy -f flv rtmp://"output link" >/dev/null 2>&1

&如果需要,可以使用终止符)。将ffmpeg命令保留在脚本中可能仍然是一个好主意,因为该命令很复杂、不明显,不是您想要重新输入的内容。

相关内容