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.