EXE

MOTIVATION

Path clobbering and a reboot may happen here even email conforms RFC!

> Email = "proger+/&reboot%[email protected]". > os:cmd(["mkdir -p ", Email]).

So we can fix this with proper escaping, e.g.:

> Path = sh_path:escape("email+=/[email protected]"). "email+=%[email protected]"

ESSENCE

reduce() -> fun({_, Chunk}, Acc) -> [Chunk|Acc] end. run(Args) -> erlang:open_port({spawn_executable, os:find_executable("sh")}, [stream, in, out, eof, use_stdio, stderr_to_stdout, binary, exit_status, {args, ["-c",Args]}, {cd, element(2,file:get_cwd())}, {env, []}]). sh(Port) -> sh(Port, reduce(), []). sh(Port, Fun, Acc) -> receive {Port, {exit_status, Status}} -> {done, Status, iolist_to_binary(lists:reverse(Acc))}; {Port, {data, {eol, Line}}} -> sh(Port, Fun, Fun({eol, Line}, Acc)); {Port, {data, {noeol, Line}}} -> sh(Port, Fun, Fun({noeol, Line}, Acc)); {Port, {data, Data}} -> case { binary:match(Data, <<"Sign the certificate? [y/n]:">>) =/= nomatch, binary:match(Data, <<"requests certified, commit?">>) =/= nomatch} of { true , _} -> Port ! {self(),{command,<<"y\n">>}}; { _ , true} -> Port ! {self(),{command,<<"y\n">>}}; {_,_} -> skip end, sh(Port, Fun, Fun({data,Data}, Acc)) end.

USAGE

Family of functions and ports involving interacting with the system shell, paths and external programs.

> Url = "https://github.com/proger/darwinkit.git", > sh:run(["git", "clone", Url], binary, "/tmp"). {done,0,<<"Cloning into 'darwinkit'...\n">>}
> UserUrl = "https://github.com/proger/darwinkit.git". > sh:run(["git", "clone", UserUrl], binary, "/tmp"). {done,128, <<"fatal: destination path 'darwinkit' ", "already exists and is not an empty directory.\n">>}
> sh:run(["ifconfig"], "/tmp/output.log", "/tank/proger/vxz/otp"). {done,0,"/tmp/output.log"}
% cat /tmp/output.log >>> {{2013,8,28},{8,39,14}} /sbin/ifconfig lo0: flags=8049 mtu 16384 options=3 inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1 inet 127.0.0.1 netmask 0xff000000 inet6 ::1 prefixlen 128 gif0: flags=8010 mtu 1280 stf0: flags=0<> mtu 1280 en0: flags=8863 mtu 1500 ether 7c:d1:c3:e9:24:65 inet6 fe80::7ed1:c3ff:fee9:2465%en0 prefixlen 64 scopeid 0x4 inet 192.168.63.163 netmask 0xfffffc00 broadcast 192.168.63.255 media: autoselect status: active p2p0: flags=8843 mtu 2304 ether 0e:d1:c3:e9:24:65 media: autoselect status: inactive >>> {{2013,8,28},{8,39,14}} exit status: 0
> sh:oneliner(["touch", filename:join("/tmp/", Path)]). {done,0,<<>>}
> sh:oneliner("uname -mnprs"). {done,0,<<"Darwin mac 18.2.0 x86_64 i386">>}
> sh:oneliner("git describe --always"). {done,128,<<"fatal: Not a valid object name HEAD">>}
> sh:oneliner("git describe --always", "/tank/proger/vxz/otp"). {done,0,<<"OTP_R16B01">>}

FDLINK PORT

Consider a case of spawning a port that does not actually read its standard input (e.g. socat that bridges AF_UNIX with AF_INET):

# pstree -A -a $(pgrep beam.smp) beam.smp -- -root /usr/lib/erlang -progname erl -- -home /root -- -pa ebin -config sys.config |-socat tcp-listen:32133,reuseaddr,bind=127.0.0.1 unix-connect:/var/run/docker.sock `-16*[{beam.smp}]

If you terminate the node, beam stdin availability for you:

> Fdlink = sh:fdlink_executable(). > erlang:open_port({spawn_executable, Fdlink}, [stream, exit_status, {args, ["/usr/bin/socat"|Args]}).

fdlink will also close the standard input of its child process.