Skip to content

Instantly share code, notes, and snippets.

@Zetaphor
Created June 8, 2025 17:08
Show Gist options
  • Save Zetaphor/de8f4a540dc849f935f33e8f335aa9a4 to your computer and use it in GitHub Desktop.
Save Zetaphor/de8f4a540dc849f935f33e8f335aa9a4 to your computer and use it in GitHub Desktop.
Fish history deleter
#
# function hdel
#
# Interactively search and delete entries from fish shell history.
# Requires 'fzf' to be installed.
#
function hdel --description "Interactively delete items from Fish history"
# 1. Dependency Check: Ensure fzf is installed.
if not command -q fzf
echo (set_color red)"Error: This function requires 'fzf' (fuzzy finder)."(set_color normal)
echo "Please install it to continue. (e.g., 'sudo apt install fzf' or 'brew install fzf')"
return 1
end
# 2. Get the history file path (backwards-compatible).
set -l histfile
if command -q fish_config && fish_config path history >/dev/null 2>&1
set histfile (fish_config path history)
else
set histfile "$HOME/.local/share/fish/fish_history"
if not test -f "$histfile"; set histfile "$HOME/.fish_history"; end
end
if not test -f "$histfile"
echo (set_color red)"History file could not be found."(set_color normal)
return 1
end
# 3. Present the history to the user via fzf for selection.
# - We extract only the command text for a clean list.
# - The user can select multiple lines with the Tab key.
# - A preview window shows the timestamp for context.
set -l to_delete (
grep '^- cmd: ' "$histfile" |
sed 's/^- cmd: //' |
fzf --multi --height=80% --reverse \
--header="[ Fuzzy search history. Press TAB to mark for deletion, ENTER to confirm ]" \
--preview='echo "Timestamp:"; grep -B 1 -F -- " {}" "$histfile" | head -n 1 | sed "s/ when: //"'
)
# 4. Check if the user cancelled (e.g., pressed Esc) or selected nothing.
if test (count $to_delete) -eq 0
echo "No commands selected. Aborting."
return 0
end
# 5. Confirmation Step: Show what will be deleted and ask for confirmation.
echo (set_color yellow)"The following (count(string trim $to_delete)) commands are marked for permanent deletion:"(set_color normal)
for cmd in $to_delete
echo " - "(set_color red)$cmd(set_color normal)
end
echo
read --prompt-str="Are you sure you want to proceed? (y/N) " confirm
if test "$confirm" != "y" -a "$confirm" != "Y"
echo "Aborted by user."
return 0
end
# 6. Safety First: Create a backup before modifying anything.
set -l backup_file "$histfile.bak."(date +%F-%T)
cp -p "$histfile" "$backup_file"
echo "↪ Safety backup created at:" (set_color blue)$backup_file(set_color normal)
# 7. Core Deletion Logic using AWK.
# This is robust and correctly handles the 2-line structure of fish history.
set -l temp_file (mktemp)
# Build an awk script that knows which commands to delete.
# We pass the commands from our fish list into an awk associative array.
set -l awk_script 'BEGIN {'
for cmd in $to_delete
# Escape backslashes and double-quotes for the awk string literal
set -l escaped_cmd (string escape --style=c $cmd)
set awk_script "$awk_script to_delete[\"$escaped_cmd\"] = 1;"
end
# The main awk logic that filters the file.
set awk_script "$awk_script" '
}
# For every line in the history file...
{
# Is it a command line?
if (match($0, /^- cmd: (.*)/, m)) {
# Is the extracted command text (m[1]) in our to_delete list?
if (m[1] in to_delete) {
# Yes. So we do nothing. This effectively deletes both
# this line and the "when:" line we saved just before it.
} else {
# No. This is a command to keep. Print its saved "when:"
# line, and then print the command line itself.
print saved_line
print $0
}
} else {
# It must be a "when:" line. Save it for the next iteration.
saved_line = $0
}
}
'
# Execute the awk script.
awk "$awk_script" "$histfile" > "$temp_file"
# 8. Replace the original history file and reload the session.
mv "$temp_file" "$histfile"
history clear-session
history merge
echo (set_color green)"✅ Successfully deleted (count $to_delete) history entries."(set_color normal)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment