Erlang Port实现调用系统命令并逐行输出执行过程

最近在做一个通过WEB调用系统命令的工具,难点是如何获取执行过程,同时可以逐行输出?

想起以前有看到霸爷提到rebar中封装了类似的功能,于是从rebar_utils中整出了下面的东西,很好用。

-module(sh_port).

-export([sh/1, sh/2]).

%%
%% Options = [Option] -- defaults to [use_stdout, abort_on_error]
%% Option = ErrorOption | OutputOption | {cd, string()} | {env, Env}
%% ErrorOption = return_on_error | abort_on_error | {abort_on_error, string()}
%% OutputOption = use_stdout | {use_stdout, bool()}
%% Env = [{string(), Val}]
%% Val = string() | false
%%
sh(Command) ->
    sh(Command, []).

sh(Command0, Options0) ->
    DefaultOptions = [use_stdout, abort_on_error],
    Options = [expand_sh_flag(V)
               || V <- proplists:compact(Options0 ++ DefaultOptions)],
    ErrorHandler = proplists:get_value(error_handler, Options),
    OutputHandler = proplists:get_value(output_handler, Options),
    Command = patch_on_windows(Command0, proplists:get_value(env, Options, [])),
    PortSettings = proplists:get_all_values(port_settings, Options) ++
        [exit_status, {line, 16384}, use_stdio, stderr_to_stdout, hide],
    Port = open_port({spawn, Command}, PortSettings),
    case sh_loop(Port, OutputHandler, []) of
        {ok, _Output} = Ok ->
            Ok;
        {error, {_Rc, _Output}=Err} ->
            ErrorHandler(Command, Err)
    end.

sh_loop(Port, Fun, Acc) ->
    receive
        {Port, {data, {eol, Line}}} ->
            sh_loop(Port, Fun, Fun(Line ++ "\n", Acc));
        {Port, {data, {noeol, Line}}} ->
            sh_loop(Port, Fun, Fun(Line, Acc));
        {Port, {exit_status, 0}} ->
            {ok, lists:flatten(lists:reverse(Acc))};
        {Port, {exit_status, Rc}} ->
            {error, {Rc, lists:flatten(lists:reverse(Acc))}}
    end.

expand_sh_flag(return_on_error) ->
    {error_handler,
     fun(_Command, Err) ->
             {error, Err}
     end};
expand_sh_flag({abort_on_error, Message}) ->
    {error_handler,
     log_msg_and_abort(Message)};
expand_sh_flag(abort_on_error) ->
    {error_handler,
     fun log_and_abort/2};
expand_sh_flag(use_stdout) ->
    {output_handler,
     fun(Line, Acc) ->
             io:format("~s", [Line]),
             [Line | Acc]
     end};
expand_sh_flag({use_stdout, false}) ->
    {output_handler,
     fun(Line, Acc) ->
             [Line | Acc]
     end};
expand_sh_flag({cd, _CdArg} = Cd) ->
    {port_settings, Cd};
expand_sh_flag({env, _EnvArg} = Env) ->
    {port_settings, Env}.

%% We do the shell variable substitution ourselves on Windows and hope that the
%% command doesn't use any other shell magic.
patch_on_windows(Cmd, Env) ->
    case os:type() of
        {win32,nt} ->
            Cmd1 = "cmd /q /c "
                ++ lists:foldl(fun({Key, Value}, Acc) ->
                                       expand_env_variable(Acc, Key, Value)
                               end, Cmd, Env),
            %% Remove left-over vars
            re:replace(Cmd1, "\\\$\\w+|\\\${\\w+}", "",
                       [global, {return, list}]);
        _ ->
            Cmd
    end.

-type err_handler() :: fun((string(), {integer(), string()}) -> no_return()).
-spec log_msg_and_abort(string()) -> err_handler().
log_msg_and_abort(Message) ->
    fun(_Command, {_Rc, _Output}) ->
            abort(Message, [])
    end.

-spec log_and_abort(string(), {integer(), string()}) -> no_return().
log_and_abort(Command, {Rc, Output}) ->
    abort("sh(~s)~n"
           "failed with return code ~w and the following output:~n"
           "~s~n", [Command, Rc, Output]).

%%
%% Given env. variable FOO we want to expand all references to
%% it in InStr. References can have two forms: $FOO and ${FOO}
%% The end of form $FOO is delimited with whitespace or eol
%%
expand_env_variable(InStr, VarName, RawVarValue) ->
    case string:chr(InStr, $$) of
        0 ->
            %% No variables to expand
            InStr;
        _ ->
            ReOpts = [global, unicode, {return, list}],
            VarValue = re:replace(RawVarValue, "\\\\", "\\\\\\\\", ReOpts),
            %% Use a regex to match/replace:
            %% Given variable "FOO": match $FOO\s | $FOOeol | ${FOO}
            RegEx = io_lib:format("\\\$(~s(\\s|$)|{~s})", [VarName, VarName]),
            re:replace(InStr, RegEx, [VarValue, "\\2"], ReOpts)
    end.

