8

I wanted to achieve the same as asked here Saving current directory to bash history but within Zsh shell. I haven't done any Zsh trickery before but so far I have:

function precmd {
  hpwd=$history[$((HISTCMD-1))]  
  if [[ $hpwd == "cd" ]]; then  
    cwd=$OLDPWD
  else
    cwd=$PWD
  fi
  hpwd="${hpwd% ### *} ### $cwd"
  echo "$hpwd" >>~/.hist_log
}

Right now I save the command annotated with the directory name to a log file. This works fine for me. Just thought there might be a way to make the same replacement in the history buffer itself.

Victor Schröder
  • 6,738
  • 2
  • 42
  • 45
Kabira K
  • 1,916
  • 2
  • 22
  • 38
  • 3
    possible duplicate of [How can I (from a script) add something to the zsh command history?](http://stackoverflow.com/questions/2816225/how-can-i-from-a-script-add-something-to-the-zsh-command-history) – Dennis Williamson May 13 '10 at 11:02
  • You could consider an independent project that supports saving the path for each command: https://github.com/chrissound/MoscoviumOrange – Chris Stryczynski Dec 08 '19 at 11:19

4 Answers4

8
function _-accept-line() {
    [[ -z "${BUFFER" ]] || [[ "${BUFFER}" =~ "### ${(q)PWD}\$" ]] || BUFFER="${BUFFER} ### ${PWD}"
    zle .accept-line
}
zle -N accept-line _-accept-line

Will add ### ${PWD} to your command line. Not the best solution you could use, but it works.

UPD: Answer based on @Dennis Williamson's comment:

function zshaddhistory() {
    print -sr "${1%%$'\n'} ### ${PWD}"
    fc -p
}
ZyX
  • 52,536
  • 7
  • 114
  • 135
  • Thanks Dennis and Zyx. It works as expected. Dennis, it is a duplicate. Somehow that question didn't show up during my search. Your article on bash/zsh history is helpfule and informative. – Kabira K May 13 '10 at 20:06
  • 1
    Can someone explain to me why `fc -p` is needed? Why is it that when I do not place `fc -p` here, the history list gains two entries for each command (the first being the one including the path, and the second entry is plain one)? – Steven Lu Jul 08 '13 at 21:30
  • I was struggling with zsh trying to parse the `#` characters, but switching on `setopt interactivecomments` addressed that and I am happy with this setup now. It bloats the command history a good bit as you traverse it though, which makes me sad. – Steven Lu Jul 09 '13 at 01:55
  • This has a problem with searching back history - all you entries still have the comments. If you just do ctrl-r, something, enter you get duplicated entries... – rkj Aug 27 '13 at 21:40
  • See Adam Cooper answer for better solution – user3041539 May 08 '22 at 12:55
3

Rather than store it on every command I added the following to the beginging of my precmd() function to store it when I change directories:

    if [ "$LAST_DIR" != "$PWD" ]
    then
            print -s "##dir## $PWD"
            LAST_DIR=$PWD
    fi

Adds a '##dir## dir name' standalone line to the history each time a command is run from a new directory.

MerlinMM
  • 31
  • 3
  • 1
    I like `print -s "$PWD # Path"`, this way I can bring up this history entry all normal-like, and it's ready for me to hit Enter to auto-cd to that path (this is a feature of `zsh`, if you just provide a path it `cd`/`pushd`'s it for you). **HOWEVER** I do believe this approach is fundamentally flawed: If you have two terms open, each in different dir, then keep their respective dirs, but switch between them issuing commands, it is not possible to know which dir those commands happened in! This is fail :( – Steven Lu Jul 09 '13 at 01:50
  • @StevenLu: There's actually a built-in zsh function for when the PWD is changed: *chpwd* So I just added my code there. FYI - I used your idea of *$PWD #Change WD* instead of the entire line being a comment so that I can use the history entry to return to the relevant directory. I used 'Change WD' instead of 'Path' so that backwards history searches don't have so many false negatives. I use this in a Cygwin term on a windows machine where I only ever run the 1 terminal so your fail case doesn't bother me. – MerlinMM Mar 24 '21 at 14:53
  • The thing I made back in 2013 was an "extended" history file of my own devising where the pwd of the command in question is always logged. It could be optimized to a log for only when it changes, as well, since I track the tty/pty in that history too. But capturing all the metadata all the time has been working out very well for me. – Steven Lu Mar 24 '21 at 23:52
2

The following adaptation of ZyX's solution resolves an issue whereby reusing history commands with ⍐ (UpArrow) leads to a pile-up of ### $PWD in the history:

function zshaddhistory() {
  history_item="${${1%%$'\n'}%%$' ###'*} ### ${PWD}"
  print -sr ${(z)history_item}
  fc -p
  return 1
}

It strips any existing PWD-comment before adding the current one. This way I can freely hit the UpArrow, execute an old command without backspacing through the comment, and the new history item will only have the one comment with the current working directory.

Adam Cooper
  • 182
  • 13
1

I modified zsh's source code; the history file like:

: 1685803508:/home/gogogo/abc/;tail -f ~/.histfile

don't change any previous things

diff --git a/Src/hist.c b/Src/hist.c
index bff0abe..9de1a3d 100644
--- a/Src/hist.c
+++ b/Src/hist.c
@@ -29,6 +29,7 @@
 
 #include "zsh.mdh"
 #include "hist.pro"
+#include <unistd.h>
 
 /* Functions to call for getting/ungetting a character and for history
  * word control. */
@@ -3027,9 +3028,11 @@ savehistfile(char *fn, int err, int writeflags)
                histfile_linect++;
            }
            t = start = he->node.nam;
+           char cwd[PATH_MAX];
+           getcwd(cwd, sizeof(cwd));
            if (extended_history) {
-               ret = fprintf(out, ": %ld:%ld;", (long)he->stim,
-                             he->ftim? (long)(he->ftim - he->stim) : 0L);
+               ret = fprintf(out, ": %ld:%s;", (long)he->stim, cwd);
+                             // he->ftim? (long)(he->ftim - he->stim) : 0L);
            } else if (*t == ':')
                ret = fputc('\\', out);
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
augusto
  • 11
  • 3