2011
Comment:
|
5519
|
Deletions are marked like this. | Additions are marked like this. |
Line 1: | Line 1: |
[[Anchor(faq46)]] | <<Anchor(faq46)>> |
Line 3: | Line 3: |
If your real question was ''How do I check whether one of my parameters was -v?'' then please see [[BashFAQ/035|FAQ #35]] instead. Otherwise, read on.... | |
Line 4: | Line 5: |
The safest way to do this would be to loop over all elements in your set/list and check them for the element/word you are looking for. Say we are looking for the content of bar in the array foo: | First of all, let's get the terminology straight. Bash has no notion of "lists" or "sets" or any such. Bash has strings and [[BashFAQ/005|arrays]]. Strings are a "list" of '''characters''', arrays are a "list" of '''strings'''. '''NOTE:''' In the general case, a string cannot possibly contain a list of other strings because there is no reliable way to tell where each substring begins and ends. Given a traditional array, the only proper way to do this is to loop over all elements in your array and check them for the element you are looking for. Say what we are looking for is in `bar` and our list is in the array `foo`: |
Line 6: | Line 11: |
# Bash | |
Line 8: | Line 14: |
done}}} | done }}} |
Line 10: | Line 17: |
Or, to stop searching when you find it: | If you need to perform this several times in your script, you might want to extract the logic into a function: |
Line 12: | Line 19: |
for element in "${foo[@]}"; do [[ $element = $bar ]] && { echo "Found $bar."; break; } done}}} |
# Bash isIn() { local pattern="$1" element shift for element do [[ $element = $pattern ]] && return 0 done return 1 } if isIn "jacob" "${names[@]}" then echo "Jacob is on the list." fi }}} |
Line 16: | Line 38: |
If for some reason your list/set is not in an array, but is a string of words, and the element you are searching for is also a word, you can use this: | Or, if you want your function to return the '''index''' at which the element was found: |
Line 18: | Line 40: |
# Bash 3.0 or higher indexOf() { local pattern=$1 local index list shift list=("$@") for index in "${!list[@]}" do [[ ${list[index]} = $pattern ]] && { echo $index return 0 } done echo -1 return 1 } if index=$(indexOf "jacob" "${names[@]}") then echo "Jacob is the ${index}th on the list." else echo "Jacob is not on the list." fi }}} You can also use extended glob with printf to search for a word in an array. ''I haven't tested it enough, so it might break in some cases --sn18'' {{{ shopt -s extglob #convert array to glob printf -v glob '%q|' "${array[@]}" glob=${glob%|} [[ $word = @($glob) ]] && echo "Found $word" }}} If your "list" is contained in a string, and for some half-witted reason you choose not to heed the warnings above, you can use the following code to search through "words" in a string. (The only real excuse for this would be that you're stuck in Bourne shell, which has no arrays.) {{{ # Bourne set -f |
|
Line 19: | Line 83: |
[[ $element = $bar ]] && echo "Found $bar." done}}} |
if test x"$element" = x"$bar"; then echo "Found $bar." fi done set +f }}} |
Line 22: | Line 90: |
A less safe, but more clever version: | Here, a "word" is defined as any substring that is delimited by whitespace (or more specifically, the characters currently in IFS). The `set -f` prevents [[glob]] expansion of the words in the list. Turning glob expansions back on (`set +f`) is optional. If you're working in bash 4 or ksh93, you have access to associative arrays. These will allow you to restructure the problem -- instead of making a list of words that are allowed, you can make an ''associative array'' whose keys are the words you want to allow. Their values could be meaningful, or not -- depending on the nature of the problem. |
Line 24: | Line 95: |
if [[ " $foo " = *\ "$bar"\ * ]]; then | # Bash 4 declare -A good for word in "goodword1" "goodword2" ...; do good["$word"]=1 done # Check whether $foo is allowed: if ((${good[$foo]})); then ... }}} Here's a hack that you shouldn't use, but which is presented for the sake of completeness: {{{ # Bash if [[ " $foo " = *" $bar "* ]]; then |
Line 26: | Line 110: |
fi}}} | fi }}} (The problem here is that is assumes ''space'' can be used as a delimiter between words. Your elements might contain spaces, which would break this!) |
Line 28: | Line 114: |
And, if for some reason you don't know the syntax of for well enough, here's how to check your script's parameters for an element. For example, '-v': | That same hack, for Bourne shells: |
Line 30: | Line 116: |
for element; do [[ $element = '-v' ]] && echo "Switching to verbose mode." done}}} |
# Bourne case " $foo " in *" $bar "*) echo "Found $bar.";; esac }}} |
Line 34: | Line 122: |
GNU's grep has a {{{\b}}} feature which allegedly matches the edges of words. Using that, one may attempt to replicate the "clever" approach used above, but it is fraught with peril: | GNU's grep has a {{{\b}}} feature which allegedly matches the edges of words. Using that, one may attempt to replicate the shorter approach used above, but it is fraught with peril: |
Line 39: | Line 127: |
Line 46: | Line 135: |
# Obviously, you can't use this if someword is '-v'!}}} | # Obviously, you can't use this if someword is '-v'! }}} |
Line 48: | Line 138: |
Since this "feature" of GNU grep is both non-portable and poorly defined, we don't recommend using it. |
Since this "feature" of GNU grep is both non-portable and poorly defined, we recommend '''not''' using it. It is simply mentioned here for the sake of completeness. == Bulk comparison == This method tries to compare the desired string to the entire contents of the array. It can potentially be very efficient, but it depends on a delimiter that must not be in the sought value or the array. Here we use $'\a', the BEL character, because it's extremely uncommon. {{{ # usage: if has "element" list of words; then ...; fi has() { local IFS=$'\a' t="$1" shift [[ $'\a'"$*"$'\a' == *$'\a'$t$'\a'* ]] } }}} ---- CategoryShell |
I want to check to see whether a word is in a list (or an element is a member of a set).
If your real question was How do I check whether one of my parameters was -v? then please see FAQ #35 instead. Otherwise, read on....
First of all, let's get the terminology straight. Bash has no notion of "lists" or "sets" or any such. Bash has strings and arrays. Strings are a "list" of characters, arrays are a "list" of strings.
NOTE: In the general case, a string cannot possibly contain a list of other strings because there is no reliable way to tell where each substring begins and ends.
Given a traditional array, the only proper way to do this is to loop over all elements in your array and check them for the element you are looking for. Say what we are looking for is in bar and our list is in the array foo:
# Bash for element in "${foo[@]}"; do [[ $element = $bar ]] && echo "Found $bar." done
If you need to perform this several times in your script, you might want to extract the logic into a function:
# Bash isIn() { local pattern="$1" element shift for element do [[ $element = $pattern ]] && return 0 done return 1 } if isIn "jacob" "${names[@]}" then echo "Jacob is on the list." fi
Or, if you want your function to return the index at which the element was found:
# Bash 3.0 or higher indexOf() { local pattern=$1 local index list shift list=("$@") for index in "${!list[@]}" do [[ ${list[index]} = $pattern ]] && { echo $index return 0 } done echo -1 return 1 } if index=$(indexOf "jacob" "${names[@]}") then echo "Jacob is the ${index}th on the list." else echo "Jacob is not on the list." fi
You can also use extended glob with printf to search for a word in an array. I haven't tested it enough, so it might break in some cases --sn18
shopt -s extglob #convert array to glob printf -v glob '%q|' "${array[@]}" glob=${glob%|} [[ $word = @($glob) ]] && echo "Found $word"
If your "list" is contained in a string, and for some half-witted reason you choose not to heed the warnings above, you can use the following code to search through "words" in a string. (The only real excuse for this would be that you're stuck in Bourne shell, which has no arrays.)
# Bourne set -f for element in $foo; do if test x"$element" = x"$bar"; then echo "Found $bar." fi done set +f
Here, a "word" is defined as any substring that is delimited by whitespace (or more specifically, the characters currently in IFS). The set -f prevents glob expansion of the words in the list. Turning glob expansions back on (set +f) is optional.
If you're working in bash 4 or ksh93, you have access to associative arrays. These will allow you to restructure the problem -- instead of making a list of words that are allowed, you can make an associative array whose keys are the words you want to allow. Their values could be meaningful, or not -- depending on the nature of the problem.
# Bash 4 declare -A good for word in "goodword1" "goodword2" ...; do good["$word"]=1 done # Check whether $foo is allowed: if ((${good[$foo]})); then ...
Here's a hack that you shouldn't use, but which is presented for the sake of completeness:
# Bash if [[ " $foo " = *" $bar "* ]]; then echo "Found $bar." fi
(The problem here is that is assumes space can be used as a delimiter between words. Your elements might contain spaces, which would break this!)
That same hack, for Bourne shells:
# Bourne case " $foo " in *" $bar "*) echo "Found $bar.";; esac
GNU's grep has a \b feature which allegedly matches the edges of words. Using that, one may attempt to replicate the shorter approach used above, but it is fraught with peril:
# Is 'foo' one of the positional parameters? egrep '\bfoo\b' <<<"$@" >/dev/null && echo yes # This is where it fails: is '-v' one of the positional parameters? egrep '\b-v\b' <<<"$@" >/dev/null && echo yes # Unfortunately, \b sees "v" as a separate word. # Nobody knows what the hell it's doing with the "-". # Is "someword" in the array 'array'? egrep '\bsomeword\b' <<<"${array[@]}" # Obviously, you can't use this if someword is '-v'!
Since this "feature" of GNU grep is both non-portable and poorly defined, we recommend not using it. It is simply mentioned here for the sake of completeness.
Bulk comparison
This method tries to compare the desired string to the entire contents of the array. It can potentially be very efficient, but it depends on a delimiter that must not be in the sought value or the array. Here we use $'\a', the BEL character, because it's extremely uncommon.
# usage: if has "element" list of words; then ...; fi has() { local IFS=$'\a' t="$1" shift [[ $'\a'"$*"$'\a' == *$'\a'$t$'\a'* ]] }