Skip to content

Commit

Permalink
Honor HISTCONTROL ignorespace and ignoreboth
Browse files Browse the repository at this point in the history
After 3458480

    Remove ignorespace from $HISTCONTROL

and after 7e55ac1

    Follow up commit for issue rcaloras#6

    -Replace ignoreboth with simpley ignoredups

this script would remove 'ignorespace' and would replace 'ignoreboth'
with 'ignoredups'.  This effectively disables the functionality of not
adding space prefixed commands into history.  It used to happen
siliently and could be quite confusing to users who use this feature.

This script relies on the command to be in the history, but we can
mostly fix the issue by "manual" removing a whitespace prefixed command
from the history after reading it from there.
  • Loading branch information
ilya-bobyr committed Feb 19, 2024
1 parent 1f77dc0 commit 6bcdfb3
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 14 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ curl https://raw.githubusercontent.com/rcaloras/bash-preexec/master/bash-preexec
echo '[[ -f ~/.bash-preexec.sh ]] && source ~/.bash-preexec.sh' >> ~/.bashrc
```

NOTE: this script may change your `HISTCONTROL` value by removing `ignorespace` and/or replacing `ignoreboth` with `ignoredups`. See [`HISTCONTROL` interaction](#histcontrol-interaction) for details.

## Usage
Two functions **preexec** and **precmd** can now be defined and they'll be automatically invoked by bash-preexec if they exist.

Expand Down Expand Up @@ -91,6 +93,10 @@ export __bp_enable_subshells="true"
```
This is disabled by default due to buggy situations related to to `functrace` and Bash's `DEBUG trap`. See [Issue #25](https://github.com/rcaloras/bash-preexec/issues/25)

## `HISTCONTROL` interaction

In order to be able to provide the last command text to the `preexec` hook, this script will remove `ignorespace` and/or will replace `ignoreboth` with `ignoredups` in your `HISTCONTROL` variable. It will remember if `HISTCONTROL` has been modified and will remove the last command from the history "manually", after reading the last command from the history list. This may cause issues when you have scripts that rely on the literal value of `HISTCONTROL` or manipulate history in their own ways.

## Library authors
If you want to detect bash-preexec in your library (for example, to add hooks to `preexec_functions` when available), use the Bash variable `bash_preexec_imported`:

Expand Down
46 changes: 36 additions & 10 deletions bash-preexec.sh
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ __bp_imported="${bash_preexec_imported}"
__bp_last_ret_value="$?"
BP_PIPESTATUS=("${PIPESTATUS[@]}")
__bp_last_argument_prev_command="$_"
__bp_ignorespace=

__bp_inside_precmd=0
__bp_inside_preexec=0
Expand All @@ -85,18 +86,26 @@ __bp_require_not_readonly() {
done
}

# Remove ignorespace and or replace ignoreboth from HISTCONTROL
# so we can accurately invoke preexec with a command from our
# history even if it starts with a space.
# Remove "ignorespace" and/or replace "ignoreboth" in HISTCONTROL so we can
# accurately invoke preexec with a command from our history even if it starts
# with a space. We then remove commands that start with a space from the
# history "manually", if either "ignorespace" or "ignoreboth" was part of
# HISTCONTROL.
__bp_adjust_histcontrol() {
local histcontrol
histcontrol="${HISTCONTROL:-}"
histcontrol="${histcontrol//ignorespace}"
# Replace ignoreboth with ignoredups
if [[ "$histcontrol" == *"ignoreboth"* ]]; then
histcontrol="ignoredups:${histcontrol//ignoreboth}"
fi;
export HISTCONTROL="$histcontrol"
histcontrol=${HISTCONTROL:-}
histcontrol=":${histcontrol//:/::}:"

if [[ "$histcontrol" == *":ignorespace:"* || "$histcontrol" == *":ignoreboth:"* ]]; then
__bp_ignorespace=yes
fi

histcontrol=${histcontrol//:ignorespace:}
histcontrol=${histcontrol//:ignoreboth:/:ignoredups:}

histcontrol=${histcontrol//::/:}
histcontrol=${histcontrol#:}
export HISTCONTROL=${histcontrol%:}
}

# This variable describes whether we are currently in "interactive mode";
Expand Down Expand Up @@ -260,6 +269,23 @@ __bp_preexec_invoke_exec() {
return
fi

# If we have removed "ignorespace" or "ignoreboth" from HISTCONTROL
# during setup, we need to remove commands that start with a space from
# the history ourselves.

# With bash 5.0 or above, we could have just ran
#
# builtin history -d -1
#
# Negative indices for `-d` are not supported before 5.0, so we compute the
# length of the history list explicit, to delete the last entry.
if [[ -n "$__bp_ignorespace" && "$this_command" == " "* ]]; then
builtin history -d "$(
export LC_ALL=C
HISTTIMEFORMAT= history 1 | sed '1 s/^ *\([0-9][0-9]*\).*/\1/'
)"
fi

# Invoke every function defined in our function array.
local preexec_function
local preexec_function_ret_value
Expand Down
38 changes: 34 additions & 4 deletions test/bash-preexec.bats
Original file line number Diff line number Diff line change
Expand Up @@ -333,18 +333,17 @@ set_exit_code_and_run_precmd() {
# Should remove ignorespace
HISTCONTROL="ignorespace:ignoredups:*"
__bp_adjust_histcontrol
[ "$HISTCONTROL" == ":ignoredups:*" ]
[ "$HISTCONTROL" == "ignoredups:*" ]

# Should remove ignoreboth and replace it with ignoredups
HISTCONTROL="ignoreboth"
__bp_adjust_histcontrol
[ "$HISTCONTROL" == "ignoredups:" ]
[ "$HISTCONTROL" == "ignoredups" ]

# Handle a few inputs
HISTCONTROL="ignoreboth:ignorespace:some_thing_else"
__bp_adjust_histcontrol
echo "$HISTCONTROL"
[ "$HISTCONTROL" == "ignoredups:::some_thing_else" ]
[ "$HISTCONTROL" == "ignoredups:some_thing_else" ]

}

Expand All @@ -367,6 +366,7 @@ set_exit_code_and_run_precmd() {

run '__bp_preexec_invoke_exec'
[ $status -eq 0 ]
echo "__bp_preexec_invoke_exec: output: '$output'"
[ "$output" == " this command has whitespace " ]
}

Expand All @@ -389,3 +389,33 @@ a multiline string'" ]
[ $status -eq 0 ]
[ "$output" == '-n' ]
}

@test "HISTCONTROL is updated, but ignorespace functionality is honoured" {
preexec_functions+=(test_preexec_echo)
HISTCONTROL=ignorespace:ignoreboth

__bp_adjust_histcontrol

[[ "$HISTCONTROL" == "ignoredups" ]]

__bp_interactive_mode

command1="this command is in the history"

history -s "$command1"
run '__bp_preexec_invoke_exec'
[[ $status == 0 ]]
[[ "$output" == "$command1" ]]
last_history=$(HISTTIMEFORMAT= history 1 | sed '1 s/^ *[0-9][0-9]* *//')
[[ "$last_history" == "$command1" ]]

command2=" this should not be in the history"

history -s "$command2"
# we need to extract command history in the subshell, as the parent shell
# history is actually not affected.
output=$(__bp_preexec_invoke_exec && \
printf "last_history: %s\n" "$(HISTTIMEFORMAT= history 1 | sed '1 s/^ *[0-9][0-9]* *//')" )
[[ $status == 0 ]]
[[ "$output" == "$command2"$'\n'"last_history: $command1" ]]
}

0 comments on commit 6bcdfb3

Please sign in to comment.