From a4bfa48cdda924f7a77a81e8d8e93e326a01b8a0 Mon Sep 17 00:00:00 2001 From: Kris Yotam <75515498+krisyotam@users.noreply.github.com> Date: Thu, 2 Jul 2026 00:11:17 -0500 Subject: [PATCH] mksh: reliable history hook via HISTFILE growth + binary record parse PS1 command substitution runs in a subshell so hook state must be persisted to disk (per-PID file); fc there is lagged one command and drops each session's last command. Detect new commands by HISTFILE growth and parse the last record from mksh's on-disk format. --- .mkshrc | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/.mkshrc b/.mkshrc index 4e9069bfda14ef010b12eadbf07602f632247c75..6b9e9984f75f2552838a6d67979c2305348b1d89 100644 --- a/.mkshrc +++ b/.mkshrc @@ -45,18 +45,33 @@ if [ -n "$BASH_VERSION" ]; then trap '_kh_last_cmd=$(fc -ln -1 2>/dev/null | sed "s/^[[:space:]]*//")' DEBUG PROMPT_COMMAND='_kh_log "$_kh_last_cmd" "$?"' else - # mksh: no DEBUG trap / PROMPT_COMMAND. Log from PS1 each prompt. - # The leading $(...) captures $? first, so the exit code is accurate, - # and _kh_prompt emits nothing so the visible prompt is unchanged. - # Skip the first render: at shell start fc -ln -1 is a stale HISTFILE - # entry, not a command run this session. - _kh_first_prompt=1 + # mksh: no DEBUG trap / PROMPT_COMMAND, and PS1 command substitution + # runs in a subshell, so hook state cannot live in shell variables -- + # it is persisted to a per-PID file instead. We detect that a new + # command ran by watching HISTFILE grow, then read the last entry + # straight from the file (fc in the PS1 subshell is lagged by one and + # would drop the final command of every session). + # + # mksh HISTFILE record layout: 0xFF, 4-byte big-endian counter, + # command bytes, NUL. So split on 0xFF, take the last record, drop the + # 4 counter bytes (cut -b5-), strip the trailing NUL. + [ -d "$HOME/.cache" ] || mkdir -p "$HOME/.cache" + _kh_state="$HOME/.cache/kh_mksh_$$" _kh_prompt() { - if [ -n "$_kh_first_prompt" ]; then - unset _kh_first_prompt - return - fi - _kh_log "$(fc -ln -1 2>/dev/null | sed 's/^[[:space:]]*//')" "$1" + typeset ec="$1" sz prev cmd + [ -f "$HISTFILE" ] || return + sz=$(wc -c < "$HISTFILE" 2>/dev/null) + prev=$(cat "$_kh_state" 2>/dev/null) + print -r -- "$sz" > "$_kh_state" + # first prompt of the session: record baseline, do not log + [ -z "$prev" ] && return + # only log when a new command was appended to history + [ "$sz" -gt "$prev" ] 2>/dev/null || return + cmd=$(tail -c 4096 "$HISTFILE" | tr "\377" "\n" | tail -n1 | cut -b5- | tr -d "\0") + _kh_log "$cmd" "$ec" } + # leading $(...) captures $? first so the exit code is accurate; the + # hook prints nothing so the visible prompt is unchanged PS1='$(_kh_prompt "$?")'"$PS1" + trap 'rm -f "$_kh_state"' EXIT fi