commit:     4a4631eef7186c29668a8c049d988b61469940fd
Author:     Kerin Millar <kfm <AT> plushkava <DOT> net>
AuthorDate: Thu Jul 25 22:17:20 2024 +0000
Commit:     Sam James <sam <AT> gentoo <DOT> org>
CommitDate: Mon Jun  2 14:21:02 2025 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=4a4631ee

isolated-functions.sh: add the contains_word() function

Add the contains_word() function, whose purpose is to determine whether
a word is contained by another string comprising zero-or-more
whitespace-separated words. This is a use case for which the has()
function tends to be rampantly misappropriated. Below are some examples.

# Slow; sensitive to the value of IFS; can incur pathname expansion.
has xattr ${FEATURES}

# Ditto.
has nostrip ${FEATURES} ${PORTAGE_RESTRICT}

# Ditto, only with $1 also being treated unsafely.
has $1 {INHERITED}

Indeed, has() is sometimes used in situations where there is simply no
call for it whatsoever. Below are some prior examples for posterity.

# Slow. Fails to quote the expansion and is a silly way of writing:
# [[ ${suffix} == @(Z|gz|bz2) ]]
has ${suffix} Z gz bz2

# A silly way of writing: [[ ${EAPI} == [23] ]]
has "${EAPI:-0}" 2 3

# A silly way of writing: [[ ${EBUILD_PHASE} != clean?(rm) ]]
! has "${EBUILD_PHASE}" clean cleanrm

The new function is faster in all cases, with the observable performance
delta increasing for matches made against words further towards the
right of the haystack string (owing to for loops being very slow in
bash). The following benchmarks entailed searching 33 words within
FEATURES for "keepwork" - the middle word - 10,000 times.

has()

real    0m2.027s
user    0m2.027s
sys     0m0.000s

contains_word()

real    0m0.905s
user    0m0.905s
sys     0m0.000s

Further, going about it in this way renders xtrace output less noisy.

Acknowledgement is due to Jan Chren (a.k.a. rindeal), who independently
issued a conceptually similar GitHub pull request (#458) in September
2019. I was initially unaware of this until Sam James pointed it out.

Link: https://github.com/gentoo/portage/pull/458
Signed-off-by: Kerin Millar <kfm <AT> plushkava.net>
Signed-off-by: Sam James <sam <AT> gentoo.org>

 bin/isolated-functions.sh | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/bin/isolated-functions.sh b/bin/isolated-functions.sh
index e763156656..ae28125de4 100644
--- a/bin/isolated-functions.sh
+++ b/bin/isolated-functions.sh
@@ -537,6 +537,9 @@ hasv() {
        return 1
 }
 
+# Determines whether the first parameter is stringwise equal to any of the
+# following parameters. Do NOT use this function for checking whether a word is
+# contained by another string. For that, use contains_word() instead.
 has() {
        local needle=$1
        shift
@@ -676,4 +679,13 @@ debug-print-section() {
        debug-print "now in section ${*}"
 }
 
+# Considers the first parameter as a word and the second parameter as a string
+# comprising zero or more whitespace-separated words before determining whether
+# said word can be matched against any of them. It addresses a use case for
+# which the has() function is commonly misappropriated, with maximal 
efficiency.
+contains_word() {
+       local IFS
+       [[ $1 == +([![:space:]]) && " ${*:2} " == *[[:space:]]"$1"[[:space:]]* 
]]
+}
+
 true

Reply via email to