Inspired by this article, I made similar commands as Elixir exercises.
http://postd.cc/a-little-story-about-the-yes-unix-command/
This is the 'no' command to deny all of the world!!!
The specification is almost same as yes
. Continue to output n
to the standard output forever.
It makes it possible to use it like this.
> no
n
n
n
n
n
n
n
...
Alternatively,
> no | sh work_a_lot.sh
Your annoying boss said, "Work harder!! Work harder!!!"
OMG...It's the way to 'KAROSHI'.
This command should be useful at such times :D
Let's start
Environment
> elixir -v
[Async - threads: 10] [hipe] [kernel - poll: kernel - poll: kernel - poll: false] [dtrace]
Elixir 1.5.2
Project creation
> mix new no
> cd no
Edit mix.exs
To create an executable file with Elixir, you can use escript
.
http://erlang.org/doc/man/escript.html
-
line: 9
Specify module to useescript
-
line: 22-24
It is better to write a function that returns the module name etc.
mix.exs
defmodule No.Mixfile do
use Mix.Project
def project do
[
app: :no,
version: "0.1.0",
elixir: "~> 1.5",
escript: escript_config(),
start_permanent: Mix.env == :prod,
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger]
]
end
defp escript_config do
[main_module: No]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},
]
end
end
No.main/1
of the module specified here becomes the entry point.
Implementation
It's not surprising that it just loops normally.I added an option -p
that generates a lot of processes and keeps outputting n
for each.Also, I made it possible to see the PID with -d
so that I could observe the parallel execution.
OptionParser
Conveniently useful!
no.exs
defmodule No do
@moduledoc """
Documentation for No.
"""
def main(args) do
args
|> parse_args()
|> run()
|> wait()
end
def say(args) do
{argv, process_num, sender, debug} = args
case debug do
true -> send sender, {:debug, "#{expletive(argv)} from process no.#{process_num}", self()}
_ -> send sender, {:ok, expletive(argv)}
end
say(args)
end
defp expletive(_ = ''), do: 'n'
defp expletive(value), do: value
defp parse_args(args) do
{opts, argv, _} = OptionParser.parse(
args,
swithces: options_list(),
aliases: aliases_list()
)
{argv, opts}
end
defp options_list() do
[
processes: :integer,
debug: :boolean,
]
end
defp aliases_list() do
[
p: :processes,
d: :debug,
]
end
defp run(args) do
{argv, opts} = args
num = case opts[:processes] do
nil -> 1
_ -> String.to_integer(opts[:processes])
end
(1..num)
|> Enum.each(
fn(n) ->
spawn_link(No, :say, [{argv, n, self(), opts[:debug]}])
end
)
end
defp wait(args) do
receive do
{:ok, message} ->
IO.puts message
{:debug, message, pid} ->
IO.inspect {message, pid}
end
wait(args)
end
end
Build
When you have finished implementing build.
Run in a directory with mix.exs
.
> mix escript.build
Executable file should be generated in the current directory.
Execution
In the current directory.
> ./no -p 100
n
n
n
n
n
...
Debug option available.
>.. / no - p 100 - d
{"n from process no. 47", # PID <0.122.0>}
{"n from process no. 47", # PID <0.122.0>}
{"n from process no. 47", # PID <0.122.0>}
{"n from process no. 40", # PID <0.115.0>}
{"n from process no. 47", # PID <0.122.0>}
...
Gotcha!!!!
How fast is it?
> ./no | pv -r > /dev/null
[93.7 KiB / s]
As for the yes
command,
> yes | pv -r > /dev/null
[31.6 MiB / s]
no
command is toooo slow!!!! xD
Calm down, I believe it will reverse in parallel processing!!
>./no - p 100 | pv -r > /dev/null
[1.12 KiB / s]
...Why?
Summary
- It is good to reimplement the Linux command as a language practice subject
- Many of the functions are reasonably sized
- It is pleasant to go through processing with *
|>
- I don't know why performance is slow.
- I will investigate later.
- Registration to Hex
- Registered -> http://blog.tokoyax.com/entry/elixir/register-to-hex
Github Repo: https://github.com/tokoyax/no
This article is translated from Japanese origin.
http://blog.tokoyax.com/entry/elixir/no-command
(reference)
Top comments (4)
Sorry if you know why this slow, calling
IO.puts
every times make performance wrong. system call block until finish of I/O. I'm not good at Elixir, but you've better to use buffer for writing. Actually, yes command does not call write(2) for each lines.I didn't know that
IO.puts
takes such a cost. I'll try to use buffer with Elixir. Thanks for your advice!!Regarding performance, this was a pretty enlightening post: reddit.com/r/unix/comments/6gxduc/...
Thanks! The 10GiB/s speed is awesome.