-
Notifications
You must be signed in to change notification settings - Fork 95
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove dependency on sed(1) for history processing #167
base: master
Are you sure you want to change the base?
Conversation
export LC_ALL=C | ||
HISTTIMEFORMAT='' builtin history 1 | sed '1 s/^ *[0-9][0-9]*[* ] //' | ||
) | ||
this_command=$(LC_ALL=C HISTTIMEFORMAT='' builtin history 1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This also removes the export
for LC_ALL
, which shouldn't be necessary here. I can move that into it's own proposed change if that's preferred.
0962448
to
e4d1398
Compare
We post-process `history 1`'s output to extract the current command. This processing needs to strip the leading history number, an optional `*` character indicating whether the entry was modified (or a space), and then a space separating character. We were previously using sed(1) for this, but we can implement an equivalent transformation using bash's native parameter expansion syntax.
e4d1398
to
c853a7e
Compare
The history number is printed as "%5d" so we need to handle all possible columns. declare -a lines=( "10000 history 1" " 1000 history 1" " 1 history 1" " 1000* history 1" " 1000 history 1" ) for line in "${lines[@]}"; do echo "${line#*[[:digit:]][* ] }" done
With the latest revision, the pattern now yields correct results for a complete set of possible #!/usr/bin/env bash
declare -a lines=(
"10000 history 1"
" 1000 history 1"
" 1 history 1"
" 1000* history 1"
" 1000 history 1"
)
for line in "${lines[@]}"; do
echo "${line#*[[:digit:]][* ] }"
done It matches anything up to the last digit as the number prefix. |
We could get more precise (and more closely reproduce the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your script produces a wrong result for the last input:
$ bash bash-completion-167.sh
history 1
history 1
history 1
history 1
history 1
though the last input " 1000 history 1"
would never happen because of the Bash code. So it anyway works.
We could get more precise (and more closely reproduce the
sed
pattern) if we (temporarily) usedextglob
, but that might be overkill for this set of constrained inputs.
I agree. The extglob engine of Bash is slow.
That's actually intentional. There's a test to ensure we don't strip whitespace from the command itself: bash-preexec/test/bash-preexec.bats Lines 363 to 372 in 11aba65
I think that's only relevant if |
Ah, I see your point. |
Thanks for the PR @jparise! and @akinomyoga for the review. @jparise do we know if this reduces the overhead time of running bash-preexec? Would be curious how much this speeds us up. @steinarvk, @dseomn - Any feedback on this change as folks who've made changes to the current implementation with sed? |
In this artificial test, the new (non- #!/usr/bin/env bash
old() {
loop=1
until [[ $loop -eq 10000 ]]; do
this_command=$(
export LC_ALL=C
HISTTIMEFORMAT='' builtin history 1 | sed '1 s/^ *[0-9][0-9]*[* ] //'
)
((++loop))
done
}
new() {
loop=1
until [[ $loop -eq 10000 ]]; do
this_command=$(LC_ALL=C HISTTIMEFORMAT='' builtin history 1)
this_command="${this_command#*[[:digit:]][* ] }"
((++loop))
done
}
echo "Old"
time -p old
echo "New"
time -p new
|
If one cares about the overhead, one can also consider using a function substitution (also known as "no-fork command substitution") introduced in Bash 5.3 (which is still beta now, though) with a proper test on #!/usr/bin/env bash
old() {
loop=1
until [[ $loop -eq 10000 ]]; do
this_command=$(
export LC_ALL=C
HISTTIMEFORMAT='' builtin history 1 | sed '1 s/^ *[0-9][0-9]*[* ] //'
)
((++loop))
done
}
new() {
loop=1
until [[ $loop -eq 10000 ]]; do
this_command=$(LC_ALL=C HISTTIMEFORMAT='' builtin history 1)
this_command="${this_command#*[[:digit:]][* ] }"
((++loop))
done
}
new2() {
loop=1
until [[ $loop -eq 10000 ]]; do
if (( BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 3) )); then
this_command=${ LC_ALL=C HISTTIMEFORMAT='' builtin history 1; }
else
this_command=$(LC_ALL=C HISTTIMEFORMAT='' builtin history 1)
fi
this_command="${this_command#*[[:digit:]][* ] }"
((++loop))
done
}
time old
time new
time new2 $ bash-5.3-beta gh0167.benchmark.sh
real 0m18.610s
user 0m9.430s
sys 0m12.251s
real 0m11.676s
user 0m4.243s
sys 0m7.507s
real 0m0.629s
user 0m0.578s
sys 0m0.046s |
We post-process history 1's output to extract the current command. This processing needs to strip the leading history number, an optional * character indicating whether the entry was modified (or a space), and then a space separating character. We were previously using sed(1) for this, but we can implement an equivalent transformation using bash's native parameter expansion syntax. This also results in ~4x reduction in per-prompt command overhead. Upstream: rcaloras/bash-preexec#167
We post-process
history 1
's output to extract the current command. This processing needs to strip the leading history number, an optional*
character indicating whether the entry was modified (or a space), and then a space separating character.We were previously using sed(1) for this, but we can implement an equivalent transformation using bash's native parameter expansion syntax.
This also results in ~4x reduction in per-prompt command overhead.