通过 Erlang 端口调用时 Shell 脚本行为异常
当从 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
或只是关闭窗口),这不适合我的要求。为什么行为上有差异?有办法解决吗?
When calling shell scripts from Erlang, I generally need their exit status (0 or something else), so I run them using this function:
%% 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.
This worked fine, until I used this to start a script which forks off a program and exits:
#!/bin/sh
FILENAME=$1
eog $FILENAME &
exit 0
(In the actual usecase, there are quite a few more arguments, and some massaging before they are passed to the program). When run from the terminal, it shows the image and exits immediately, as expected.
But running from Erlang, it doesn't. In the log file I see that it starts fine:
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
and the eog
window appears. But I don't get
22/Mar/2011 13:47:14.709 Info: Starting player finished successfully
until killing the eog
process (with kill
or just closing the window), which isn't suitable for my requirements. Why the difference in behavior? Is there a way to fix it?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
通常,如果您在 shell 脚本中使用
&
在后台运行命令,并且 shell 脚本在该命令之前终止,则该命令将被孤立。 erlang 可能会尝试阻止 open_port 中的孤立进程并等待eog
终止。通常,如果您想在 shell 脚本期间在后台运行某些内容,您应该在脚本末尾添加wait
来等待后台进程终止。但这正是您不想做的。您可以在 shell 脚本中尝试以下操作:
如果您的操作系统有
daemon
命令。我检查了 FreeBSD,它有一个: daemon(8)这不是所有类似 Unix 系统上都可用的命令,但是在您的操作系统中可能有不同的命令执行相同的操作。
守护程序实用程序将自身与控制终端分离并执行其参数指定的程序。
我不确定这是否能解决您的问题,但我怀疑
eog
以某种方式保持连接到 stdin/stdou 作为一种控制终端。无论如何值得一试。这还应该解决作业控制打开错误的可能问题,这也可能导致该问题。由于
daemon
确实正常退出,因此您的 shell 无法在退出时尝试等待后台作业,因为 shell 视图中没有任何后台作业。说了这么多:为什么不在 Erlang 中在
eog
运行时保持端口打开呢?开始:
用
exec
调用它不会分叉它,而是用eog
替换 shell 进程。您在 Erlang 中看到的退出状态将是eog
终止时的状态。如果您愿意,您还可以关闭端口并终止 Erlang 的eog
。Normally if you run a command in background with
&
in a shell script and the shell script terminates before the command, then the command gets orphaned. It might be that erlang trys to prevent orphaned processes in open_port and waits foreog
to terminate. Normally if you want to run something in background during a shell script you should put in await
at the end of the script to wait for your background processes to terminate. But this is exactly what youd don't want to do.You might try the following in your shell script:
If your operating system has a
daemon
command. I checked in FreeBSD and it has one: daemon(8)This is not a command available on all Unix alike systems, however there might be a different command doing the same thing in your operating system.
The daemon utility detaches itself from the controlling terminal and executes the program specified by its arguments.
I'm not sure if this solves your problem, but I suspect that
eog
somehow stays attached to stdin/stdou as a kind of controling terminal. Worth a try anyway.This should also solve the possible problem that job control is on erroneously which could also cause the problem. Since
daemon
does exit normally your shell can't try to wait for the background job on exit because there is none in the shells view.Having said all this: why not just keep the port open in Erlang while
eog
runs?Start it with:
Calling it with
exec
doesn't fork it bu replaces the shell process witheog
. The exit status you'll see in Erlang will then be the status ofeog
when it terminates. Also you have the possibility to close the port and terminateeog
from Erlang if you want to do so.也许您的
/bin/sh
在非交互运行时不支持作业控制?至少我的 Ubuntu 系统上的/bin/sh
(实际上是dash(1)
!) 提到:当您从终端运行脚本时,shell 可能会识别出它正在交互运行并支持作业控制。当您将 shell 脚本作为端口运行时,shell 可能会在没有作业控制的情况下运行。
Perhaps your
/bin/sh
doesn't support job control when it isn't run interactively? At least the/bin/sh
(actuallydash(1)
!) on my Ubuntu system mentions:When you run the script from a terminal, the shell probably recognizes that it is being run interactively and supports job control. When you run the shell script as a port, the shell probably runs without job control.