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.