DEV Community

Cover image for A Little Story About the 'no' Command
Takuya Aoki
Takuya Aoki

Posted on

A Little Story About the 'no' Command

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
...
Enter fullscreen mode Exit fullscreen mode

Alternatively,

> no | sh work_a_lot.sh
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Project creation

> mix new no
> cd no
Enter fullscreen mode Exit fullscreen mode

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 use escript
  • 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Build

When you have finished implementing build.
Run in a directory with mix.exs.

> mix escript.build
Enter fullscreen mode Exit fullscreen mode

Executable file should be generated in the current directory.

Execution

In the current directory.

> ./no -p 100
n
n
n
n
n
...
Enter fullscreen mode Exit fullscreen mode

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>}
...
Enter fullscreen mode Exit fullscreen mode

Gotcha!!!!

How fast is it?

> ./no | pv -r > /dev/null
[93.7 KiB / s]
Enter fullscreen mode Exit fullscreen mode

As for the yes command,

> yes | pv -r > /dev/null
[31.6 MiB / s]
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

...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)

Collapse
 
mattn profile image
Yasuhiro Matsumoto

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.

Collapse
 
tokoyax profile image
Takuya Aoki

I didn't know that IO.puts takes such a cost. I'll try to use buffer with Elixir. Thanks for your advice!!

Collapse
 
joshcheek profile image
Josh Cheek

Regarding performance, this was a pretty enlightening post: reddit.com/r/unix/comments/6gxduc/...

Collapse
 
tokoyax profile image
Takuya Aoki

Thanks! The 10GiB/s speed is awesome.