Check if a Bash array contains a value

前端 未结 30 2440
执笔经年
执笔经年 2020-11-22 07:14

In Bash, what is the simplest way to test if an array contains a certain value?

相关标签:
30条回答
  • 2020-11-22 08:03

    If you want to do a quick and dirty test to see if it's worth iterating over the whole array to get a precise match, Bash can treat arrays like scalars. Test for a match in the scalar, if none then skipping the loop saves time. Obviously you can get false positives.

    array=(word "two words" words)
    if [[ ${array[@]} =~ words ]]
    then
        echo "Checking"
        for element in "${array[@]}"
        do
            if [[ $element == "words" ]]
            then
                echo "Match"
            fi
        done
    fi
    

    This will output "Checking" and "Match". With array=(word "two words" something) it will only output "Checking". With array=(word "two widgets" something) there will be no output.

    0 讨论(0)
  • 2020-11-22 08:04

    One-line solution

    printf '%s\n' "${myarray[@]}" | grep -P '^mypattern$'
    

    Explanation

    The printf statement prints each element of the array on a separate line.

    The grep statement uses the special characters ^ and $ to find a line that contains exactly the pattern given as mypattern (no more, no less).


    Usage

    To put this into an if ... then statement:

    if printf '%s\n' "${myarray[@]}" | grep -q -P '^mypattern$'; then
        # ...
    fi
    

    I added a -q flag to the grep expression so that it won't print matches; it will just treat the existence of a match as "true."

    0 讨论(0)
  • 2020-11-22 08:04

    Here's a compilation of several possible implementations, complete with integrated verification and simple benchmarking:

    #!/usr/bin/env bash
    
    # Check if array contains item [$1: item, $2: array name]
    function in_array_1() {
        local needle="$1" item
        local -n arrref="$2"
        for item in "${arrref[@]}"; do
            [[ "${item}" == "${needle}" ]] && return 0
        done
        return 1
    }
    
    # Check if array contains item [$1: item, $2: array name]
    function in_array_2() {
        local needle="$1" arrref="$2[@]" item
        for item in "${!arrref}"; do
            [[ "${item}" == "${needle}" ]] && return 0
        done
        return 1
    }
    
    # Check if array contains item [$1: item, $2: array name]
    function in_array_3() {
        local needle="$1" i
        local -n arrref="$2"
        for ((i=0; i < ${#arrref[@]}; i++)); do
            [[ "${arrref[i]}" == "${needle}" ]] && return 0
        done
        return 1
    }
    
    # Check if array contains item [$1: item, $2..$n: array items]
    function in_array_4() {
        local needle="$1" item
        shift
        for item; do
            [[ "${item}" == "${needle}" ]] && return 0
        done
        return 1
    }
    
    # Check if array contains item [$1: item, $2..$n: array items]
    function in_array_5() {
        local needle="$1" item
        for item in "${@:2}"; do
            [[ "${item}" == "${needle}" ]] && return 0
        done
        return 1
    }
    
    # Check if array contains item [$1: item, $2: array name]
    function in_array_6() {
        local needle="$1" arrref="$2[@]" array i
        array=("${!arrref}")
        for ((i=0; i < ${#array[@]}; i++)); do
            [[ "${array[i]}" == "${needle}" ]] && return 0
        done
        return 1
    }
    
    # Check if array contains item [$1: item, $2..$n: array items]
    function in_array_7() {
        local needle="$1" array=("${@:2}") item
        for item in "${array[@]}"; do
            [[ "${item}" == "${needle}" ]] && return 0
        done
        return 1
    }
    
    # Check if array contains item [$1: item, $2..$n: array items]
    function in_array_8() {
        local needle="$1"
        shift
        while (( $# > 0 )); do
            [[ "$1" == "${needle}" ]] && return 0
            shift
        done
        return 1
    }
    
    
    #------------------------------------------------------------------------------
    
    
    # Generate map for array [$1: name of source array, $2: name of target array]
    # NOTE: target array must be pre-declared by caller using 'declare -A <name>'
    function generate_array_map() {
        local -n srcarr="$1" dstmap="$2"
        local i key
        dstmap=()
        for i in "${!srcarr[@]}"; do
            key="${srcarr[i]}"
            [[ -z ${dstmap["${key}"]+set} ]] && dstmap["${key}"]=${i} || dstmap["${key}"]+=,${i}
        done
    }
    
    # Check if array contains item [$1: item, $2: name of array map]
    function in_array_9() {
        local needle="$1"
        local -n mapref="$2"
        [[ -n "${mapref["${needle}"]+set}" ]] && return 0 || return 1
    }
    
    
    #------------------------------------------------------------------------------
    
    
    # Test in_array function [$1: function name, $2: function description, $3: test array size]
    function test() {
        local tname="$1" tdesc="$2" tn=$3 ti=0 tj=0 ta=() tct=0 tepapre="" tepapost="" tepadiff=()
        local -A tam=()
    
        echo -e "\e[1m${tname} (${tdesc}):\e[0m"
    
        # Generate list of currently defined variables
        tepapre="$(compgen -v)"
    
        # Fill array with random items
        for ((ti=0; ti < ${tn}; ti++)); do
            ta+=("${RANDOM} ${RANDOM} ${RANDOM} ${RANDOM}")
        done
    
        # Determine function call type (pass array items, pass array name, pass array map)
        case "${tname}" in
            "in_array_1"|"in_array_2"|"in_array_3"|"in_array_6") tct=0; ;;
            "in_array_4"|"in_array_5"|"in_array_7"|"in_array_8") tct=1; ;;
            "in_array_9") generate_array_map ta tam; tct=2; ;;
            *) echo "Unknown in_array function '${tname}', aborting"; return 1; ;;
        esac
    
        # Verify in_array function is working as expected by picking a few random
        # items and checking
        echo -e "\e[1mVerification...\e[0m"
        for ((ti=0; ti < 10; ti++)); do
            tj=$(( ${RANDOM} % ${#ta[@]} ))
            echo -n "Item ${tj} '${ta[tj]}': "
            if (( ${tct} == 0 )); then
                "${tname}" "${ta[tj]}" ta && echo -en "\e[1;32mok\e[0m" || echo -en "\e[1;31mnok\e[0m"
                echo -n " "
                "${tname}" "${ta[tj]}.x" ta && echo -en "\e[1;31mnok\e[0m" || echo -en "\e[1;32mok\e[0m"
            elif (( ${tct} == 1 )); then
                "${tname}" "${ta[tj]}" "${ta[@]}" && echo -en "\e[1;32mok\e[0m" || echo -en "\e[1;31mnok\e[0m"
                echo -n " "
                "${tname}" "${ta[tj]}.x" "${ta[@]}" && echo -en "\e[1;31mnok\e[0m" || echo -en "\e[1;32mok\e[0m"
            elif (( ${tct} == 2 )); then
                "${tname}" "${ta[tj]}" tam && echo -en "\e[1;32mok\e[0m" || echo -en "\e[1;31mnok\e[0m"
                echo -n " "
                "${tname}" "${ta[tj]}.x" tam && echo -en "\e[1;31mnok\e[0m" || echo -en "\e[1;32mok\e[0m"
            fi
            echo
        done
    
        # Benchmark in_array function
        echo -en "\e[1mBenchmark...\e[0m"
        time for ((ti=0; ti < ${#ta[@]}; ti++)); do
            if (( ${tct} == 0 )); then
                "${tname}" "${ta[ti]}" ta
            elif (( ${tct} == 1 )); then
                "${tname}" "${ta[ti]}" "${ta[@]}"
            elif (( ${tct} == 2 )); then
                "${tname}" "${ta[ti]}" tam
            fi
        done
    
        # Generate list of currently defined variables, compare to previously
        # generated list to determine possible environment pollution
        echo -e "\e[1mEPA test...\e[0m"
        tepapost="$(compgen -v)"
        readarray -t tepadiff < <(echo -e "${tepapre}\n${tepapost}" | sort | uniq -u)
        if (( ${#tepadiff[@]} == 0 )); then
            echo -e "\e[1;32mclean\e[0m"
        else
            echo -e "\e[1;31mpolluted:\e[0m ${tepadiff[@]}"
        fi
    
        echo
    }
    
    
    #------------------------------------------------------------------------------
    
    
    # Test in_array functions
    n=5000
    echo
    ( test in_array_1 "pass array name, nameref reference, for-each-loop over array items" ${n} )
    ( test in_array_2 "pass array name, indirect reference, for-each-loop over array items" ${n} )
    ( test in_array_3 "pass array name, nameref reference, c-style for-loop over array items by index" ${n} )
    ( test in_array_4 "pass array items, for-each-loop over arguments" ${n} )
    ( test in_array_5 "pass array items, for-each-loop over arguments as array" ${n} )
    ( test in_array_6 "pass array name, indirect reference + array copy, c-style for-loop over array items by index" ${n} )
    ( test in_array_7 "pass array items, copy array from arguments as array, for-each-loop over array items" ${n} )
    ( test in_array_8 "pass array items, while-loop, shift over arguments" ${n} )
    ( test in_array_9 "pre-generated array map, pass array map name, direct test without loop" ${n} )
    

    Results:

    in_array_1 (pass array name, nameref reference, for-each-loop over array items):
    Verification...
    Item 862 '19528 10140 12669 17820': ok ok
    Item 2250 '27262 30442 9295 24867': ok ok
    Item 4794 '3857 17404 31925 27993': ok ok
    Item 2532 '14553 12282 26511 32657': ok ok
    Item 1911 '21715 8066 15277 27126': ok ok
    Item 4289 '3081 10265 16686 19121': ok ok
    Item 4837 '32220 1758 304 7871': ok ok
    Item 901 '20652 23880 20634 14286': ok ok
    Item 2488 '14578 8625 30251 9343': ok ok
    Item 4165 '4514 25064 29301 7400': ok ok
    Benchmark...
    real    1m11,796s
    user    1m11,262s
    sys     0m0,473s
    EPA test...
    clean
    
    in_array_2 (pass array name, indirect reference, for-each-loop over array items):
    Verification...
    Item 2933 '17482 25789 27710 2096': ok ok
    Item 3584 '876 14586 20885 8567': ok ok
    Item 872 '176 19749 27265 18038': ok ok
    Item 595 '6597 31710 13266 8813': ok ok
    Item 748 '569 9200 28914 11297': ok ok
    Item 3791 '26477 13218 30172 31532': ok ok
    Item 2900 '3059 8457 4879 16634': ok ok
    Item 676 '23511 686 589 7265': ok ok
    Item 2248 '31351 7961 17946 24782': ok ok
    Item 511 '8484 23162 11050 426': ok ok
    Benchmark...
    real    1m11,524s
    user    1m11,086s
    sys     0m0,437s
    EPA test...
    clean
    
    in_array_3 (pass array name, nameref reference, c-style for-loop over array items by index):
    Verification...
    Item 1589 '747 10250 20133 29230': ok ok
    Item 488 '12827 18892 31996 1977': ok ok
    Item 801 '19439 25243 24485 24435': ok ok
    Item 2588 '17193 18893 21610 9302': ok ok
    Item 4436 '7100 655 8847 3068': ok ok
    Item 2620 '19444 6457 28835 24717': ok ok
    Item 4398 '4420 16336 612 4255': ok ok
    Item 2430 '32397 2402 12631 29774': ok ok
    Item 3419 '906 5361 32752 7698': ok ok
    Item 356 '9776 16485 20838 13330': ok ok
    Benchmark...
    real    1m17,037s
    user    1m17,019s
    sys     0m0,005s
    EPA test...
    clean
    
    in_array_4 (pass array items, for-each-loop over arguments):
    Verification...
    Item 1388 '7932 15114 4025 15625': ok ok
    Item 3900 '23863 25328 5632 2752': ok ok
    Item 2678 '31296 4216 17485 8874': ok ok
    Item 1893 '16952 29047 29104 23384': ok ok
    Item 1616 '19543 5999 4485 22929': ok ok
    Item 93 '14456 2806 12829 19552': ok ok
    Item 265 '30961 19733 11863 3101': ok ok
    Item 4615 '10431 9566 25767 13518': ok ok
    Item 576 '11726 15104 11116 74': ok ok
    Item 3829 '19371 25026 6252 29478': ok ok
    Benchmark...
    real    1m30,912s
    user    1m30,740s
    sys     0m0,011s
    EPA test...
    clean
    
    in_array_5 (pass array items, for-each-loop over arguments as array):
    Verification...
    Item 1012 '29213 31971 21483 30225': ok ok
    Item 2802 '4079 5423 29240 29619': ok ok
    Item 473 '6968 798 23936 6852': ok ok
    Item 2183 '20734 4521 30800 2126': ok ok
    Item 3059 '14952 9918 15695 19309': ok ok
    Item 1424 '25784 28380 14555 21893': ok ok
    Item 1087 '16345 19823 26210 20083': ok ok
    Item 257 '28890 5198 7251 3866': ok ok
    Item 3986 '29035 19288 12107 3857': ok ok
    Item 2509 '9219 32484 12842 27472': ok ok
    Benchmark...
    real    1m53,485s
    user    1m53,404s
    sys     0m0,077s
    EPA test...
    clean
    
    in_array_6 (pass array name, indirect reference + array copy, c-style for-loop over array items by index):
    Verification...
    Item 4691 '25498 10521 20673 14948': ok ok
    Item 263 '25265 29824 3876 14088': ok ok
    Item 2550 '2416 14274 12594 29740': ok ok
    Item 2269 '2769 11436 3622 28273': ok ok
    Item 3246 '23730 25956 3514 17626': ok ok
    Item 1059 '10776 12514 27222 15640': ok ok
    Item 53 '23813 13365 16022 4092': ok ok
    Item 1503 '6593 23540 10256 17818': ok ok
    Item 2452 '12600 27404 30960 26759': ok ok
    Item 2526 '21190 32512 23651 7865': ok ok
    Benchmark...
    real    1m54,793s
    user    1m54,326s
    sys     0m0,457s
    EPA test...
    clean
    
    in_array_7 (pass array items, copy array from arguments as array, for-each-loop over array items):
    Verification...
    Item 2212 '12127 12828 27570 7051': ok ok
    Item 1393 '19552 26263 1067 23332': ok ok
    Item 506 '18818 8253 14924 30710': ok ok
    Item 789 '9803 1886 17584 32686': ok ok
    Item 1795 '19788 27842 28044 3436': ok ok
    Item 376 '4372 16953 17280 4031': ok ok
    Item 4846 '19130 6261 21959 6869': ok ok
    Item 2064 '2357 32221 22682 5814': ok ok
    Item 4866 '10928 10632 19175 14984': ok ok
    Item 1294 '8499 11885 5900 6765': ok ok
    Benchmark...
    real    2m35,012s
    user    2m33,578s
    sys     0m1,433s
    EPA test...
    clean
    
    in_array_8 (pass array items, while-loop, shift over arguments):
    Verification...
    Item 134 '1418 24798 20169 9501': ok ok
    Item 3986 '12160 12021 29794 29236': ok ok
    Item 1607 '26633 14260 18227 898': ok ok
    Item 2688 '18387 6285 2385 18432': ok ok
    Item 603 '1421 306 6102 28735': ok ok
    Item 625 '4530 19718 30900 1938': ok ok
    Item 4033 '9968 24093 25080 8179': ok ok
    Item 310 '6867 9884 31231 29173': ok ok
    Item 661 '3794 4745 26066 22691': ok ok
    Item 4129 '3039 31766 6714 4921': ok ok
    Benchmark...
    real    5m51,097s
    user    5m50,566s
    sys     0m0,495s
    EPA test...
    clean
    
    in_array_9 (pre-generated array map, pass array map name, direct test without loop):
    Verification...
    Item 3696 '661 6048 13881 26901': ok ok
    Item 815 '29729 13733 3935 20697': ok ok
    Item 1076 '9220 3405 18448 7240': ok ok
    Item 595 '8912 2886 13678 24066': ok ok
    Item 2803 '13534 23891 5344 652': ok ok
    Item 1810 '12528 32150 7050 1254': ok ok
    Item 4055 '21840 7436 1350 15443': ok ok
    Item 2416 '19550 28434 17110 31203': ok ok
    Item 1630 '21054 2819 7527 953': ok ok
    Item 1044 '30152 22211 22226 6950': ok ok
    Benchmark...
    real    0m0,128s
    user    0m0,128s
    sys     0m0,000s
    EPA test...
    clean
    
    0 讨论(0)
  • 2020-11-22 08:05

    Combining a few of the ideas presented here you can make an elegant if statment without loops that does exact word matches.

    find="myword"
    array=(value1 value2 myword)
    if [[ ! -z $(printf '%s\n' "${array[@]}" | grep -w $find) ]]; then
      echo "Array contains myword";
    fi
    

    This will not trigger on word or val, only whole word matches. It will break if each array value contains multiple words.

    0 讨论(0)
  • 2020-11-22 08:07

    My version of the regular expressions technique that's been suggested already:

    values=(foo bar)
    requestedValue=bar
    
    requestedValue=${requestedValue##[[:space:]]}
    requestedValue=${requestedValue%%[[:space:]]}
    [[ "${values[@]/#/X-}" =~ "X-${requestedValue}" ]] || echo "Unsupported value"
    

    What's happening here is that you're expanding the entire array of supported values into words and prepending a specific string, "X-" in this case, to each of them, and doing the same to the requested value. If this one is indeed contained in the array, then the resulting string will at most match one of the resulting tokens, or none at all in the contrary. In the latter case the || operator triggers and you know you're dealing with an unsupported value. Prior to all of that the requested value is stripped of all leading and trailing whitespace through standard shell string manipulation.

    It's clean and elegant, I believe, though I'm not too sure of how performant it may be if your array of supported values is particularly large.

    0 讨论(0)
  • 2020-11-22 08:08

    The answer with most votes is very concise and clean, but it can have false positives when a space is part of one of the array elements. This can be overcome when changing IFS and using "${array[*]}" instead of "${array[@]}". The method is identical, but it looks less clean. By using "${array[*]}", we print all elements of $array, separated by the first character in IFS. So by choosing a correct IFS, you can overcome this particular issue. In this particular case, we decide to set IFS to an uncommon character $'\001' which stands for Start of Heading (SOH)

    $ array=("foo bar" "baz" "qux")
    $ IFS=$'\001'
    $ [[ "$IFS${array[*]}$IFS" =~ "${IFS}foo${IFS}" ]] && echo yes || echo no
    no
    $ [[ "$IFS${array[*]}$IFS" =~ "${IFS}foo bar${IFS}" ]] && echo yes || echo no
    yes
    $ unset IFS
    

    This resolves most issues false positives, but requires a good choice of IFS.

    note: If IFS was set before, it is best to save it and reset it instead of using unset IFS


    related:

    • Accessing bash command line args $@ vs $*
    0 讨论(0)
提交回复
热议问题