【问题标题】:#! /usr/bin/env and process names: portability at a price?#! /usr/bin/env 和进程名称:可移植性是有代价的?
【发布时间】:2011-09-23 15:40:17
【问题描述】:

有很多goodreasons可以使用#! /usr/bin/env.底线:它使您的代码更具可移植性。嗯,有点。看看这个....


我有两个几乎相同的脚本,bintest.py

#! /usr/bin/python
import time
time.sleep(5*60)

envtest.py

#! /usr/bin/env python
import time
time.sleep(5*60)

请注意,它们仅在 shebangs 上有所不同。


bintest.py 按预期运行

br@carina:~$ ./bintest.py & ps && killall bintest.py [1] 15061 PID TTY 时间命令 14625 点/0 00:00:00 重击 15061 分/0 00:00:00 bintest.py 15062 点/0 00:00:00 ps br@carina:~$ [1]+ 终止 ./bintest.py

但是envtest.py 做的事情并不理想

br@carina:~$ ./envtest.py & ps && killall envtest.py [1] 15066 PID TTY 时间命令 14625 点/0 00:00:00 重击 15066 分/0 00:00:00 蟒蛇 15067 点/0 00:00:00 ps envtest.py:找不到进程 br@carina:~$ killall python br@carina:~$ [1]+ 终止 ./envtest.py

我们看到的是,使用#! /usr/bin/env 导致进程接收到名称“python”而不是“envtest.py”,从而使我们的killall 无效。在某种程度上,我们似乎已经将一种可移植性换成了另一种:我们现在可以轻松地更换 python 解释器,但我们已经失去了命令行上的“kill-ability”。那是怎么回事?如果这里有实现两者的最佳实践,它是什么?

【问题讨论】:

  • 还有一些充分的理由使用#!/usr/bin/env;见this questionmy answer
  • @KeithThompson,我之所以为此提供赏金是因为我正在研究一组需要在 Linux 和 Mac OS X 上运行的脚本,它们将所需的可执行文件放在不同的放在 $PATH 中,但我仍然希望 top“正确”列出每个脚本,以便您可以区分一个脚本和另一个脚本。
  • 解决方案不是使用安装脚本来替换 shebang 行上的正确本地解释器吗?

标签: command-line environment-variables portability kill shebang


【解决方案1】:

命令行上的“kill-ability”可以通过使用从 shell $! 变量获得的后台进程的 PID 进行可移植且可靠的处理。

$ ./bintest.py & bg_pid=$! ; echo bg_pid=$bg_pid ; ps && kill $bg_pid
[1] 2993
bg_pid=2993
  PID TTY          TIME CMD
 2410 pts/0    00:00:00 bash
 2993 pts/0    00:00:00 bintest.py
 2994 pts/0    00:00:00 ps
$ 
[1]+  Terminated              ./bintest.py
$ 

和 envtest.py

$ ./envtest.py & bg_pid=$! ; echo bg_pid=$bg_pid ; ps && kill $bg_pid
[1] 3016
bg_pid=3016
  PID TTY          TIME CMD
 2410 pts/0    00:00:00 bash
 3016 pts/0    00:00:00 python
 3017 pts/0    00:00:00 ps
$ 
[1]+  Terminated              ./envtest.py
$ 

正如@Adam Bryzak 指出的那样,这两个脚本都不会导致在 Mac OS X 上设置进程标题。因此,如果该功能是一项严格要求,您可能需要在您的应用程序中安装和使用 python 模块setproctitle

这篇 Stackoverflow 帖子讨论了 setting process title in python

