Get the index of a value in a Bash array

后端 未结 15 510
说谎
说谎 2021-01-30 03:41

I have something in bash like

myArray=(\'red\' \'orange\' \'green\')

And I would like to do something like

echo ${         


        
相关标签:
15条回答
  • 2021-01-30 04:26

    You must declare your array before use with

    declare -A myArray
    myArray=([red]=1 [orange]=2 [green]=3)
    echo ${myArray['orange']}
    
    0 讨论(0)
  • 2021-01-30 04:28

    In zsh you can do

    xs=( foo bar qux )
    echo ${xs[(ie)bar]}
    

    see zshparam(1) subsection Subscript Flags

    0 讨论(0)
  • 2021-01-30 04:32

    This will do it:

    #!/bin/bash
    
    my_array=(red orange green)
    value='green'
    
    for i in "${!my_array[@]}"; do
       if [[ "${my_array[$i]}" = "${value}" ]]; then
           echo "${i}";
       fi
    done
    

    Obviously, if you turn this into a function (e.g. get_index() ) - you can make it generic

    0 讨论(0)
  • 2021-01-30 04:32

    No. You can only index a simple array with an integer in bash. Associative arrays (introduced in bash 4) can be indexed by strings. They don't, however, provided for the type of reverse lookup you are asking for, without a specially constructed associative array.

    $ declare -A myArray
    $ myArray=([red]=0 [orange]=1 [green]=2)
    $ echo ${myArray[green]}
    2
    
    0 讨论(0)
  • 2021-01-30 04:34

    This is just another way to initialize an associative array as chepner showed. Don't forget that you need to explicitly declare or typset an associative array with -A attribute.

    i=0; declare -A myArray=( [red]=$((i++)) [orange]=$((i++)) [green]=$((i++)) )
    echo ${myArray[green]}
    2
    

    This removes the need to hard code values and makes it unlikely you will end up with duplicates.

    If you have lots of values to add it may help to put them on separate lines.

    i=0; declare -A myArray; 
    myArray+=( [red]=$((i++)) )
    myArray+=( [orange]=$((i++)) )
    myArray+=( [green]=$((i++)) )
    echo ${myArray[green]}
    2
    

    Say you want an array of numbers and lowercase letters (eg: for a menu selection) you can also do something like this.

    declare -a mKeys_1=( {{0..9},{a..z}} );
    i=0; declare -A mKeys_1_Lookup; eval mKeys_1_Lookup[{{0..9},{a..z}}]="$((i++))";
    

    If you then run

    echo "${mKeys_1[15]}"
    f
    echo "${mKeys_1_Lookup[f]}"
    15
    
    0 讨论(0)
  • 2021-01-30 04:36

    Another tricky one-liner:

    index=$((-1 + 10#0$(IFS=$'\n' echo "${my_array[*]}" | grep --line-number --fixed-strings -- "$value" | cut -f1 -d:)))
    

    features:

    • supports elements with spaces
    • returns -1 when not found

    caveats:

    • requires value to be non-empty
    • difficult to read

    Explanations by breaking it down in execution order:

    IFS=$'\n' echo "${my_array[*]}"
    

    set array expansion separator (IFS) to a new line char & expand the array

    grep --line-number --fixed-strings -- "$value"
    

    grep for a match:

    • show line numbers (--line-number or -n)
    • use a fixed string (--fixed-strings or -F; disables regex)
    • allow for elements starting with a - (--)

      cut -f1 -d:

    extract only the line number (format is <line_num>:<matched line>)

    $((-1 + 10#0$(...)))
    

    subtract 1 since line numbers are 1-indexed and arrays are 0-indexed

    • if $(...) does not match:

      • nothing is returned & the default of 0 is used (10#0)
    • if $(...) matches:
      • a line number exists & is prefixed with 10#0; i.e. 10#02, 10#09, 10#014, etc
      • the 10# prefix forces base-10/decimal numbers instead of octal


    Using awk instead of grep, cut & bash arithmetic:

    IFS=$'\n'; awk "\$0 == \"${value//\"/\\\"}\" {print NR-1}" <<< "${my_array[*]}"
    

    features:

    • supports elements with spaces
    • supports empty elements
    • less commands opened in a subshell

    caveats:

    • returns when not found

    Explanations by breaking it down in execution order:

    IFS=$'\n' [...] <<< "${my_array[*]}"
    

    set array expansion separator (IFS) to a new line char & expand the array

    awk "\$0 == \"${value//\"/\\\"}\" {print NR-1}"
    

    match the entire line & print the 0-indexed line number

    • ${value//\"/\\\"} replaces double quotes in $value with escaped versions
    • since we need variable substitution, this segment has more escaping than wanted
    0 讨论(0)
提交回复
热议问题