First, the solution. A long one-liner. Making use of Python being shipped with FreeNAS:
# cat .history | python -c 'from datetime import datetime as dt; import sys; lines=[l for l in sys.stdin]; chunks=[lines[i:i + 2] for i in range(0, len(lines), 2)]; [print(dt.fromtimestamp(int(time[2:])), cmd.strip()) for time, cmd in chunks]' | head -n5
2019-09-15 22:31:36 ls
2019-09-15 22:31:41 gpart
2019-09-15 22:31:44 gpart list
2019-09-15 22:32:32 gpart list
2019-09-15 22:32:45 gpart list
A little more readable (not so much horizontal scrolling):
cat .history | python -c \
'from datetime import datetime as dt;'
'import sys; lines=[l for l in sys.stdin];'
'chunks=[lines[i:i + 2] for i in range(0, len(lines), 2)];'
'[print(dt.fromtimestamp(int(time[2:])), cmd.strip()) for time, cmd in chunks]' \
| head -n5
FreeBSD’s history
command does not seem to have an interface for formatting the time each command was executed. I only get the time of the day. Example output line:
$ history
...
556 17:49 find . -iname "*astronaut*"
...
That is of limited use when you have accumulated commands over the course of many years; year, month, and day can be rather useful information :-) aaaaah, this was 2019!
This mailing list thread seems to confirm that there is no interface for configuring the output time format.
So I decided to simply parse the ~/.history
data file with Python. The history data file looks like that (showing the first four lines):
$ head -n4 < .history
#+1568579496
ls
#+1568579501
gpart
The ugly Python one-liner above iterates over text lines in stdin and groups them in pairs of two. Each pair is used for creating one line of text in the output.
I saved my full history with
# cat .history | python -c 'from datetime import datetime as dt; import sys; lines=[l for l in sys.stdin]; chunks=[lines[i:i + 2] for i in range(0, len(lines), 2)]; [print(dt.fromtimestamp(int(time[2:])), cmd.strip()) for time, cmd in chunks]' > freenas_history_root_230429.log
I think the reference documentation for the specific history command that I was working with here is
history [-hTr] [n]
history -S|-L|-M [filename] (+)
history -c (+)
The first form prints the history event list. If n is given
only the n most recent events are printed or saved. With -h,
the history list is printed without leading numbers. If -T is
specified, timestamps are printed also in comment form. (This
can be used to produce files suitable for loading with 'history
-L' or 'source -h'.) With -r, the order of printing is most
recent first rather than oldest first.
With -S, the second form saves the history list to filename.
If the first word of the savehist shell variable is set to a
number, at most that many lines are saved. If the second word
of savehist is set to `merge', the history list is merged with
the existing history file instead of replacing it (if there is
one) and sorted by time stamp. (+) Merging is intended for an
environment like the X Window System with several shells in si-
multaneous use. If the second word of savehist is `merge' and
the third word is set to `lock', the history file update will
be serialized with other shell sessions that would possibly
like to merge history at exactly the same time.
With -L, the shell appends filename, which is presumably a his-
tory list saved by the -S option or the savehist mechanism, to
the history list. -M is like -L, but the contents of filename
are merged into the history list and sorted by timestamp. In
either case, histfile is used if filename is not given and
~/.history is used if histfile is unset. `history -L' is ex-
actly like 'source -h' except that it does not require a file-
name.
Note that login shells do the equivalent of `history -L' on
startup and, if savehist is set, `history -S' before exiting.
Because only ~/.tcshrc is normally sourced before ~/.history,
histfile should be set in ~/.tcshrc rather than ~/.login.
If histlit is set, the first and second forms print and save
the literal (unexpanded) form of the history list.
The last form clears the history list.
taken from FreeBSD 13’s csh
man page.
There is no mention of e.g. HISTTIMEFORMAT
or a corresponding command line argument.
Notes:
* In the output of the Python program I deliberately didn’t care to do timezone handling; the output is a local time probably in UTC or my local timezone, and both are fine.
* In the output you might see a BrokenPipeError
— which is entirely expected when the process attached to stdin goes away.
Leave a Reply