Use an OS process like a bash pipe: Send it STDIN and get its STDOUT

523 views Asked by At

I'm trying to use an external process which reads the STDIN, and writes to STDOUT.

I want to write the equivalent of this in Elixir, without using an external library or wrapper script:

$ echo foo | nkf
foo

i.e. send data to nkf on stdin, and get the converted result back from nkf's stdout, knowing that it has finished processing the stream.

I was trying to do this with ports, but the problem is a single sent message can be returned in multiple received messages, so there's no way to tell when the end of the message has been reached (simplified example, "foo" is a whole file in reality):

iex(1)> port = Port.open({:spawn, "nkf -u"}, [:binary])
#Port<0.7>
iex(2)> Port.command(port, "foo")
true
iex(3)> flush
{#Port<0.7>, {:data, "fo"}}
{#Port<0.7>, {:data, "o"}}
:ok

How can I get the same bash pipe behaviour with Ports in Elixir?

2

There are 2 answers

0
Adam Millerchip On

The beam does not currently provide a way to close the stream to the process and wait for the stream from the process to finish sending. Using ports, closing the port will also close the stream from the external process, even if the process has not finished sending data. Due to this, it is not currently possible to do using only built-in features - it's necessary to enlist the help of external tools like porcelain or erlexec.

1
Pratyush On

You can use :os.cmd("echo foo | nkf")

Mentioned here:

Shell commands If you desire to execute a trusted command inside a shell, with pipes, redirecting and so on, please check :os.cmd/1.