It is possible to tell IEx to read commands from a file and leave the user at its prompt by doing
cat file.txt - | iex
where the file is something like
IO.puts "Hello"
IO.puts "World"
but the user will not have typical IEx prompt goodies like autocompletion, Ctrl+A, etc
Let IEx be called like cat file.txt - | iex and still support its prompt goodies.
Yes, unfortunately this is an Erlang limitation. So if somebody is interested in this behaviour, they would need to report and, most likely, implement this feature upstream. Thanks for the report!
Is this even something that even erlang could easily fix? The shell is making cat take ownership of the stdin pipe...
But two workaround I can see:
You could always use a .iex.exs file or whatever it was to feed in an initial string of commands.
Use expect to pass in the file then give you stdin to interact with, like via:
╰─➤ cat t.exs
add = fn(a, b) -> a + b end
╰─➤ expect -c "spawn iex;send [exec cat t.exs];interact"
spawn iex
add = fn(a, b) -> a + b end
Erlang/OTP 21 [erts-10.1.1] [source] [64-bit] [smp:6:6] [ds:6:6:10] [async-threads:1] [hipe]
Interactive Elixir (1.7.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> add = fn(a, b) -> a + b end
#Function<12.128620087/2 in :erl_eval.expr/5>
iex(2)> add.(1,2)
3
I'm surprised at how often people forget that "expect" exists, I use the heck out of it. ^.^
You can easily encode that expect command into a shell function.
You can also write a shell script that juggles the standard pipes too, not too hard but a good bit more verbose with a couple subshells spawned.
@OvermindDL1
The first workaround does not work for all cases because .iex.exs is compiled before it is executed, so it will not run if you define there a module with a macro, and then try to require the module and use its macro, also inside the file. It will generate a compilation error. For example, given .iex.exs
defmodule X do
defmacro hello do
quote do
alias Foo.Bar
IO.puts("Hello World")
end
end
end
require X
X.hello
it will generate a compilation error
** (CompileError) iex:10: module X is not loaded but was defined. This happens when you depend on a module in the same context in which it is defined. For example:
...
If the module is defined at the top-level and you are trying to use it at the top-level, this is not supported by Elixir
...
Now the second workaround .... is brilliant! It sends to IEx real strings as if they were typed in the prompt, like i Foo, which is not accepted inside an Elixir file. For example,
$ expect -c "spawn iex;send [exec cat x.exs];interact"
spawn iex -S mix
defmodule X do
defmacro hello do
quote do
alias Foo.Bar
IO.puts("Hello World")
end
end
end
require X
X.helloErlang/OTP 21 [erts-10.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]
Interactive Elixir (1.7.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> defmodule X do
...(1)> defmacro hello do
...(1)> quote do
...(1)> alias Foo.Bar
...(1)> IO.puts("Hello World")
...(1)> end
...(1)> end
...(1)> end
{:module, X,
<<70, 79, 82, 49, 0, 0, 4, 192, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 127,
0, 0, 0, 13, 8, 69, 108, 105, 120, 105, 114, 46, 88, 8, 95, 95, 105, 110,
102, 111, 95, 95, 7, 99, 111, 109, 112, ...>>, {:hello, 0}}
iex(2)>
nil
iex(3)> require X
X
iex(4)> X.hello
Hello World
:ok
iex(5)>
This workaround works most of the time, but some times it doesn't and I do not know why because I am not as a Unix guru as you ;-)
$ expect -c "spawn iex;send [exec cat x.exs];interact"
spawn iex -S mix
defmodule X do
defmacro hello do
quote do
alias Foo.Bar
IO.puts("Hello World")
end
end
end
require X
X.helloErlang/OTP 21 [erts-10.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]
Interactive Elixir (1.7.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
This workaround works most of the time, but some times it doesn't and I do not know why
What shell are you using? I'm guessing it's sending too fast. ^.^;
If that is the case then just have it wait for the iex prompt by adding the expect iex command to it like: expect -c "spawn iex;expect iex;send [exec cat t.exs];interact"
And yes, I was trying to keep the command simple but I failed, you should always always always wait for an input line before sending data with expect... ^.^;
Now is perfect! I suspected about that "sending too fast". I tried just expect; and then sleep 2;, but I missed the need for iex in expect iex;. Perfect, and a new nice tool in my Unix arsenal.
And by the way, I am running inside docker container based on Elixir 1.7 image. Brutal!
Thanks a lot.
@OvermindDL1 I am trying to create a function to automate this. The skeleton is
function iex() {
expect -c "spawn iex ARGS;expect iex;send [exec cat iex.exs];interact"
}
and I want to call it, for example, as iex or iex -S mix, and have the empty string or -S mix passed where ARGS is above.
How can I do this function?
Found the solution:
function iex() {
args=$@
expect -c "spawn iex $args;expect iex;send [exec cat iex.exs];interact"
}
It seems I have a new and more powerful IEx.
Best Regards @OvermindDL1
Yep that solution would work as long as you don't need important spacing between the arguments. :-)
Also I wouldn't really name the function as iex or you'll have to exec iex any time you want to access elixir's iex directly, and I'm not sure if expect might end up calling it via the shell or not. ^.^
You could also just pop the first arg off the argument list and use it as the function name too.
Or even better, make the expect script it's own callable script and do it all inside it (it is tcl'ish), like put this in a file named something like iexf on your path and make it executable:
#!/usr/bin/expect --
spawn iex {*}[lrange $argv 1 end]
expect iex
send [exec cat [lindex $argv 0]]
interact
Then just call it like iexf iex.exs whatever other elixir args here.
:-)
@OvermindDL1 I followed your advice and implemented the iexf ... command, but using a Bash function to avoid adding an extra file.
Thanks again. :-)