DEV Community

Sushant Bajracharya
Sushant Bajracharya

Posted on • Edited on

3

Emulate bash "history" but for iex

Personally, I think the bash command, history, is a neat productivity tool. I use it all the time history | grep and I wanted the same thing in IEx as well.

To search through the history in IEx, you need to first enable it. Then either use up arrow keys or ctrl + r to search. That's cool but still not exactly what I need.

So, the first thing I had to do was to find the place where the history is stored.

# run it in your IEx
:filename.basedir(:user_cache, "erlang-history")

The history files are written by erlang's disk_log, so there will be weird characters when you open it in your editor. My initial thought was, if the history is written by disk_log:log/2 which uses erlang:term_to_binary/1, then I can read those history files with erlang:binary_to_term/1. But it turns out when writing history, disk_log appends some kind of weird binaries which you can find in disk_log.hrl eg <<12,33,44,55>>. So, I tried disk_log:chunk/2 to parse it.

# this will print the content of the history file with header
# in IEx, it will print charlists
# in erl, it will print strings

:disk_log.chunk(:'$#group_history', :start)

It did parse the history but it had weird headers and also didn't give me the whole content of all the history files.

{{:continuation, #PID<0.81.0>, {1, 52393}, []},
 [
   {:vsn, {0, 1, 0}},

I found a file called group_history.erl. It has two public api load/0 and 'add/2'. The load/0 api parsed and returned the whole history just as I wished.

:group_history.load()

And then I finally wrote the rest of the code to emulate bash history command

defmodule History do
  def search(term) do
    load_history()
    |> Stream.filter(&String.match?(&1, ~r/#{term}/))
    |> Enum.reverse()
    |> Stream.with_index(1)
    |> Enum.each(fn {value, index} ->
      IO.write("#{index}  ")
      IO.write(String.replace(value, term, "#{IO.ANSI.red()}#{term}#{IO.ANSI.default_color()}"))
    end)
  end

  def search do
    load_history()
    |> Enum.reverse()
    |> Stream.with_index(1)
    |> Enum.each(fn {value, index} ->
      IO.write("#{index}  #{value}")
    end)
  end

  defp load_history, do: :group_history.load() |> Stream.map(&List.to_string/1)
end

I have put this code in my .iex.exs file so that I can call it whenever I am in my IEx.

History.search("map")
History.search()

Hi there!!

Hi, I am looking for awesome opportunities to work with Elixir. If you are hiring or need a hand to finish your pet/side project then let's get connected. Here is my email sussyoung9[at]gmail[dot]com

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay