bash 如何在不迭代元素的情况下检查字符串是否在数组中?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11396740/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 22:24:18  来源:igfitidea点击:

How can I check if a string is in an array without iterating over the elements?

bash

提问by Homunculus Reticulli

Is there a way of checking if a string exists in an array of strings - without iterating through the array?

有没有办法检查字符串数组中是否存在字符串 - 而不遍历数组?

For example, given the script below, how I can correctly implement it to test if the value stored in variable $test exists in $array?

例如,给定下面的脚本,我如何正确实现它来测试存储在变量 $test 中的值是否存在于 $array 中?

array=('hello' 'world' 'my' 'name' 'is' 'perseus')

#pseudo code
$test='henry'
if [$array[$test]]
   then
      do something
   else
      something else
fi

Note

笔记

I am using bash 4.1.5

我正在使用 bash 4.1.5

采纳答案by Charles Duffy

With bash 4, the closest thing you can do is use associative arrays.

使用 bash 4,您可以做的最接近的事情是使用关联数组。

declare -A map
for name in hello world my name is perseus; do
  map["$name"]=1
done

...which does the exactsame thing as:

...与以下内容完全相同

declare -A map=( [hello]=1 [my]=1 [name]=1 [is]=1 [perseus]=1 )

...followed by:

...其次是:

tgt=henry
if [[ ${map["$tgt"]} ]] ; then
  : found
fi

回答by Todd A. Jacobs

There will always technicallybe iteration, but it can be relegated to the shell's underlying array code. Shell expansionsoffer an abstraction that hide the implementation details, and avoid the necessity for an explicit loop within the shell script.

技术上讲,总会有迭代,但它可以归为 shell 的底层数组代码。Shell 扩展提供了一种隐藏实现细节的抽象,并避免了在 shell 脚本中显式循环的必要性。

Handling word boundaries for this use case is easier with fgrep, which has a built-in facility for handling whole-word fixed strings. The regular expression match is harder to get right, but the example below works with the provided corpus.

使用fgrep可以更轻松地处理此用例的单词边界,它具有用于处理全字固定字符串的内置工具。正则表达式匹配更难正确,但下面的示例适用于提供的语料库。

External Grep Process

外部 Grep 进程

array=('hello' 'world' 'my' 'name' 'is' 'perseus')
word="world"
if echo "${array[@]}" | fgrep --word-regexp "$word"; then
    : # do something
fi

Bash Regular Expression Test

Bash 正则表达式测试

array=('hello' 'world' 'my' 'name' 'is' 'perseus')
word="world"
if [[ "${array[*]}" =~ (^|[^[:alpha:]])$word([^[:alpha:]]|$) ]]; then
    : # do something
fi

回答by Paused until further notice.

You can use an associative array since you're using Bash 4.

您可以使用关联数组,因为您使用的是 Bash 4。

declare -A array=([hello]= [world]= [my]= [name]= [is]= [perseus]=)

test='henry'
if [[ ${array[$test]-X} == ${array[$test]} ]]
then
    do something
else
    something else
fi

The parameter expansion substitutes an "X" if the array element is unset (but doesn't if it's null). By doing that and checking to see if the result is different from the original value, we can tell if the key exists regardless of its value.

如果数组元素未设置,则参数扩展将替换为“X”(但如果为空则不会)。通过这样做并检查结果是否与原始值不同,我们可以判断该键是否存在,而不管其值如何。

回答by Bernard

array=('hello' 'world' 'my' 'name' 'is' 'perseus')
regex="^($(IFS=\|; echo "${array[*]}"))$"

test='henry'
[[ $test =~ $regex ]] && echo "found" || echo "not found"

回答by Fred Astair

Reading your post I take it that you don't just want to know if a string exists in an array (as the title would suggest) but to know if that string actually correspond to an element of that array. If this is the case please read on.

阅读您的帖子时,我认为您不仅想知道数组中是否存在字符串(如标题所示),还想知道该字符串是否实际上对应于该数组的元素。如果是这种情况,请继续阅读。

I found a way that seems to work fine .

我找到了一种似乎工作正常的方法。

Useful if you're stack with bash 3.2 like I am (but also tested and working in bash 4.2):

如果您像我一样使用 bash 3.2(但也在 bash 4.2 中测试和工作),则很有用:

array=('hello' 'world' 'my' 'name' 'is' 'perseus')
IFS=:     # We set IFS to a character we are confident our 
          # elements won't contain (colon in this case)

test=:henry:        # We wrap the pattern in the same character

# Then we test it:
# Note the array in the test is double quoted, * is used (@ is not good here) AND 
# it's wrapped in the boundary character I set IFS to earlier:
[[ ":${array[*]}:" =~ $test ]] && echo "found! :)" || echo "not found :("
not found :(               # Great! this is the expected result

test=:perseus:      # We do the same for an element that exists
[[ ":${array[*]}:" =~ $test ]] && echo "found! :)" || echo "not found :("
found! :)               # Great! this is the expected result

array[5]="perseus smith"    # For another test we change the element to an 
                            # element with spaces, containing the original pattern.

