commit:     cd4185b89d919e3aa73420b187412b40f4fef669
Author:     Kerin Millar <kfm <AT> plushkava <DOT> net>
AuthorDate: Sat Jun  7 15:59:29 2025 +0000
Commit:     Sam James <sam <AT> gentoo <DOT> org>
CommitDate: Sat Jun  7 22:54:06 2025 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=cd4185b8

bashrc-functions.sh: accelerate __strip_duplicate_slashes() by not looping

Accelerate the __strip_duplicate_slashes() function by using the
combination of a string-replacing parameter expansion and an extended
pattern matching operator to strip the slashes in a single pass.

Doing so depends upon the extglob option, which must be handled with a
commensurate degree of caution. It is important to understand that the
extglob option affects the behaviour of the parser. Thus, it is possible
to write code which is syntantically valid in view of the extglob option
being enabled, yet which is invalid otherwise.

$ bash -O extglob -c ': +(.)'         # ok
$ bash -c ': +(.)'                    # error
bash: -c: line 1: syntax error near unexpected token `('
$ bash -c 'shopt -s extglob; : +(.)'  # still an error!
bash: -c: line 1: syntax error near unexpected token `('
$ bash -c $'shopt -s extglob\n: +(.)' # ok

In the third case, an error occurs because bash encounters the pattern
matching operator before it has had an opportunity to enable the extglob
option. The fourth cases addresses this by concluding the invocation of
the shopt builtin with a <newline>. Doing so gives bash the opportunity
to enable the option before the command bearing the pattern is parsed.

Consider, also, the following sample program.

#!/bin/bash
lowernames() {
    # Enable the extglob option upon the function being called.
    shopt -s extglob
    printf '%s\n' +([a-z])
}
lowernames

Bash will fail to parse the program as a whole, simply because the
extglob option is not enabled at the point that the +([a-z]) pattern is
seen and (unsuccessfully) parsed.

Now, since portage does not enable extglob of its own volition, it
presents a challenge. How does one prevent bash from being offended by
the presence of an extended pattern before the opportunity has arisen to
enable the extglob option at runtime? As it happens, the answer is a
remarkably simple one, which is to use eval to execute a string
containing the otherwise offending pattern.

#!/bin/bash
lowernames() {
   # Enable the extglob option upon the function being called.
   shopt -s extglob
   # Conceal the pattern from bash during the parsing stage. This works
   # because eval operands are never evaluated until eval is executed.
   eval 'printf "%s\n" +([a-z])'
}
lowernames

The revised __strip_duplicate_slashes() function makes use of eval for
exactly this reason. Apart from evaluating the 'reset_extglob' payload,
there is no property of eval being exploited other than its ability to
conceal code from bash during the parsing stage.

Finally, use the printf builtin instead of echo.

Signed-off-by: Kerin Millar <kfm <AT> plushkava.net>
Signed-off-by: Sam James <sam <AT> gentoo.org>

 bin/bashrc-functions.sh | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/bin/bashrc-functions.sh b/bin/bashrc-functions.sh
index 32611ebaa8..873e355734 100644
--- a/bin/bashrc-functions.sh
+++ b/bin/bashrc-functions.sh
@@ -27,12 +27,15 @@ register_success_hook() {
 }
 
 __strip_duplicate_slashes() {
-       if [[ -n ${1} ]] ; then
-               local removed=${1}
-               while [[ ${removed} == *//* ]] ; do
-                       removed=${removed//\/\///}
-               done
-               echo "${removed}"
+       local str=$1 reset_extglob
+
+       if [[ ${str} ]]; then
+               # The extglob option affects the behaviour of the parser and
+               # must thus be treated with caution. Given that extglob is not
+               # normally enabled by portage, use eval to conceal the pattern.
+               reset_extglob=$(shopt -p extglob)
+               eval "shopt -s extglob; str=\${str//+(\/)/\/}; ${reset_extglob}"
+               printf '%s\n' "${str}"
        fi
 }
 

Reply via email to