Follow stdin - reads the file, but doesn't process the inserted lines

Hi everyone

I’m trying to create a CLI with Elixir to read and follow a stdin. It reads the file, but don’t process the inserted lines after the app start. I tried with escript and release. I want to run something like this

cli_command < file

For releases I created a GenServer that start start call a loop module on start

defmodule CLI.Server do
  @moduledoc """
  CLI Server
  """

  use GenServer

  def start_link(state) do
    GenServer.start_link(__MODULE__, state, name: __MODULE__)
  end

  def init(opts \\ []) do
    send(self(), :listen)

    opts = [
      input: Keyword.get(opts, :input, :stdio),
      writer: Keyword.get(opts, :writer, fn content -> IO.write(:stdio, content) end)
    ]

    {:ok, opts}
  end

  def handle_info(:listen, [input: input, writer: writer] = state) do
    CLI.Handler.listen(input, writer)

    {:noreply, state}
  end
end
defmodule CLI.Handler do
  def listen(input, writer) do
    input
    |> IO.stream(:line)
    |> Enum.each(fn line ->
      response = CLI.Operation.run(line)

      writer.(response)
      writer.("\n")
    end)

    listen(input, writer)
  end
end

For escript I used something like CLI.Handler as main_module.

For both cases it didn’t write the file following lines. Any idea what is the best practice to do this?

I made some tests with this implementation

defmodule CLI.Handler do
  def listen(stream, writer) do
    case IO.gets(stream, "") do
      line when is_binary(line) ->
        line
        |> CLI.Operation.run()
        |> writer.()

        writer.("\n")
        listen(stream, writer)

      :eof ->
        listen(stream, writer)

      other ->
        other
    end
  end
end

But it stops reading when the read result is :eof even I add new lines to stream. However if only start the app (without < file) the stdin starts listen and process all the lines that I input

How do you send lines into a stream that has been closed?

It is a recursive method so it doesn’t close until I stop it manually. When I run bin/cli start < file it process all the file lines (line by line) and when it is :eof I ask to keep in loop, but even I add new lines to file, this result still :eof. I guess I should rewind the pointer in this case, but this method does not exist

:eof means “end of file”. It is specified, that there can never be something after the end of a file. Most implementations of file functions wont read anymore from this point onwards without reopening the file or “seeking” in to to reposition the “cursor”.

Do you perhaps need a fifo pipe instead?

1 Like

I don’t know, I expected it works as Ruby $stdin.gets works when it’s in a loop or the tail -f file works in Unix.