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