Search code examples
shellcommand-history

History (or fc) commands don't work from within a script


When using tools like grep or some git output (for status, for example), I often want to make something with the (last) file returned in the list, like copying it to the clipboard or viewing it.

I'd like to automate that with a script that would rerun the last command, and identify the last returned file name, with such as script:

#!/usr/bin/env bash

# Retrieve the last command from the shell history.
last_command=$(history | tail -n 1)

# Check if the last command is empty.
if [ -z "$last_command" ]; then
    printf >&2 "No command found in the shell history.\n"
    exit 2
fi

# Run the last command and capture its output.
command_output=$($last_command)

# Extract the last line (assuming it's a list of files).
last_file=$(printf "%s" "$command_output" | tail -n 1)

# Debugging information.
printf "Last file: %s\n" "$last_file"

Though, for whatever reason which escapes me, history does return the last command when run interactively from the command line, but it does not when run from the script. Hence last_command is always empty...

With set -x:

++ history
++ tail -n 1
+ last_command=
+ [[ -z '' ]]
+ printf 'No command found in the shell history.\n'
No command found in the shell history.
+ exit 2

Any idea?

(PS- I've also tried many different alternatives, such as using fc, but with no more success.)

EDIT -- Changing the first command to:

history_file="$HOME/.bash_history"
last_command=$(tail -n 1 "$history_file")

shows me another last command than the real last command outputted by history!??


Solution

  • Like the man page indicates, you have to set -o history to make history available to scripts; but even then, the feature is enabled independently from your current interactive history. Since this seems like it's intended entirely for interactive use, it's probably something you should make into a function in your .bash_profile or similar instead.

    rerun () {
        local last_command=$(history 2 | sed -n 's/^ *[1-9][0-9]* *//p;q')
    
        # Check if the last command is empty.
        if [ -z "$last_command" ]; then
            echo "rerun: No command found in the shell history" >&2
            return 2
        fi
    
        $last_command | tail -n 1
    }
    

    This is still way too simplistic if the last command was a pipeline. You want to eval the last command to really repeat it.

    (I find this too limited to be of actual use. Just press the up arrow and add | tail -n1 | xargs cp -t dest to copy the last file in particular someplace. More often, I want to copy or otherwise manipulate all the files or lines.)