Hacker News new | past | comments | ask | show | jobs | submit login
Jog: Print the last 10 commands you ran in the current directory (github.com/natethinks)
250 points by _lx4l on Nov 8, 2021 | hide | past | favorite | 73 comments



Here is a zsh function (added to ~/.zshrc) I made for use with https://github.com/larkery/zsh-histdb to get the same effect, but only showing commands whose exit_status is 0:

  jog() {
      sqlite3 $HOME/.histdb/zsh-history.db "
  SELECT
      replace(commands.argv, '
  ', '
  ')
  FROM commands
  JOIN history ON history.command_id = commands.id
  JOIN places ON history.place_id = places.id
  WHERE history.exit_status = 0
  AND dir = '${PWD}'
  AND places.host = '${HOST}'
  AND commands.argv != 'jog'
  AND commands.argv NOT LIKE 'z %'
  AND commands.argv NOT LIKE 'cd %'
  AND commands.argv != '..'
  ORDER BY start_time DESC
  LIMIT 10
  "
  }


I love having stuff like this in zsh except that I very often have commands that don't exit with 0 either because I made a mistake, or because of something happening like referencing a file that might have been there but wasn't.

I understand not wanting to clog up your history with typos, but some configs even default to not being able to press up-arrow to get a command that I just mistyped.


The function lrobinovitch showed is not overwriting your history, it just shows the successful commands when running `jog`. Your up/down-arrow and ctrl+p will still work like normal, typos or not.

But if you did do a mistake in your command, like referring to a file that doesn't exist, don't you rerun the command again but with the correct parameters? Then it would be included there as well (when running `jog`).

Otherwise, it's trivial to remove the WHERE statement so it shows all the commands, seems zsh-histdb stores everything, successful or not.


Indeed not. In fact the issue would be with some history never getting recorded at all. Thank you for clarifying though, that the dir-based history tools don't affect the existing history.

The example I was trying to explain above referred to deliberately referencing a filename which may not correspond to an existing file. Like I might stat a file, to know when it was created, but if it hasn't been created yet then that's an answer too.


zsh-histdb is such a gem and I wish was just the default everywhere.


I looked it up and the installation seems pretty involved, so I gave up.


Involved? Love to hear how it could be easier, requires just about 3 steps for download+installation+usage

1. Download the `sqlite-history.zsh` file to somewhere (or use oh-my-zsh)

2. Source that file in your `.zshrc`

3. Run `autoload -Uz add-zsh-hook` in your `.zshrc`

That's it. I can't imagine a way it could be easier in fact.


Looks like you have to do more stuff on mac


> Looks like you have to do more stuff on mac

Are you referring to the "Note for OS X users" section in the README? If so, it's only additional step, so 4 in total... I know macOS users might not be the most technical, but I think most of them (at least developers on macOS) should be able to follow it :)


I'm an assisted human being, I need instructions like these :D https://github.com/zsh-users/zsh-syntax-highlighting/blob/ma...

btw my comment was not here to tell people I was lazy. It was here as feedback. Users are lazy. Online businesses track and look to decrease the number of clicks that lead to a sale, there's no reason developer tools shouldn't try to do the same :)

