I feel like there is no ZSH setup option to auto-remove .zsh_history
after a certain time (e.g. older than one month from now). What would be the simplest hack to achieve it?

- 153
1 Answers
Correct, there are no time-based expiration options for .zsh_history
. Probably because any sort of timed expiry requires the EXTENDED_HISTORY
format (which includes start time for each command), and EXTENDED_HISTORY
is a new(er) feature — newer than most of the expiration options, at least.
Also, zsh functionality doesn't have the best relationship with the fuzzy concept of "time" — just read the restrictions on the time-based globbing flags to appreciate that:
Any fractional part of the difference between the access time and the current part in the appropriate units is ignored in the comparison. For instance,
echo *(ah-5)
would echo files accessed within the last five hours, whileecho *(ah+5)
would echo files accessed at least six hours ago, as times strictly between five and six hours are treated as five hours.
Beyond that, if you have any of the duplicate-removal options turned on, zsh will remove older entries in favor of the newest one, so the list of "old" entries is in constant flux, making time-based expiration trickier.
If you do want to do it, as LSerni says, parsing it yourself is probably the best option. One tricky thing about doing that is, multi-line entries in the history are written out verbatim, meaning on multiple lines — so you can't parse it simply "line-by-line" because some lines aren't history entries, they're continuations.
My other worry is that there's probably no truly safe way to do this except to integrate it with zsh's history-management directly, because any outside code modifying the history file is in constant danger of interference from (or interfering with) a running instance of zsh
. I mean, unless you were to write something in bash
that only runs when you can't possibly be logged in, like during the single-user portions of the boot or shutdown processes.
This Python code avoids that issue by copying the history to a new file, reading from it, and then writing the entries that pass the cutoff date out to a different new file. The destination file could be changed to .zsh_history
to overwrite the original file, but I take no responsibility for any file corruption that may occur.
Entries older than CUTOFF
are dropped (I've used 7 days in the example, but you can easily change 7
to 30
or 31
for one month.) It's assumed this is running with $HOME
as the working directory.
This also assumes the history is sorted by time, I don't know how safe an assumption that is. (If it's not true, you may end up keeping more entries than intended, because once it hits the first entry timestamped after the cutoff time, everything after is included. So, older entries that come after the first newer one will be accidentally preserved. Which seems like the right kind of incorrect output to favor, vs. possibly dropping anything unintended.)
#!/usr/bin/python3
import shutil
import time
from itertools import dropwhile
CUTOFF = time.time() - (86400 * 7) # one week ago
hist = []
shutil.copy2(".zsh_history", ".zsh_history.bak")
with open(".zsh_history.bak", "rb") as f:
for l in f.readlines():
if l.startswith(b': '):
# Start of a new history entry
# Add a new tuple (time, history_line) to the list
ts = int(l.split(b':')[1])
hist.append((ts, l))
continue
# Continuation line, append it to the previous entry
prev = hist.pop()
hist.append((prev[0], b''.join([prev[1], l])))
with open(".zsh_history.new", "wb") as f:
# Drop list entries while timestamp < CUTOFF,
# Then write contents of each remaining entry to file
for l in dropwhile(lambda x: x[0] < CUTOFF, hist):
f.write(l[1])

- 921
awk
, extracting the second colon-delimited field and compare it to the Unix time of 30 days ago. If larger, keep the line. The new output replaces the history file. Have this run at login, or maybe at logout via.zlogout
. – LSerni Oct 22 '21 at 19:31