Skip to content

Commit 9b35b56

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 da2dbb5 commit 9b35b56

File tree

3 files changed

+73
-9
lines changed

3 files changed

+73
-9
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ curl https://raw.githubusercontent.com/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
## Tests
95101
You can run tests using [Bats](https://github.com/bats-core/bats-core).
96102
```bash

bash-preexec.sh

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ __bp_imported="defined"
5151
__bp_last_ret_value="$?"
5252
BP_PIPESTATUS=("${PIPESTATUS[@]}")
5353
__bp_last_argument_prev_command="$_"
54+
__bp_ignorespace=
5455

5556
__bp_inside_precmd=0
5657
__bp_inside_preexec=0
@@ -70,15 +71,25 @@ __bp_require_not_readonly() {
7071
done
7172
}
7273

73-
# Remove ignorespace and or replace ignoreboth from HISTCONTROL
74-
# so we can accurately invoke preexec with a command from our
75-
# history even if it starts with a space.
74+
# Remove ignorespace and or replace ignoreboth from HISTCONTROL so we can
75+
# accurately invoke preexec with a command from our history even if it starts
76+
# with a space. We then remove commands that start with a space from the
77+
# history "manually", if either "ignorespace" or "ignoreboth" was part of
78+
# HISTCONTROL.
7679
__bp_adjust_histcontrol() {
7780
local histcontrol
78-
histcontrol="${HISTCONTROL//ignorespace}"
81+
if [[ "$HISTCONTROL" == *"ignorespace"* || "$HISTCONTROL" == *"ignoreboth"* ]]; then
82+
__bp_ignorespace=yes
83+
fi
84+
histcontrol="${HISTCONTROL//ignorespace:}"
85+
histcontrol="${histcontrol//:ignorespace}"
86+
histcontrol="${histcontrol//ignorespace}"
7987
# Replace ignoreboth with ignoredups
8088
if [[ "$histcontrol" == *"ignoreboth"* ]]; then
81-
histcontrol="ignoredups:${histcontrol//ignoreboth}"
89+
histcontrol="${histcontrol//ignoreboth:}"
90+
histcontrol="${histcontrol//:ignoreboth}"
91+
histcontrol="${histcontrol//ignoreboth}"
92+
histcontrol="ignoredups${histcontrol:+:}${histcontrol}"
8293
fi;
8394
export HISTCONTROL="$histcontrol"
8495
}
@@ -239,6 +250,23 @@ __bp_preexec_invoke_exec() {
239250
return
240251
fi
241252

253+
# If we have removed "ignorespace" or "ignoreboth" from HISTCONTROL
254+
# during setup, we need to remove commands that start with a space from
255+
# the history ourselves.
256+
257+
# With bash 5.0 or above, we could have just ran
258+
#
259+
# builtin history -d -1
260+
#
261+
# Negative indices for `-d` are not supported before 5.0, so we need to
262+
# do some computation ourselves.
263+
if [[ -n "$__bp_ignorespace" && "$this_command" == " "* ]]; then
264+
builtin history -d "$(
265+
export LC_ALL=C
266+
HISTTIMEFORMAT= history 1 | sed '1 s/^ *\([0-9][0-9]*\).*/\1/'
267+
)"
268+
fi
269+
242270
# Invoke every function defined in our function array.
243271
local preexec_function
244272
local preexec_function_ret_value

test/bash-preexec.bats

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -306,18 +306,17 @@ test_preexec_echo() {
306306
# Should remove ignorespace
307307
HISTCONTROL="ignorespace:ignoredups:*"
308308
__bp_adjust_histcontrol
309-
[ "$HISTCONTROL" == ":ignoredups:*" ]
309+
[ "$HISTCONTROL" == "ignoredups:*" ]
310310

311311
# Should remove ignoreboth and replace it with ignoredups
312312
HISTCONTROL="ignoreboth"
313313
__bp_adjust_histcontrol
314-
[ "$HISTCONTROL" == "ignoredups:" ]
314+
[ "$HISTCONTROL" == "ignoredups" ]
315315

316316
# Handle a few inputs
317317
HISTCONTROL="ignoreboth:ignorespace:some_thing_else"
318318
__bp_adjust_histcontrol
319-
echo "$HISTCONTROL"
320-
[ "$HISTCONTROL" == "ignoredups:::some_thing_else" ]
319+
[ "$HISTCONTROL" == "ignoredups:some_thing_else" ]
321320

322321
}
323322

@@ -340,6 +339,7 @@ test_preexec_echo() {
340339

341340
run '__bp_preexec_invoke_exec'
342341
[ $status -eq 0 ]
342+
echo "__bp_preexec_invoke_exec: output: '$output'"
343343
[ "$output" == " this command has whitespace " ]
344344
}
345345

@@ -362,3 +362,33 @@ a multiline string'" ]
362362
[ $status -eq 0 ]
363363
[ "$output" == '-n' ]
364364
}
365+
366+
@test "HISTCONTROL is updated, but ignorespace functionality is honoured" {
367+
preexec_functions+=(test_preexec_echo)
368+
HISTCONTROL=ignorespace:ignoreboth
369+
370+
__bp_adjust_histcontrol
371+
372+
[[ "$HISTCONTROL" == "ignoredups" ]]
373+
374+
__bp_interactive_mode
375+
376+
command1="this command is in the history"
377+
378+
history -s "$command1"
379+
run '__bp_preexec_invoke_exec'
380+
[[ $status == 0 ]]
381+
[[ "$output" == "$command1" ]]
382+
last_history=$(HISTTIMEFORMAT= history 1 | sed '1 s/^ *[0-9][0-9]* *//')
383+
[[ "$last_history" == "$command1" ]]
384+
385+
command2=" this should not be in the history"
386+
387+
history -s "$command2"
388+
# we need to extract command history in the subshell, as the parent shell
389+
# history is actually not affected.
390+
output=$(__bp_preexec_invoke_exec && \
391+
printf "last_history: %s\n" "$(HISTTIMEFORMAT= history 1 | sed '1 s/^ *[0-9][0-9]* *//')" )
392+
[[ $status == 0 ]]
393+
[[ "$output" == "$command2"$'\n'"last_history: $command1" ]]
394+
}

0 commit comments

Comments
 (0)