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

后端 未结 9 2300
眼角桃花
眼角桃花 2021-02-13 18:54

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 impl

相关标签:
9条回答
  • 2021-02-13 19:12

    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 bash and Modify every element of a Bash array without looping).

    (
    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
    )
    
    0 讨论(0)
  • 2021-02-13 19:14

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

    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.

    External Grep Process

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

    Bash Regular Expression Test

    array=('hello' 'world' 'my' 'name' 'is' 'perseus')
    word="world"
    if [[ "${array[*]}" =~ (^|[^[:alpha:]])$word([^[:alpha:]]|$) ]]; then
        : # do something
    fi
    
    0 讨论(0)
  • 2021-02-13 19:17

    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
    
    0 讨论(0)
  • 2021-02-13 19:20

    You can use an associative array since you're using 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.

    0 讨论(0)
  • 2021-02-13 19:25
    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
    
    0 讨论(0)
  • 2021-02-13 19:31

    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):

    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.

    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
      

    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.

    Hope it helps anyone!

    Regards, Fred

    0 讨论(0)
提交回复
热议问题