Exporting zsh history separated by ‘’ characters instead of ‘n’

I want to access zsh history with entries separated out by instead of n. The built-in parser can clearly handle multiline entries, because fc 123 brings up an editor with newlines preserved. However, fc -l 123 (which prints to stdout instead of starting an editor) converts newline characters to the equivalent of \n. This means that any commands in history containing the literal n cannot be disambiguated from newlines (e.g., printf "n" in an awk invocation).

Is there a way to get at unaltered history entries using fc or history builtins in zsh? I’m trying to avoid the effort of writing a history file parser. For reference, fish has history -z for this use case.

(Bonus: I want to do the same thing to analyze bash history also. Its fc seems to behave differently from zsh’s.)

Asked By: gcv

||

Use the history variable and parameter expansion (possibly including (associative) array index) features.

For example, the raw equivalent of fc -l 123 123 is $history[123]. To get a range of history elements in an array, you can use ${(v)history[(I)<123-456>]}. To get a null-separated list of history elements in a string, you can use ${(vpj[])history[(I)<123-456>]}. (But note that history elements can contain null bytes, and you can’t pass null bytes to external commands, so this isn’t particularly useful.)

To print those NUL-delimited, you can also use print -N, combined with -r to disable backslash expansion and -C1 so it prints nothing if there’s no match:

print -rNC1 -- ${(v)history[(I)<123-456>]} | fzf --read0

Or:

print -rNC1 -- $history | fzf --read0

For the whole history for instance.

With bash, if we assume you don’t enter commands whose second and following lines start with spaces, digits and at least 2 spaces, you could do something like:

HISTTIMEFORMAT= history | LC_ALL=C gawk -v RS='n *[0-9]+  ' -v ORS='' '
  NR==1 {sub(/^ *[0-9]+  /, "")}
  {print}'

To convert the history output (with timestamp removed) like:

  123  cmd
  124  echo 'multiline
command'
  125  ...

to:

cmd<NUL>echo 'multiline
command'<NUL>...<NUL>
Answered By: Stéphane Chazelas
Categories: Answers Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.