【讨论】:

    【解决方案2】:

    我认为您不能一直依靠使用脚本名称的killall 来工作。在 Mac OS X 上,运行两个脚本后,我从 ps 得到以下输出:

     2108 ttys004    0:00.04 /usr/local/bin/python /Users/adam/bin/bintest.py
     2133 ttys004    0:00.03 python /Users/adam/bin/envtest.py
    

    并运行 killall bintest.py 导致

    No matching processes belonging to you were found
    

    【讨论】:

    • pskillall 的内部结构因系统而异,因此部分问题是使用killall 作为批评的标准。不过,这个问题很有趣。
    【解决方案3】:

    虽然我仍然想要一个使脚本语言既跨平台又易于从命令行监控的解决方案,但如果您只是在寻找 killall <scriptname> 的替代方法来停止自定义服务,我可以这样做解决了:

    kill `ps -fC <interpreterName> | sed -n '/<scriptName>/s/^[^0-9]*\([0-9]*\).*$/\1/gp'`
    

    对于那些不太熟悉 ps 和正则表达式的人,ps-f 修饰符让它列出了关于进程的“完整”信息集,包括它的命令行参数,-C 告诉它将列表过滤为仅与下一个命令行参数匹配的命令。将&lt;interpreterName&gt; 替换为pythonnode 或其他任何内容。

    sed-n 参数告诉它默认不打印任何内容,并且正则表达式脚本必须明确指示您要打印某些内容。

    在正则表达式中,第一个 /&lt;scriptName&gt;/ 告诉它将其结果过滤到仅包含内部正则表达式的行。例如,您可以将&lt;scriptName&gt; 替换为envtest

    s 表示将跟随替换正则表达式。 /^[^0-9]*\([0-9]*\).*$/ 是行匹配部分,/\1/ 是替换部分。在行匹配部分,开头的^ 和结尾的$ 表示匹配必须从行首开始,到行尾结束——整行被检查将被替换。

    [^0-9]* 涉及一些事情:[] 用于定义一组允许的字符。在这部分正则表达式中,破折号- 表示一个字符范围,因此它扩展为0123456789^ 这里的意思是“不”,直接意思是“匹配任何不是数字的字符”。之后的星号* 表示继续匹配此集中的字符,直到遇到不匹配的字符,在本例中为数字。

    \([0-9]*\) 有两个部分,\(\)[0-9]*。后者应该很容易从前面的解释中理解:它只匹配数字,并尽可能多地抓取。 \(\) 表示将匹配的内容保存到临时变量。 (在其他 RegEx 版本中,包括 Javascript 和 Perl,则使用 ()。)

    最后,.* 表示匹配所有剩余的字符,因为. 表示任何可能的字符。

    /\1/ 部分表示用\1 替换行的匹配部分(在这种情况下是整行),这是对保存的临时变量的引用(如果有两个\(\)部分,RegEx 中的第一个是\1,第二个是\2)。

    之后的g 表示“贪婪”并在遇到的每一行上运行此匹配代码,p 表示打印到达此点的任何行。

    从技术上讲,如果您有多个脚本副本正在运行,那么这会爆炸,而且您真的想要稍微重一点的:

    ps -fC <interpreterName> | sed -n '/<scriptName>/s/^[^0-9]*\([0-9]*\).$/kill \1/gp' | bash
    

    如果您想真正复制 kill*all* 功能,但这会为您想要杀死的每个脚本生成一个单独的 bash shell。

    【讨论】:

      【解决方案4】:

      在评论中,您说问题在于不同的系统(尤其是 MacOS 和 Linux)将可执行文件放在不同的目录中。

      您可以通过在两个系统上创建具有相同完整路径的目录并创建指向可执行文件的符号链接来解决此问题。

      在 Ubuntu、Solaris 和 Cygwin 上的实验表明,以 shebang 命名的可执行文件可以是符号链接。 (我无法访问 MacOS 系统,所以我不确定它是否可以在那里工作。)

      例如,在我的 Ubuntu 系统上:

      $ cat hello.bash
      #!/tmp/bin/bash
      
      echo Yes, it works
      $ ./hello.bash
      -bash: ./hello.bash: /tmp/bin/bash: bad interpreter: Permission denied
      $ mkdir /tmp/bin
      $ ln -s /bin/bash /tmp/bin/.
      $ ./hello.bash
      Yes, it works
      $ 
      

      在所有相关系统上设置公​​用目录无疑是不方便的。 (我在这个例子中使用了/tmp;不同的位置可能会更好。)

      我不确定这将如何与killall 交互,但值得一试。

      【讨论】:

      • 不过就是这样;您不能依赖 /tmp/ 在重新启动后存在,因此您的脚本不能依赖它存在。不过,这非常接近,因为killall 按预期工作。
      • @DavidEllis:这就是为什么我说不同的位置可能会更好——但你可以有一个脚本在启动时创建和填充/tmp/bin。这取决于您对系统的控制程度。
      猜你喜欢
      • 1970-01-01
      • 2021-08-18
      • 2016-03-23
      • 1970-01-01
      • 2011-11-28
      • 2013-07-16
      • 2011-07-29
      • 2015-02-01
      • 1970-01-01
      相关资源
      最近更新 更多