Bash split string on delimiter, assign segments to array

bash

Solution

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

Problem

In bash, I would like to transform a PATH-like environment variable that may contain space-separated elements into an `array`, making sure elements bearing spaces do not cause word-splitting, appearing as "multiple elements". Let `PATH_VARIABLE` be the variable in question. Let `un:dodecaedro:per:tirare:per:i danni` be the content of the variable. It is intended for the desired `array` _to have `6` elements, not `7`. ``` 0) un 1) dodecaedro 2) per 3) tirare 4) per 5) i danni ``` The "tricky" entry may be the space-separated value: `i danni`. I am looking for the absolute most elegant and correct way to achieve this. Limitation: it must work with my bash version: `v3.2.48(1)-release` In python this is done just beautifully as so: ``` >>> v='un:dodecaedro:per:tirare:per:i danni' >>> len(v.split(':')) 6 ``` Works. Shows what I am looking for. What's the best way to do this in our beloved bash? Can you specifically improve on my attempt `4`? Here my attempts ``` #!/bin/bash PATH_VARIABLE='un:dodecaedro:per:tirare:per:i danni' # WRONG a1=($(echo $PATH_VARIABLE | tr ':' '\n')) # WRONG a2=($( while read path_component; do echo "$path_component" done < <(echo "$PATH_VARIABLE" | tr ':' '\n') )) # WORKS, it is elegant.. but I have no bash 4! # readarray -t a3 < <(echo "$PATH_VARIABLE" | tr ':' '\n') # WORKS, but it looks "clunky" to me :( i=0 while read line; do a4[i++]=$line done < <(echo "$PATH_VARIABLE" | tr ':' '\n') n=${#a4[@]} for ((i=0; i < n; i++)); do printf '%2d) %s\n' "$i" "${a4[i]}" done ``` My environment bash v3.2.48(1)-release osx OS X v10.8.3 (build 12D78)

Original source