【问题标题】:Shell script behaves strangely when called via an Erlang port通过 Erlang 端口调用 Shell 脚本时行为异常
【发布时间】:2011-07-20 10:17:34
【问题描述】:

当从 Erlang 调用 shell 脚本时,我通常需要它们的退出状态(0 或其他),所以我使用这个函数来运行它们:

%% in module util
os_cmd_exitstatus(Action, Cmd) ->
    ?debug("~ts starting... Shell command: ~ts", [Action, Cmd]),
    try erlang:open_port({spawn, Cmd}, [exit_status, stderr_to_stdout]) of
        Port -> 
            os_cmd_exitstatus_loop(Action, Port)
    catch
        _:Reason ->
            case Reason of
                badarg ->
                    Message = "Bad input arguments";
                system_limit ->
                    Message = "All available ports in the Erlang emulator are in use";
                _ ->
                    Message = file:format_error(Reason)
            end,
            ?error("~ts: shell command error: ~ts", [Action, Message]),
            error
    end.

os_cmd_exitstatus_loop(Action, Port) ->
    receive
        {Port, {data, Data}} ->
            ?debug("~ts... Shell output: ~ts", [Action, Data]),
            os_cmd_exitstatus_loop(Action, Port);
        {Port, {exit_status, 0}} ->
            ?info("~ts finished successfully", [Action]),
            ok;
        {Port, {exit_status, Status}} ->
            ?error("~ts failed with exit status ~p", [Action, Status]),
            error;
        {'EXIT', Port, Reason} ->
            ?error("~ts failed with port exit: reason ~ts", 
                         [Action, file:format_error(Reason)]),
            error
    end.

这很好,直到我用它来启动一个分叉程序并退出的脚本:

#!/bin/sh

FILENAME=$1

eog $FILENAME &

exit 0

(在实际用例中,还有很多参数,并且在将它们传递给程序之前进行了一些按摩)。从终端运行时,它会按预期显示图像并立即退出。

但是从 Erlang 运行,它没有。在日志文件中,我看到它开始正常:

22/Mar/2011 13:38:30.518  Debug: Starting player starting... Shell command: /home/aromanov/workspace/gmcontroller/scripts.dummy/image/show-image.sh /home/aromanov/workspace/media/images/9e89471e-eb0b-43f8-8c12-97bbe598e7f7.png

并出现eog 窗口。但我明白

22/Mar/2011 13:47:14.709  Info: Starting player finished successfully

直到杀死eog 进程(使用kill 或只是关闭窗口),这不适合我的要求。为什么行为上的差异?有办法解决吗?

