Skip to content

Commit 597db75

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 f558191 commit 597db75

File tree

3 files changed

+73
-9
lines changed

3 files changed

+73
-9
lines changed

README.md

Lines changed: 8 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,12 @@ 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+
100+
Unfortunately, this only works with Bash 5.0 and above. In older version of Bash, `ignorespace` and `ignoreboth` will be just silently ignored.
101+
94102
## Tests
95103
You can run tests using [Bats](https://github.com/bats-core/bats-core).
96104
```bash

bash-preexec.sh

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ __bp_imported="defined"
4646
__bp_last_ret_value="$?"
4747
BP_PIPESTATUS=("${PIPESTATUS[@]}")
4848
__bp_last_argument_prev_command="$_"
49+
__bp_ignorespace=
4950

5051
__bp_inside_precmd=0
5152
__bp_inside_preexec=0
@@ -62,15 +63,25 @@ __bp_require_not_readonly() {
6263
done
6364
}
6465

65-
# Remove ignorespace and or replace ignoreboth from HISTCONTROL
66-
# so we can accurately invoke preexec with a command from our
67-
# history even if it starts with a space.
66+
# Remove ignorespace and or replace ignoreboth from HISTCONTROL so we can
67+
# accurately invoke preexec with a command from our history even if it starts
68+
# with a space. We then remove commands that start with a space from the
69+
# history "manually", if either "ignorespace" or "ignoreboth" was part of
70+
# HISTCONTROL.
6871
__bp_adjust_histcontrol() {
6972
local histcontrol
70-
histcontrol="${HISTCONTROL//ignorespace}"
73+
if [[ "$HISTCONTROL" == *"ignorespace"* || "$HISTCONTROL" == *"ignoreboth"* ]]; then
74+
__bp_ignorespace=yes
75+
fi
76+
histcontrol="${HISTCONTROL//ignorespace:}"
77+
histcontrol="${histcontrol//:ignorespace}"
78+
histcontrol="${histcontrol//ignorespace}"
7179
# Replace ignoreboth with ignoredups
7280
if [[ "$histcontrol" == *"ignoreboth"* ]]; then
73-
histcontrol="ignoredups:${histcontrol//ignoreboth}"
81+
histcontrol="${histcontrol//ignoreboth:}"
82+
histcontrol="${histcontrol//:ignoreboth}"
83+
histcontrol="${histcontrol//ignoreboth}"
84+
histcontrol="ignoredups${histcontrol:+:}${histcontrol}"
7485
fi;
7586
export HISTCONTROL="$histcontrol"
7687
}
@@ -213,6 +224,18 @@ __bp_preexec_invoke_exec() {
213224
return
214225
fi
215226

227+
# If we have remove "ignorespace" or "ignoreboth" from HISTCONTROL during
228+
# setup, we need to remove commands that start with a space from the
229+
# history ourselves.
230+
231+
# Unfortunately, we need bash 5.0 or above, as "history -d" does not
232+
# support negative indices in earlier versions and HISTCMD seems to be
233+
# unreliable. At least in the unit tests HISTCMD is just set to 1, after
234+
# any number of "history -s" calls.
235+
if [[ "${BASH_VERSINFO:-0}" -ge 5 && -n "$__bp_ignorespace" && "$this_command" == " "* ]]; then
236+
builtin history -d -1
237+
fi
238+
216239
# If none of the previous checks have returned out of this function, then
217240
# the command is in fact interactive and we should invoke the user's
218241
# preexec functions.

test/bash-preexec.bats

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -236,18 +236,17 @@ test_preexec_echo() {
236236
# Should remove ignorespace
237237
HISTCONTROL="ignorespace:ignoredups:*"
238238
__bp_adjust_histcontrol
239-
[ "$HISTCONTROL" == ":ignoredups:*" ]
239+
[ "$HISTCONTROL" == "ignoredups:*" ]
240240

241241
# Should remove ignoreboth and replace it with ignoredups
242242
HISTCONTROL="ignoreboth"
243243
__bp_adjust_histcontrol
244-
[ "$HISTCONTROL" == "ignoredups:" ]
244+
[ "$HISTCONTROL" == "ignoredups" ]
245245

246246
# Handle a few inputs
247247
HISTCONTROL="ignoreboth:ignorespace:some_thing_else"
248248
__bp_adjust_histcontrol
249-
echo "$HISTCONTROL"
250-
[ "$HISTCONTROL" == "ignoredups:::some_thing_else" ]
249+
[ "$HISTCONTROL" == "ignoredups:some_thing_else" ]
251250

252251
}
253252

@@ -292,3 +291,37 @@ a multiline string'" ]
292291
[ $status -eq 0 ]
293292
[ "$output" == '-n' ]
294293
}
294+
295+
@test "HISTCONTROL is updated, but ignorespace functionality is honoured" {
296+
if [ "${BASH_VERSINFO:-0}" -lt 5 ]; then
297+
skip "Proper history manipulation is supported only since Bash 5.0"
298+
fi
299+
300+
preexec_functions+=(test_preexec_echo)
301+
HISTCONTROL=ignorespace:ignoreboth
302+
303+
__bp_adjust_histcontrol
304+
305+
[[ "$HISTCONTROL" == "ignoredups" ]]
306+
307+
__bp_interactive_mode
308+
309+
command1="this command is in the history"
310+
311+
history -s "$command1"
312+
run '__bp_preexec_invoke_exec'
313+
[[ $status == 0 ]]
314+
[[ "$output" == "$command1" ]]
315+
last_history=$(HISTTIMEFORMAT= history 1 | sed '1 s/^ *[0-9][0-9]*[* ] //')
316+
[[ "$last_history" == "$command1" ]]
317+
318+
command2=" this should not be in the history"
319+
320+
history -s "$command2"
321+
# we need to extract command history in the subshell, as the parent shell
322+
# history is actually not affected.
323+
output=$(__bp_preexec_invoke_exec && \
324+
printf "last_history: %s\n" "$(HISTTIMEFORMAT= history 1 | sed '1 s/^ *[0-9][0-9]*[* ] //')" )
325+
[[ $status == 0 ]]
326+
[[ "$output" == "$command2"$'\n'"last_history: $command1" ]]
327+
}

0 commit comments

Comments
 (0)