Bash разделите строку на разделителе, назначьте сегменты массиву

В , я бы хотел чтобы преобразовать переменную среды, подобную PATH, которая может содержать элементы, разделенные пробелами, в array, убедившись, что элементы, несущие пробелы, не вызывают расщепления слов, появляясь как "несколько элементов".


Пусть PATH_VARIABLE - рассматриваемая переменная.

Пусть un:dodecaedro:per:tirare:per:i danni - содержимое переменной.

Это предназначено для желаемого array _ для элементов 6, а не 7.

0) un
1) dodecaedro
2) per
3) tirare
4) per
5) i danni

"Трудная" запись может быть разделенной пробелом: i danni.

Я ищу абсолютно оптимальный и правильный способ достижения этого.

Ограничение: оно должно работать с моим версия: v3.2.48(1)-release


В это делается просто красиво так:

>>> v='un:dodecaedro:per:tirare:per:i danni'
>>> len(v.split(':'))
6

Работает. Показывает то, что я ищу.


Какой лучший способ сделать это в нашей любимой bash?

Можете ли вы конкретно улучшить мою попытку 4?

Здесь мои попытки


#!/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

Моя среда

v3.2.48 (1 ) -release

OS X v10.8.3 (построить 12D78)


Ответ 1

f() {
  local IFS=:
  local foo
  set -f # Disable glob expansion
  foo=( [email protected] ) # 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

Изменив ответ Джима Макнамары, вы можете просто reset IFS:

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

Я предпочитаю область функций, потому что она защищает изменения IFS от кровотечения в глобальном масштабе, не требуя особой осторожности reset.

Редактирование и пояснения:

В качестве пояснения: во втором примере установка IFS меняет глобальную переменную. Существенная разница между этим:

IFS=: arr=( $foo )

и это:

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

заключается в том, что первая - это две переменные назначения и никакие команды, а последняя - простая команда (см. простую команду в man (1) bash.)

Демонстрация:

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

Ответ 2

# 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 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

Правило №1: не втискивайте составную структуру данных в строку или поток, если нет альтернативы. PATH - это один случай, когда вам приходится иметь дело с ним.

Правило №2: Избегайте разделения слов и полей любой ценой. Практически не существует законных оснований применять разбиение слов на значение параметра в неминерализованных оболочках, таких как Bash. Почти всех новичков можно избежать, просто не разбирая слово с IFS. Всегда цитируйте.

Ответ 3

Рассмотрим:

$ 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

Ну ладно, мне пришлось слишком долго отформатировать ответ... +1 @kojiro.