tags:

views:

209

answers:

3

Is there a Erlang call where I can do Retval = subprocess:call(["cmd", "arg1", "arg2", "arg3"])?

If I'm building a complex command to execute, with os:cmd/1 it is easy to make escaping mistakes. Compare to Python's subprocess.call() method where I pass in a list of strings and know that it is passed verbatim to the subprocess, nothing mangled.

Thanks.

A: 

Can't you just do os:cmd(string:join(["cmd", "arg1", "arg2"], " ")) ?

Zed
That does not address the problem, which is that the string given to os:cmd/1 will be processed by a shell. This will expand things in a system-dependent way. For example, os:cmd("echo 'Hello'") will pass Hello to the echo command without the single quotes. It is hard to get this sort of thing right so that the program gets the exact same argument strings that you have on the Erlang side. You need a way to run the program without a shell as middle-man.
RichardC
+4  A: 

Nowadays you can do open_port({spawn_executable, Command}, [{args, [A1, ..., An]}]), but there is no convenient wrapper for this in the os module yet. You could take the code for os:cmd/1 and tweak it to use spawn_executable. Check the docs for erlang:open_port/2 for more details.

RichardC
+2  A: 

This is what I have come up with.

-module(mycmd).
-export([cmd/2]).

cmd(Cmd, Args) ->
    Tag = make_ref(), 
    {Pid, Ref} = erlang:spawn_monitor(fun() ->
            Rv = cmd_sync(Cmd, Args),
            exit({Tag, Rv})
        end),
    receive
        {'DOWN', Ref, process, Pid, {Tag, Data}} -> Data;
        {'DOWN', Ref, process, Pid, Reason} -> exit(Reason)
    end.

cmd_sync(Cmd, Args) ->
    P = open_port({spawn_executable, os:find_executable(Cmd)}, [
            binary, use_stdio, stream, eof, {args, Args}]),
    cmd_receive(P, []).

cmd_receive(Port, Acc) ->
    receive
        {Port, {data, Data}} -> cmd_receive(Port, [Data|Acc]);
        {Port, eof}          -> {ok, lists:reverse(Acc)}
    end.
me2