test=:perseus:
[[ ":${array[*]}:" =~ $test ]] && echo "found!" || echo "not found :("
not found :(               # Great! this is the expected result

unset IFS        # Remember to unset IFS to revert it to its default value  

Let me explain this:

让我解释一下:

This workaround is based on the principle that "${array[*]}"(note the double quotes and the asterisk) expands to the list of elements of array separated by the first character of IFS.

此解决方法基于"${array[*]}"(注意双引号和星号)扩展为由 IFS 的第一个字符分隔的数组元素列表的原则。

  1. Therefore we have to set IFS to whatever we want to use as boundary (a colon in my case):

    IFS=:
    
  2. Then we wrap the element we are looking for in the same character:

    test=:henry:
    
  3. And finally we look for it in the array. Take note of the rules I followed to do the test (they are all mandatory): the array is double quoted, * is used (@ is not good) AND it's wrapped in the boundary character I set IFS to earlier:

    [[ ":${array[*]}:" =~ $test ]] && echo found || echo "not found :("
    not found :(
    
  4. If we look for an element that exists:

    test=:perseus:
    [[ ":${array[*]}:" =~ $test ]] && echo "found! :)" || echo "not found :("
    found! :)
    
  5. For another test we can change the last element 'perseus' for 'perseus smith' (element with spaces), just to check if it's a match (which shouldn't be):

    array[5]="perseus smith"
    test=:perseus:
    [[ ":${array[*]}:" =~ $test ]] && echo "found!" || echo "not found :("
    not found :(
    

    Great!, this is the expected result since "perseus" by itself is not an element anymore.

  6. Important!: Remember to unset IFS to revert it to its default value (unset) once you're done with the tests:

    unset IFS
    
  1. 因此,我们必须将 IFS 设置为我们想要用作边界的任何内容(在我的情况下为冒号):

    IFS=:
    
  2. 然后我们将要查找的元素包装在同一个字符中:

    test=:henry:
    
  3. 最后我们在数组中寻找它。请注意我在进行测试时遵循的规则(它们都是强制性的):数组是双引号的,使用 *(@ 不好)并且它被包裹在我之前将 IFS 设置为的边界字符中:

    [[ ":${array[*]}:" =~ $test ]] && echo found || echo "not found :("
    not found :(
    
  4. 如果我们寻找一个存在的元素:

    test=:perseus:
    [[ ":${array[*]}:" =~ $test ]] && echo "found! :)" || echo "not found :("
    found! :)
    
  5. 对于另一个测试,我们可以将最后一个元素 'perseus' 更改为 'perseus smith'(带空格的元素),只是为了检查它是否匹配(不应该是):

    array[5]="perseus smith"
    test=:perseus:
    [[ ":${array[*]}:" =~ $test ]] && echo "found!" || echo "not found :("
    not found :(
    

    太好了!这是预期的结果,因为“perseus”本身不再是一个元素。

  6. 重要提示!:完成测试后,请记住取消设置 IFS 以将其恢复为默认值(未设置):

    unset IFS
    

So so far this method seems to work, you just have to be careful and choose a character for IFS that you are sure your elements won't contain.

到目前为止,这种方法似乎有效,您只需要小心并为 IFS 选择一个您确定您的元素不会包含的字符。

Hope it helps anyone!

希望它可以帮助任何人!

Regards, Fred

问候, 弗雷德

回答by newbie

In most cases, the following would work. Certainly it has restrictions and limitations, but easy to read and understand.

在大多数情况下,以下方法会起作用。当然,它有限制和局限,但易于阅读和理解。

if [ "$(echo " ${array[@]} " | grep " $test ")" == "" ]; then
    echo notFound
else
    echo found
fi

回答by James

q=( 1 2 3 )
[ "${q[*]/1/}" = "${q[*]}" ] && echo not in array || echo in array 
#in array
[ "${q[*]/7/}" = "${q[*]}" ] && echo not in array || echo in array 
#not in array

回答by David C. Rankin

#!/bin/bash

test="name"

array=('hello' 'world' 'my' 'yourname' 'name' 'is' 'perseus')
nelem=${#array[@]}
[[ "${array[0]} " =~ "$test " ]] || 
[[ "${array[@]:1:$((nelem-1))}" =~ " $test " ]] || 
[[ " ${array[$((nelem-1))]}" =~ " $test" ]] && 
echo "found $test" || echo "$test not found"

Just treat the expanded array as a string and check for a substring, but to isolate the first and last element to ensure they are not matched as part of a lesser-included substring, they must be tested separately.

只需将扩展数组视为字符串并检查子字符串,但要隔离第一个和最后一个元素以确保它们不作为较少包含的子字符串的一部分匹配,则必须单独测试它们。

回答by hrez

Instead of iterating over the array elements it is possible to use parameter expansion to delete the specified string as an array item (for further information and examples see Messing with arrays in bashand Modify every element of a Bash array without looping).

可以使用参数扩展来删除作为数组项的指定字符串,而不是迭代数组元素(有关更多信息和示例,请参阅在 bash 中使用数组修改 Bash 数组的每个元素而不循环)。

(
set -f
export IFS=""

test='henry'
test='perseus'

array1=('hello' 'world' 'my' 'name' 'is' 'perseus')
#array1=('hello' 'world' 'my' 'name' 'is' 'perseusXXX' 'XXXperseus')

# removes empty string as array item due to IFS=""
array2=( ${array1[@]/#${test}/} )

n1=${#array1[@]}
n2=${#array2[@]}

echo "number of array1 items: ${n1}"
echo "number of array2 items: ${n2}"
echo "indices of array1: ${!array1[*]}"
echo "indices of array2: ${!array2[*]}"

echo 'array2:'
for ((i=0; i < ${#array2[@]}; i++)); do 
   echo "${i}: '${array2[${i}]}'"
done

if [[ $n1 -ne $n2 ]]; then
   echo "${test} is in array at least once! "
else
   echo "${test} is NOT in array! "
fi
)