(I know it's free, not complaining)


For what it's worth, I installed it with my zsh plugin manager without any issues. A simple case of adding it to the list:

  zinit light-mode for \
    zinit-zsh/z-a-as-monitor \
    zinit-zsh/z-a-patch-dl \
    reegnz/jq-zsh-plugin \
    larkery/zsh-histdb


This makes me wonder, is there a SQLite equivalent for JSON based data?


SQLite is a good equivalent :D https://www.sqlite.org/json1.html


The other tools mentioned are all good, theres also jiq, which lets you run jq interactively against an input until you get the incantation right, then confirm it to get the full output.

https://github.com/fiatjaf/jiq


For some JSON files, sqlite-utils' memory command works great. E.g.

    sqlite-utils memory 'SELECT * FROM a JOIN b USING (pkey)' a.json b.json
See https://sqlite-utils.datasette.io/en/stable/cli.html#queryin...


You could use jq to do something similar.


I've posted this here in a comment before, but I just made some small updates and figured I'd submit. A while back I wished I could see what commands I'd run in a directory I hadn't worked in for months. I found a couple projects that could accomplish this but consisted of thousands of lines of code and a database install. I wanted a more "unix" solution so I built this.


I like the symplicity.

Have you thought about bundling it up so it can be installed with antigen or oh-my-zsh[0]

EDIT: you'd basically just put the content in this comment[1] into a file called `jog.plugin.zsh`

[0] https://github.com/zsh-users/antigen/wiki/Development#notes-...

[1] https://news.ycombinator.com/item?id=29159563


This is for zsh.

If you are using bash, here's an alternative to save the commands, their start time, stop time, and eventual error code, in a SQLite database that you can query with fzy: https://github.com/csdvrx/bash-timestamping-sqlite

With the mappings, Ctrl-R will do a search history restricted to the current directory AND to successful execution (return == 0), while Ctrl-T will do a global history search for all directories and return values.

EDIT: to answer a question I've seen posted below, if you want to only navigate the commands from the current directory, do Ctrl-R then use the arrows: the command are by default sorted by the time they where previously run. If you then start typing letters, it will filter these commands using fzy.


IPython has a sqlite-backed history too. I manage to corrupt it frequently, presumably due to segfaults in extensions I'm developing, or because I rarely have fewer than 10 sessions open. I don't mind occasionally restarting IPython and very rarely nuking its history... but if that happened to me in bash, I'd be beyond frustrated. Is a non-concurrent database really a good idea here?


> non-concurrent database

What does this even mean? Is this IPython backed history using Write-Ahead Logging?

> I manage to corrupt it frequently, presumably due to segfaults in extensions

A C-based program can't defend itself against corrupted memory. Your logger would need to run in a separated process from your actual code.

https://www.sqlite.org/howtocorrupt.html#_memory_corruption


I always respect when people take the time to put a small but useful thing like this up in a repo by itself.

It has always been a frustration that the current working directory metadata isn't saved along with history entries, so you can never really reconstruct what was run.

Edit: I should really try out https://github.com/larkery/zsh-histdb. There's no reason a modern system's history shouldn't store start and finish timestamps, return code, current working directory, etc.


> It has always been a frustration that the current working directory metadata isn't saved along with history entries, so you can never really reconstruct what was run.

It is if you use fish shell.


I'm a Fish inside Iterm2 user, constantly with a four panel split screen, and I'm always slightly amazed that _something_ seems to even remember which pane I ran various commands in. I have no idea which is remembering though.


That's a cool idea. I have a similar, but less elegant setup: I just append every single command that I type to a global (synchronized between machines) history file, one file per month, then I use fzf to search through these when I need something (which is plugged into the usual ^R shell binding). Every command I've typed over the past couple of years is accessible immediately that way.

It's not scoped by directory though, but I'm not sure I'd personally need that feature.


Came here to write something along the lines of "useless for me, because I hardly ever use cd at all in order to get a history containing lots of useful full path references". But you're right, something modern could be far more elegant.

(but it won't happen, these days it's crazy rare to "dig in" in a dazzlingly customized environment, we'd rather tweak our skills to work on the widest range of default environments. Certainly not the best conditions for innovation)


> these days it's crazy rare to "dig in" in a dazzlingly customized environment

Speak for yourself. The time I spend working locally is vastly more than I spend ssh-ing into somewhere vanilla so it's worth making my environment as comfortable and full-featured as possible.

Edit:

> I hardly ever use cd… to get a history [with] path references

That’s one way of solving the problem, but then you miss out on great tools like direnv… and relative paths.


> It has always been a frustration that the current working directory metadata isn't saved along with history entries, so you can never really reconstruct what was run.

And git branch if applicable.


Yes, underrated idea. I recently kept a spreadsheet of commands I'd ran, which branch, and where the output went. It would be great to see this tracked.


I keep shell sessions logged to individual files, collected for years back. I often reuse past commands. But if the command run from a git folder it's hard to tell which branch. It's also important to use full path names.


Zsh-histdb is one of my absolute lifesavers. It's literally the first thing I install on new systems, after zsh itself and oh-my-zsh. I could never go back to working without it.



Thanks I'll check this out! Any other useful zsh plugins you want to call out? It's been my favorite shell for a while mostly because of OMZ but always interested in some new magic!


> It has always been a frustration that the current working directory metadata isn't saved along with history entries

Agreed. Wild that's not built in at this point.


This is one of the things that Fish shell gets right out of the box. Command autocomplete is per-directory, and shows you a preview of the commands as you type.


I had thought that too, but, interestingly enough, this doesn't seem to be the case. ~/.local/share/fish/fish_history does not include the cwd.

In fact it seems like Fish does something better: when adding to the history, it sees if any of the arguments to the command are valid file paths. These are stored separately. Fish will then autocomplete the command if detected paths are valid.

So for example if you run "git log somefile", whenever "somefile" is in your cwd you will get an autocompletion for that command.

This matches the documentation (https://fishshell.com/docs/current/interactive.html#history-...) which says "fish suggests commands as you type, based on command history, completions, and valid file paths".


Neat. That is even better.


We use a shell history per directory per user. This is very helpful, esp in distributed shared environments.

https://github.com/albertz/wiki/blob/master/shell.md#history


CTRL+R is a bigger timesaver for me, but I could see this being useful if my memory weren't as keyword-based.


I scrolled down looking for someone mentioning Ctrl-R. I rarely write down shell command lines, even very complex ones, since I usually can quickly (very quickly) access them from my shell history with just a few keystrokes after Ctrl-R. It is the single most useful and important tool in my shell toolbox.

It also obviates the need for many scripts and aliases I would otherwise write: Command lines can be (practically) arbitrarily long, and zsh at least has good multiline edit functionality (including just spawning an editor if you really need to). So there's a lot of "impromptu scripts", sometimes with different variations, in my shell's history.

Some other features I would not want to miss (one from zsh, the other also available in bash, I think), albeit less critical:

1. Alt-. (Alt dot) immediately recalls the last argument passed to the last command line. I use this very very often. "mkdir long_name" followed by "cd long_name" is just one example.

2. Alt-q makes the currently typed command line disappear. But the next time you press enter to get a new prompt, even if you have entered a different command line, it will reappear. Super useful if you remember that you want to do something else before completing your command, without opening another terminal (some things like changing the current directory or env variables can't even be done in another terminal). This can even be done multiple times, it's a stack.

Both need Alt behaving as "Meta" (a checkbox in macOS's Terminal.app for example), otherwise you can still type "ESC" followed by "." or "q", but that's two separate keystrokes.


Alt-. is in bash. I use it dozens of times per day, and its best feature is pressing it repeatedly to get arguments from earlier commands. Also if you press Alt-<number> before it, e.g. Alt-2, it inserts argument 2 (or whatever) from the previous command (0-indexed).

Alt-q isn't in bash, at least by default, but it sounds pretty useful. I accomplish a similar thing with Alt-#, call the (now commented) command back with Up after however many other commands, then Alt-3 Alt-# will uncomment and run it (any number works there, but 3 is on the same key as #.


I do the exact same thing you do, and sometimes I think about enabling comments so I can have arbitrary tokens to CTRL+R toward.

I have been using vi mode in zsh for so long, I never learned about Alt+.; thank you so much for introducing me!

bindkey -M viins '\e.' insert-last-word

https://stackoverflow.com/questions/34290179/get-last-argume...


For bash (without an sqlite database), you can add your cwd to your history file with:

  export PROMPT_COMMAND='hpwd=$(history 1); hpwd="${hpwd# *[0-9]*  }"; if [[ ${hpwd%% *} == "cd" ]]; then cwd=$OLDPWD; else cwd=$PWD; fi; hpwd="${hpwd% ### *} ### $cwd"; history -s "$hpwd"'
Then you can grep your history for your CWD.

This is a one line version of the following:

  hpwd=$(history 1)              # grab the most recent command
  hpwd="${hpwd# *[0-9]*  }"      # strip off the history line number
  if [[ ${hpwd%% *} == "cd" ]]   # if it's a cd command, we want the old directory
  then                           #   so the comment matches other commands "where *were\* you when this was done?"
      cwd=$OLDPWD
  else
      cwd=$PWD
  fi
  hpwd="${hpwd% ### \*} ### $cwd" # strip off the old ### comment if there was one so they 
                                 #   don't accumulate, then build the comment
  history -s "$hpwd"             # replace the most recent command with itself plus the comment
From here:

https://stackoverflow.com/questions/945288/saving-current-di...



Using fzf to power ^R (reverse history search) is such a huge productivity win. Also it has the side effect of showing you the last 10 commands without any other config or setup.


Have you tried Peco [1] in the past? Seems similar. I'd be curious to hear what people's experiences are of both technologies.

[1]: https://github.com/peco/peco


Thanks for posting that. Just installed it. Peco seems really neat.


By default that doesn't seem to pay attention to the current directory or the directories commands were run in. Is that a configuration option or something?


No but it would be very straightforward to do something like what the OP does, and then instead of using tail you just pipe the grep result for PWD into fzf. Which has some advantages: all commands instead of last n, plus of course the killer feature which is fuzzy matching.


I'm a fan of McFly, which tracks history across directories and drives Ctrl-R with it. Although it can be a bit non-deterministic, it does help really work better than grepping history.

https://earthly.dev/blog/command-line-tools/#mcfly


I have had this snippet in my .bashrc for ages:

  function cwd() {
    cd "$1" || return
    if [ -w "${PWD}/.bash_history" ]; then
      history -a
      history -c
      HISTFILE="${PWD}/.bash_history"
      history -r
    fi
    if [ -r "${PWD}/.bashrc" ]; then
      source "${PWD}/.bashrc"
    fi
  }
Yes, a contextual history is very valuable, but the context isn't necessarily tied to the directory I'm currently in. Like git does as well, I'd want an entire subtree to share the same history. I'm sure that there's more elegant solutions for it than having to explicitly use cwd instead of cd to change contexts, but so far it's served me well.


Also, another way to find past commands you run `history | grep <whatever-you-are-looking-for>` and works wonders; or as other users have mentioned, Ctrl-R works too.


I love how the whole thing is just two commands. You can actually just add this to your .zshrc to have the full functionality

    function zshaddhistory() {
        echo "${1%%$'\n'}|${PWD}   " >> ~/.zsh_history_ext
    }
    
    function jog() {
        grep -v "jog" ~/.zsh_history_ext | grep -a --color=never "${PWD}   " | cut -f1 -d"|" | tail
    }


I'm not sure I love littering the filesystem with dotfiles (it gives me svn PTSD). Although the advantage is that it'll stay in the directory if it's renamed/moved/compressed etc...

But then the inconvenient is that it'll stay in the directory if it's renamed/moved/compressed etc... Better be careful if you tend to input sensitive data on the command line.


it looks to be ~/.<dotfile> not ./.<dotfile> so i wouldn't worry there


Very cool! I built something in python a while back, to track each project's history by detecting if a virtual environment/venv is active: https://github.com/haraball/pystory


I am unsure how shell history works when there are many terminals open at once. The history within a single shell is not mixed up by this, but I have never learned what happens when the terminals closes. I often wondered what would be the ideal way for history to work with many shells.


I use GNU screen.

I use a script to name each GNU screen session "projectA", "sysadmin", etc.

Then in my .bash_profile, I set the Bash history to be a separate file in my ~/.bash_hist_dir subdirectory.

    if [ "x$SCREEN_SESSION_NAME" != "x" -a "x$WINDOW" != "x" ]; then
        if [ ! -d ~/.bash_hist_dir ]; then
            mkdir ~/.bash_hist_dir
        fi
        export HISTFILE=~/.bash_hist_dir/${SCREEN_SESSION_NAME}.${WINDOW}
    fi
So each separate window in each session gets a unique history file.

I have up to 10 windows per GNU screen session, and sometimes 6 to 8 terminal tabs.

Each separate GNU screen window is used for specific things: "edit", "build", "debug", etc.

This all makes it easy to maintain the context of all the things I work on.


The ideal way is an immediate commit to a globally shared database, otherwise each shell in each terminal will only able to access its own history.


This is cool but it really feels like this should be just one thing you can do easily, because you are storing your shell history in a relational database. As I’ve been meaning to start doing for ages, so maybe this will spur me.


I'm assuming this is part of how fish shell completion works, and now I'm interested to read more about exactly what it does.

It feels good enough that you might not need this, but maybe it could learn some tricks from this.


This is such a great idea, but what's even more useful would be to make arrow keys only traverse the commands ran in the current directory. Would there be any prior art?


I like it. I'll add that to the to-do list.


fzf[0] + per-directory-history[1] fill exactly this niche for me. ^R always shows the all commands run in the current directory, ordered by recency (and filterable).

[0]: https://github.com/junegunn/fzf [1]: https://github.com/jimhester/per-directory-history


I run my shells inside emacs and really like the fact that all my previous output is there in the buffer accessible to search/copying etc.


For PowerShell, the command is:

  h -c 10
As a nice bonus, you also get to see the runtime of those 10 commands, out-of-the-box.


But that one is not directory-specific, right?


You're right, my bad


Fantastic idea, will be adopting this.


with fzf, if you type ctrl-r, the last 10 history items pop up before you start searching


fish implementation

alias jog="history --max=10"


Does this only show for the current directory? It doesn't seem so from the documentation


No, just the last 10 commands, so almost totally different.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: