Bash split string on delimiter, assign segments to array

后端 未结 3 1237
野趣味
野趣味 2020-12-31 09:28

In bash, I would like to transform a PATH-like environment variable that may contain space-separated elements into an array, making sure eleme

相关标签:
3条回答
  • 2020-12-31 09:47

    Consider:

    $ foo='1:2 3:4 5:6'
    $ IFS=':'; arr=($foo)
    $ echo "${arr[0]}"
    1
    $ echo "${arr[1]}"
    2 3
    $ echo "${arr[2]}"
    4 5
    $ echo "${arr[3]}"
    6
    

    Oh well - took me too long to format an answer... +1 @kojiro.

    0 讨论(0)
  • 2020-12-31 09:56
    # Right. Add -d '' if PATH members may contain newlines.
    IFS=: read -ra myPath <<<"$PATH"
    
    # Wrong!
    IFS=: myPath=($PATH)
    
    # Wrong!
    IFS=:
    for x in $PATH; do ...
    
    # How to do it wrong right...
    # Works around some but not all word split problems
    # For portability, some extra wrappers are needed and it's even harder.
    function stupidSplit {
        if [[ -z $3 ]]; then
            return 1
        elif [[ $- != *f* ]]; then
            trap 'trap RETURN; set +f' RETURN
            set -f
        fi
        IFS=$3 command eval "${1}=(\$${2})"
    }
    
    function main {
        typeset -a myPath
        if ! stupidSplit myPath PATH :; then
            echo "Don't pass stupid stuff to stupidSplit" >&2
            return 1
        fi
    }
    
    main
    

    Rule #1: Don't cram a compound data structure into a string or stream unless there's no alternative. PATH is one case where you have to deal with it.

    Rule #2: Avoid word / field splitting at all costs. There are almost no legitimate reasons to apply word splitting on the value of a parameter in non-minimalist shells such as Bash. Almost all beginner pitfalls can be avoided by just never word splitting with IFS. Always quote.

    0 讨论(0)
  • 2020-12-31 10:06
    f() {
      local IFS=:
      local foo
      set -f # Disable glob expansion
      foo=( $@ ) # Deliberately unquoted 
      set +f
      printf '%d\n' "${#foo[@]}"
      printf '%s\n' "${foo[@]}"
    }
    
    f 'un:dodecaedro:per:tirare:per:i danni'
    6
    un
    dodecaedro
    per
    tirare
    per
    i danni
    

    Modifying Jim McNamara's answer, you could just reset IFS:

    oIFS="$IFS"
    foo='un:dodecaedro:per:tirare:per:i danni'
    IFS=: arr=( $foo )
    IFS="$oIFS"
    

    I prefer the function scope because it protects IFS changes from bleeding into the global scope without requiring special care to reset it.

    Edits and explanations:

    As a matter of clarification: In the second example, the IFS setting does change the global variable. The salient difference between this:

    IFS=: arr=( $foo )
    

    and this:

    IFS=: read -a arr <<< "$foo"
    

    is that the former is two variable assignments and no commands, and the latter is a simple command (see simple command in man (1) bash.)

    Demonstration:

    $ echo "$BASH_VERSION"
    3.2.48(1)-release
    $ echo "$IFS"
    
    
    $ foo='un:dodecaedro:per:tirare:per:i danni'
    $ IFS=: read -a arr <<< "$foo"
    $ echo "${#arr[@]}"
    6
    $ echo "$IFS"
    
    
    $ IFS=: arr1=( $foo )
    $ echo "${#arr1[@]}"
    6
    $ echo "$IFS"
    :
    
    0 讨论(0)
提交回复
热议问题