-spec abort() -> no_return().
abort() ->
    throw(rebar_abort).

-spec abort(string(), [term()]) -> no_return().
abort(String, Args) ->
    io:format(String, Args),
    abort().

(本文来自瑞仙的Erlang开发博客)

时间: 2024-08-26 04:45:57

Erlang Port实现调用系统命令并逐行输出执行过程的相关文章

python调用系统命令popen、system

python调用Shell脚本,有两种方法:os.system(cmd)或os.popen(cmd),前者返回值是脚本的退出状态码,后者的返回值是脚本执行过程中的输出内容.所以说一般我们认为popen更加强大 os.system(cmd): 该方法在调用完shell脚本后,返回一个16位的二进制 数,低位为杀死所调用脚本的信号号码,高位为脚本的退出状态码,即脚本中“exit 1”的代码执行后,os.system函数返回值的高位数则是1,如果低位数是0的情况下,则函数的返回值是0×100,换算为1

Python调用系统命令的6种方法

Python调用系统命令的6种方法在Python中调用系统命令一般使用os或者subprocess模块,下面介绍Python中最常用的6种调用系统命令的方法.1.os.system()该函数返回命令执行结果的返回值,system()函数在执行过程中进行了以下三步操作:1.fork一个子进程:2.在子进程中调用exec函数去执行命令:3.在父进程中调用wait(阻塞)去等待子进程结束.返回0表示命令执行成功,其他表示失败.用法:os.system("command")2.os.popen

调用系统命令 system-config-kickstart 报错,解决办法如下

[[email protected] ~]# system-config-kickstart Xlib:  extension "RANDR" missing on display "localhost:10.0". /usr/share/system-config-kickstart/kickstartGui.py:103: GtkWarning: GtkSpinButton: setting an adjustment with non-zero page si

python调用系统命令 shell命令

使用python调用系统命令,基本有3种选择: 1. 使用os模块的system方法 import os os.system('ls') 2. 使用os模块的popen方法 import os os.popen('ls') 3. 使用commands模块的getstatusoutput方法 import commands commands.getstatusoutput('ls') 以上3种方式都可以调用系统命令,但其中第三种方式,过程中如果系统命令报错,例如mkdir一个已存在的目录,其不会把

在web上逐行输出较大的txt文件

在某些场景下,需要在web上展示一些日志文件,这些日志文件是放在文件服务器上的一些txt. 当日志文件很大时,下载日志会导致页面长时间卡住,一直在loading状态,而且下载完日志之后分析日志并生成dom,瞬间大量的dom渲染可能导致页面崩溃. 于是想着优化一下日志的输出方式,开始下载即在页面上一行一行打印日志,就像一些IDE中输出程序的编译过程一样. 最终实现的方法如下: 在下载文件的时候,让请求过一层代理,代理写输出流的时候分段输出: ? int l; byte[] buffer = new

在winform中调用js文件并输出结果

在winform中调用js文件并输出结果默认分类 2007-10-19 16:35:06 阅读25 评论0 字号:大中小 由于项目需要在winform中调一个强大的js,所以把这个tip记录在此: 1.下载并安装Microsoft 下载 http://www.microsoft.com/downloads/details.aspx?displaylang=zh-cn&FamilyID=D05FCF37-4D9F-4769-9442-0BCEEF907033 2.在项目中添加引用:msscript

JAVA中调用CMD命令,并输出执行结果

package com.wzw.util; import java.io.BufferedReader; import java.io.InputStreamReader; public class CmdDemo { public static void main(String[] args) { BufferedReader br = null; try { Process p = Runtime.getRuntime().exec("net user"); br = new Bu

oracle 定时任务 job 调用存储过程有回到输出参数(含out参数)

oracle 定时任务 job 调用存储过程有返回输出参数(含out参数) 因前台调用一个含有OUT参数的存储过程,同时在JOB里也想调用同一个存储过程,不想将OUT参数去掉重新建一个存储过程再被JOB调用.虽然OUT参数在JOB里没有任何意义,但是考虑到程序最简化,不重复建设,采用了如下方法,即在调用存储过程前先定义参数变量.以下s1,s2均为OUT参数,希望对大家有所帮助.begin  sys.dbms_job.submit(job => :job1,                    

jquery更改ready方法调用顺序,在ready之后执行Js代码

*/--> jquery更改ready方法调用顺序,在ready之后执行Js代码 Table of Contents 问题描述 在所有的ready方法之后执行上面的方法 重写$.fn.ready方法 查看$.fn.ready的源码定义 修改自己的$.fn.ready 闭包,增加安全性 问题描述 我想要控制Input,回车不提交表单,思路如下: $(function(){ $("form input").on("keypress",function(event)