How do you read from stdout of an external program in Erlang? - erlang

Here's the code I wrote:
-module(comments).
-record(comment, {user, contents, id, path, sub_comments}).
-export([sort_comments/1]).
comments2csv(Port, []) -> Port ! {self(), close};
comments2csv(Port, [#comment{user=User, contents=Contents, id=ID, path=Path}|Rest]) when is_integer(User) ->
Port ! {self(), {command, list_to_binary(Path ++ ":" ++ integer_to_list(User) ++ ":" ++ integer_to_list(ID) ++ ":" ++ Contents ++ "\n")}},
comments2csv(Port, Rest).
sort_comments(Comments) ->
Comments2 = [#comment{user=1, contents="hello", id=1, path="/1/", sub_comments=[]}],
Port = open_port({spawn_executable, "./comsort"}, [binary]),
comments2csv(Port, Comments2),
receive
{Port, {data, Data}} ->
io:format("~p~n", [Data]);
{Port, closed} ->
io:format("closed~n"),
receive
X ->
io:format("~p~n", [X])
end
end.
It calls up an external program written in Haskell, and when I run it from the shell it prints "closed" and then hangs. I can't for the life of me figure out why it's not reading the output from the stdout of the program. The Haskell program is set up to print XML data to stdout when it's done receiving CSV data from stdin, and then it exits.
I loosely based it off of the tutorial here:

Ok, so I found this which has the options that I need to give to open_port in order to receive stuff from stdout. Now it works.
Edit: Nevermind. It seems that what I'm trying to do is impossible. Erlang doesn't let you close the file descriptor for stdin to a program called externally so comsort can't ever gather up all of the input and then send out the xml at the end because it never sees an end to the input.

Related

Can not spawn function on remote node with spawn(Node, Fun) in erlang

experimenting with distributed erlang, here's what I have:
loop()->
receive {From, ping} ->
io:format("received ping from ~p~n", [From]),
From ! pong,
loop();
{From, Fun} when is_function(Fun) ->
io:format("executing function ~p received from ~p~n", [Fun, From]),
From ! Fun(),
loop()
end.
test_remote_node_can_execute_sent_clojure()->
Pid = spawn(trecias, fun([])-> loop() end),
Pid ! {self(), fun()-> erlang:nodes() end},
receive Result ->
Result = [node()]
after 300 ->
timeout
end.
getting: Can not start erlang:apply,[#Fun<tests.1.123107452>,[]] on trecias
node I execute the test on runs on the same machine as the node 'trecias'. Both nodes can load same code.
Any ideas what is amiss?
In the spawn call, you've specified the node name as trecias, but you need to specify the full node name including the hostname, e.g. trecias#localhost.
Also, the function you pass to spawn/2 must take zero arguments, but the one in the code above takes one argument (and crashes if that argument isn't the empty list). Write it as fun() -> loop() end instead.
When spawning an anonymous function on a remote node, you also need to make sure that the module is loaded on both nodes, with the same version. Otherwise you'll get a badfun error.

Erlang: How to pipe stdin input from a file to an erlang program and match eof?

How to pipe input from a file as stdin to an erlang program running in the shell as well as standalone?
I have a file hr.erl and I compile it from the shell. There is a function in it which accepts input from stdin using io:fread(). I have written a case expression with guards where if it matches {ok, [0]} it should terminate. Instead of 0, I actually need it to be eof.
How to send eof when running in a shell?
I have a file inp.txt with values 1 2 3 and 0 on each line. How can I pass it using the < pipe operator? Is there anything like erl -hr <inp.txt? Can I pipe it to stdin within the shell.
Here is my program so far (updated to include eof).
-module(hr).
-export([main/0]).
r(L) ->
case io:fread("", "~d") of
eof ->
io:format("eof~n", []),
ok;
{ok, [0]} ->
io:format("terminate~n", []),
lists:reverse(L);
{ok, [N]} ->
io:format("inp ~p~n", [N]),
r([N|L])
end.
main() -> r([]).
From shell
1> c(hr).
{ok,hr}
2> hr:main().
1
inp 1
2
inp 2
3
inp 3
0
terminate
[1,2,3]
Thanks.
This is one of the Erlang FAQs:
http://www.erlang.org/faq/how_do_i.html#id49435
Look here for the fastest line oriented IO for Erlang known to me. Note the usage of -noshell and -noinput command line parameters. Key part is
read() ->
Port = open_port({fd, 0, 1}, [in, binary, {line, 256}]),
read(Port, 0, [], []).
read(Port, Size, Seg, R) ->
receive
{Port, {data, {eol, <<$>:8, _/binary>> = Line}}} ->
read(Port, Size + size(Line) + 1, [],
[iolist_to_binary(lists:reverse(Seg, [])) | R]);
{Port, {data, {eol, Line}}} ->
read(Port, Size + size(Line) + 1, [Line | Seg], R);
{'EXIT', Port, normal} ->
{Size, [list_to_binary(lists:reverse(Seg, [])) | R]};
Other ->
io:format(">>>>>>> Wrong! ~p~n", [Other]),
exit(bad_data)
end.
EDIT: Note that the line oriented IO was fixed in R16B http://erlang.org/pipermail/erlang-questions/2013-February/072531.html so you need this trick no longer.
EDIT2: There is an answer using fixed file:read_line/1.
I am able to pipe input when using escript. Write the erlang program without module or export info, with main(_) function, i.e., in escript compatible way. Then we can pipe input using cat like
cat inp.txt | escript hr.erl
This works and program terminates when it encounters eof. But I still don't know why it's not working when using redirect operator <.

Writing Meck testcases for gen_tcp function

Here is a simple IRC bot module written by Erlang:
IRC Bot
Could someone helps me write the testcase for the function connect and parse_line with MECK
connect(Host, Port) ->
{ok, Sock} = gen_tcp:connect(Host, Port, [{packet, line}]),
% According to RFC1459, we need to tell the server our nickname and username
gen_tcp:send(Sock, "NICK " ++ ?nickname ++ "\r\n"),
gen_tcp:send(Sock, "USER " ++ ?nickname ++ " blah blah blah blah\r\n"),
loop(Sock).
parse_line(Sock, [User,"PRIVMSG",Channel,?nickname|_]) ->
Nick = lists:nth(1, string:tokens(User, "!")),
irc_privmsg(Sock, Channel, "You talkin to me, " ++ Nick ++ "?");
parse_line(Sock, [_,"376"|_]) ->
gen_tcp:send(Sock, "JOIN :" ++ ?channel ++ "\r\n");
parse_line(Sock, ["PING"|Rest]) ->
gen_tcp:send(Sock, "PONG " ++ Rest ++ "\r\n");
parse_line(_, _) ->
0.
Thanks you very much, I have already know how to use MECK to write some simple Erlang testcases about input/ otput, lists...but this IRC bot seems tobe quite beyond my current ability.
I would suggest splitting your parsing code and output code from the rest of your logic. There is hardly any reason to test such low-level functionality, but if you added functions "in between" the low-level interface and your code, you could easily write test cases without even using Meck.

Can't spawn with number parameter?

I'm a beginner at Erlang and I've been working through "Learn You Some Erlang For Great Good!". I use a modified version of this example code where the critic has a parameter:
critic(Count) ->
receive
{From, {"Rage Against the Turing Machine", "Unit Testify"}} ->
From ! {self(), {"They are great!", Count}};
{From, {"System of a Downtime", "Memoize"}} ->
From ! {self(), {"They're not Johnny Crash but they're good.", Count}};
{From, {"Johnny Crash", "The Token Ring of Fire"}} ->
From ! {self(), {"Simply incredible.", Count}};
{From, {_Band, _Album}} ->
From ! {self(), {"They are terrible!", Count}}
end,
critic(Count).
Which is spawned like this:
restarter() ->
process_flag(trap_exit, true),
Pid = spawn_link(?MODULE, critic, [my_atom]),
register(critic, Pid),
receive
{'EXIT', Pid, normal} -> % not a crash
ok;
{'EXIT', Pid, shutdown} -> % manual termination, not a crash
ok;
{'EXIT', Pid, _} ->
restarter()
end.
The module is used like this:
1> c(linkmon).
{ok,linkmon}
2> Monitor = linkmon:start_critic().
<0.163.0>
3> linkmon:judge("Rage Against the Turing Machine", "Unit Testify").
{"They are great!",my_atom}
Now, when I change "my_atom" to a simple number (like 255) the monitor crashes:
1> c(linkmon).
{ok,linkmon}
2> Monitor = linkmon:start_critic().
=ERROR REPORT==== 14-Jul-2013::20:42:20 ===
Error in process <0.173.0> with exit value: {badarg,[{erlang,register,[critic,<0.174.0>] []},{linkmon,restarter,0,[{file,"linkmon.erl"},{line,16}]}]}
However, it does work when I send [1] (so the code is "spawn(....., [[255]]).")
Why can't I pass a single number? Is just skimming over the documentation of spawn/3 doesn't really tell me anything... except maybe that I missed something and a number is not an Erlang term. But then how do I pass a number?
The error message says that the call to register(critic, Pid) on line 16 crashes due to "badarg" even though the arguments look ok. This can happen if the process referred to by Pid is already dead (if it crashes immediately, e.g. if you pass the wrong number of args), or if you already have a process around using that name. Ensure that the length of the list in the spawn(Mod,Fun,[...]) matches the number of args to your critic() function, and call "whereis(critic)" in the shell to check if there's an old process blocking the name from being reused.

ERLANG - Pattern Matching

I have a variable:
Data = [[<<>>,
[<<"10">>,<<"171">>],
[<<"112">>,<<"Gen20267">>],
[<<"52">>,<<"20100812-06:32:30.687">>]]
I am trying to pattern match for two specific cases..
One where anything that resembles the outside structure - simply []
Anything inside goes I have tried [ _ ] but no go?
The Second, for a specific pattern inside, like when I see a <<"10">> or <<"112">> or <<"52">> then I am going to take the right side which is the actual data into an atom.
Basically the <<"10">> or <<"112">> or <<"52">> are the fields, the right side the data.
I have tried statements like [<<"10">>, _ ] still no go
Here is the rest of the code:
dataReceived(Message) ->
receive
{start} ->
ok;
[ _ ] -> %%No go
io:format("Reply 1 = ~p~n", [Message]);
[<<"10">>, _ ] -> %%No go
io:format("Reply 1 = ~p~n", [Message])
end.
As a note the Message is not sent as a tuple it is exactly like Data =
Can anyone lead me in the right direction?
Thanks and Goodnight!
-B
UPDATE
Ok now I think Im getting warmer, I have to pattern match whatever comes in.
So if I had say
Message = = [[<<>>],
[<<"10">>,<<"171">>],
[<<"112">>,<<"Gen20267">>],
[<<"52">>,<<"20100812-06:32:30.687">>]]
And I was looking to pattern match the field <<"112">>
Such as the 112 is always going to say 112, but the Gen2067 can change whenever to whatever.. its the data, it will be stored in a variable.
loop() ->
receive
[_,[<<"112">>, Data], _] when is_list(X) -> %% Match a list inside another.
?DEBUG("Got a list ~p~n", [X]),
loop();
_Other ->
?DEBUG("I don't understand ~p~n", [_Other]),
loop()
end.
I feel im close, but not 100%
-B
Update OP is trying to pass an argument to the function and not send messages.
As the name indicates the receive block is used to receive and process messages sent to a process. When you call dataReceived with an argument it proceeds to wait for messages. As no messages are sent it will continue to wait endlessly. Given the current code if you want it to do something then you'll have to spawn the function, get the process ID and then send a message to the process ID.
You probably need a function where the argument is pattern matched and not messages.
Something like this:
dataReceived([Message]) when is_list(Message) ->
io:format("Got a list as arg ~p~n", [Message]);
dataReceived(_Other) ->
io:format("Unknown arg ~p~n", [_Other]).
On a side note your third pattern [X] when is_list(X) will never match as the second pattern is a superset of the third. Anything that matches [X] when is_list(X) will always match [X] and therefore your third match clause will never get triggered.
Original Answer
I am not sure I understand your question. Are you trying to send a message to the function or are you passing it an argument?
This is a partial answer about how to match a list of lists in case you are sending a message.
-module(mtest).
-export([run/0]).
-ifdef(debug).
-define(DEBUG(Format, Args), io:format(Format, Args)).
-else.
-define(DEBUG(Format, Args), void).
-endif.
loop() ->
receive
[X] when is_list(X) -> %% Match a list inside another.
?DEBUG("Got a list ~p~n", [X]),
loop();
_Other ->
?DEBUG("I don't understand ~p~n", [_Other]),
loop()
end.
Take a look at the first clause in the receive block. [X] when is_list(X) will bind the inner list to the name X. I tested it with the value of Data you provided and it worked.
%% From the shell.
1> c(mtest, {d, debug}).
{ok,mtest}
2> Pid = mtest:run().
<0.40.0>
3> Data = [[<<>>, [<<"10">>,<<"171">>], [<<"112">>,<<"Gen20267">>], [<<"52">>,<<"20100812-06:32:30.687">>]]].
[[<<>>,
[<<"10">>,<<"171">>],
[<<"112">>,<<"Gen20267">>],
[<<"52">>,<<"20100812-06:32:30.687">>]]]
4> Pid ! Data.
[[<<>>,
[<<"10">>,<<"171">>],
[<<"112">>,<<"Gen20267">>],
[<<"52">>,<<"20100812-06:32:30.687">>]]]
Got a list [<<>>,
[<<"10">>,<<"171">>],
[<<"112">>,<<"Gen20267">>],
[<<"52">>,<<"20100812-06:32:30.687">>]]
5>

Resources