DEV Community

Sushant Bajracharya
Sushant Bajracharya

Posted on • Edited on

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

Top comments (0)