【问题讨论】:

    标签: bash erlang sh erlang-ports


    【解决方案1】:

    通常,如果您在 shell 脚本中使用 & 在后台运行命令,并且 shell 脚本在命令之前终止,则该命令将被孤立。可能是 erlang 试图阻止 open_port 中的孤立进程并等待 eog 终止。通常,如果您想在 shell 脚本期间在后台运行某些东西,您应该在脚本末尾输入 wait 以等待后台进程终止。但这正是你不想做的。

    您可以在 shell 脚本中尝试以下操作:

    #!/bin/sh
    
    FILENAME=$1
    
    daemon eog $FILENAME
    
    # exit 0 not needed: daemon returns 0 if everything is ok
    

    如果您的操作系统有daemon 命令。我检查了 FreeBSD,它有一个:daemon(8)

    这不是在所有类似 Unix 的系统上都可用的命令,但是在您的操作系统中可能有不同的命令在做同样的事情。

    守护程序实用程序将自己与控制终端分离并执行其参数指定的程序。

    我不确定这是否能解决您的问题,但我怀疑 eog 以某种方式保持连接到 stdin/stdou 作为一种控制终端。无论如何都值得一试。

    这也应该解决作业控制错误开启的可能问题,这也可能导致问题。由于daemon 确实正常退出,因此您的 shell 无法尝试在退出时等待后台作业,因为 shell 视图中没有。

    说了这么多:为什么不在 eog 运行时在 Erlang 中保持端口开放?

    开始:

    #!/bin/sh
    
    FILENAME=$1
    
    exec eog $FILENAME
    

    exec 调用它不会派生它,而是用eog 替换shell 进程。然后,您将在 Erlang 中看到的退出状态将是 eog 终止时的状态。如果您愿意,您也可以关闭端口并从 Erlang 终止 eog

    【讨论】:

    • 哦,daemon(1) 包装器,好主意。我考虑了exec eog 选项,但这不会让 Alexey 记录“开始播放成功完成”;他的包装器不知道该进程是等待十秒还是二十秒才能运行,而运行十秒或二十秒就可以了。但是选择从 Erlang 中杀死 eog 听起来非常值得。
    • 但是后台进程也不会给出这个信息。如果在准备阶段出现错误,则会检测到这一点。否则,要么运行带有选项 -x 的 shell(在运行命令时打印命令),要么在启动前回显。运行 open_port{line Max_line} 并解释您获得的消息以了解进度。
    • 事实上,保持端口开放(既能够更轻松地杀死程序,更重要的是,对于我的用例而言,无需轮询就知道它何时完成)是我最初的设计 :) 不幸的是,我无法说服我的经理这是个好主意。
    • daemon 似乎是解决方案,但 daemon mplayer /home/aromanov/workspace/gmcontroller/working_dir/media/video/635e0b3a-99f0-49e4-8a19-15c1de32a3a2.avi 工作正常,daemon mplayer /home/aromanov/workspace/gmcontroller/working_dir/media/video/635e0b3a-99f0-49e4-8a19-15c1de32a3a2.avi -loop 0 不行。大概daemon 认为-loop 0 被传递给它,不识别该选项,因此不启动任何东西。引用命令没有帮助,并且联机帮助页没有说明任何内容。有什么想法吗?
    • 您可以尝试使用-- 将(空)守护程序选项与被调用命令的选项分开:例如daemon -- mplayer foo.avi -loop 0。通常守护进程不应该在command 部分之后解释任何选项,但是您的操作系统上的实现似乎是草率的。许多选项解析函数接受 -- 为“我的选项到此结束”
    【解决方案2】:

    也许您的/bin/sh 在不以交互方式运行时不支持作业控制?至少我的 Ubuntu 系统上的/bin/sh(实际上是dash(1)!)提到:

          -m monitor       Turn on job control (set automatically
                           when interactive).
    

    当您从终端运行脚本时,shell 可能会识别出它正在以交互方式运行并支持作业控制。当您将 shell 脚本作为端口运行时,shell 可能会在没有作业控制的情况下运行。

    【讨论】:

    • 创建 hashbang 行 #!/bin/bash -m 没有帮助。
    • Alexy,-m 来自dash(1),而不是bash(1),所以它只适用于#!/bin/sh(如果你的系统和我的一样,并使用dash(1) 提供/bin/sh)。对于bash(1),它将在脚本中添加set -m
    • 帮助我的系统上的bash 也提到-m。但是,是的,这似乎是问题所在:#!/bin/sh -m 给出“22/Mar/2011 15:42:02.507 调试:启动播放器... Shell 输出:/home/aromanov/workspace/gmcontroller/scripts.dummy/video/ video-play.sh: 0: 无法访问 tty;作业控制已关闭”使用 bashset -m 不会给出任何错误消息,但与不使用 set -m 时相同。
    • Alexey,用 C 重写脚本是公平的游戏吗? fork(2)exec() 视频播放器会非常简单,避免工作控制混乱。 shell 脚本是否提供了足够多的其他值,以至于用 C 重写会很困难?
    • 其实,这可能是个好主意。构建有点复杂,但如果没有更好的解决方案,这是值得的。
    猜你喜欢
    • 1970-01-01
    • 2015-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-15
    • 1970-01-01
    相关资源
    最近更新 更多