Понимание этого потребует некоторых усилий. Будьте терпеливы. Решение будет корректно работать в bash. Необходимы некоторые "башисты".
Первое: Нам нужно использовать "Косвенный" доступ к переменной ${!variable}
. Если $variable
содержит строку animal_name
, то "Расширение параметра": ${!variable}
расширится до содержимого $animal_name
.
Давайте посмотрим эту идею в действии, я сохранил имена и значения, которые вы использовали, где это возможно, чтобы вам было легче понять:
#!/bin/bash
function delim_to_array() {
local VarName=$1
local IFS="$2";
printf "inside IFS=<%s>\n" "$IFS"
echo "inside var $VarName"
echo "inside list = ${!VarName}"
echo a\=\(${!VarName}\)
eval a\=\(${!VarName}\)
printf "in <%s> " "${a[@]}"; echo
eval $VarName\=\(${!VarName}\)
}
animal_list="anaconda, bison, cougar, dingo"
delim_to_array "animal_list" ","
printf "out <%s> " "${animal_list[@]}"; echo
printf "outside IFS=<%s>\n" "$IFS"
# Now we can use animal_name as an array
for animal in "${animal_list[@]}"; do
echo "NAME: $animal"
done
Если этот полный скрипт будет выполнен (предположим, что он называется so-setvar.sh), вы должны увидеть:
$ ./so-setvar.sh
inside IFS=<,>
inside var animal_list
inside list = anaconda, bison, cougar, dingo
a=(anaconda bison cougar dingo)
in in in in
out out out out
outside IFS=<
>
NAME: anaconda
NAME: bison
NAME: cougar
NAME: dingo
Поймите, что "внутри" означает "внутри функции", а "снаружи" - наоборот.
Значение внутри $VarName
- это имя переменной: animal_list
, как строка.
Значение в ${!VarName}
- это список: anaconda, bison, cougar, dingo
Теперь, чтобы показать, как построено решение, есть строка с echo:
echo a\=\(${!VarName}\)
которая показывает, что выполняет следующая строка с eval
:
a=(anaconda bison cougar dingo)
После того, как evaluated, переменная a
представляет собой массив со списком животных. В данном случае переменная a используется для того, чтобы показать, как именно eval влияет на нее.
А затем значения каждого элемента a
выводятся как
.
И то же самое выполняется во внешней части функции как
Это показано в этих двух строках:
in in in in
out out out out
Обратите внимание, что реальное изменение было выполнено в последнем eval функции.
Вот и все, готово. Теперь var имеет массив значений.
Фактически, ядро функции состоит из одной строки: eval $VarName\=\(${!VarName}\)
Кроме того, значение IFS установлено как локальное для функции, что заставляет его вернуться к значению, которое оно имело до выполнения функции, без дополнительной работы. Спасибо Peter Cordes за комментарий к оригинальной идее.
На этом объяснение закончено, надеюсь, все понятно.
Если мы удалим все ненужные строки, оставив только ядро eval, и создадим новую переменную для IFS, мы сократим функцию до минимального выражения:
delim_to_array() {
local IFS="${2:-$' :|'}"
eval $1\=\(${!1}\);
}
Установка значения IFS в качестве локальной переменной позволяет нам также установить значение "по умолчанию" для функции. Всякий раз, когда значение, необходимое для IFS, не передается в функцию в качестве второго аргумента, локальная IFS принимает значение "по умолчанию". Мне показалось, что по умолчанию должны быть пробел ( ) (который всегда является полезным разделяющим значением), двоеточие (:) и вертикальная линия (|). Любое из этих трех значений будет разделять значения. Конечно, по умолчанию можно установить любые другие значения, которые соответствуют вашим потребностям.
read
:Чтобы уменьшить риск некавычек в eval, можно использовать:
delim_to_array() {
local IFS="${2:-$' :|'}"
# eval $1\=\(${!1}\);
read -ra "$1" <<<"${!1}"
}
test="fail-test"; a="fail-test"
animal_list='bison, a space, {1..3},~/,${a},$a,$((2+2)),$(echo "fail"),./*,*,*'
delim_to_array "animal_list" ","
printf "<%s>" "${animal_list[@]}"; echo
$ so-setvar.sh
< a space>< {1..3}><~/><${a}><$a><$((2+2))><$(echo "fail")><./*><*><*>
Большинство значений, заданных выше для var animal_list
, не работают с eval.
Но проходят read без проблем.
eval
. Чтобы действительно понять, что и как работает эта функция, я переписал код, который вы опубликовали, используя эту функцию:
#!/bin/bash
delim_to_array() {
local IFS="${2:-$' :|'}"
# printf "inside IFS=<%s>\n" "$IFS"
# eval $1\=\(${!1}\);
read -ra "$1" <<<"${!1}";
}
animal_list="anaconda, bison, cougar, dingo"
delim_to_array "animal_list" ","
printf "NAME: %s\t " "${animal_list[@]}"; echo
people_list="alvin|baron|caleb|doug"
delim_to_array "people_list"
printf "NAME: %s\t " "${people_list[@]}"; echo
$ ./so-setvar.sh
NAME: anaconda NAME: bison NAME: cougar NAME: dingo
NAME: alvin NAME: baron NAME: caleb NAME: doug
Как вы можете видеть, IFS устанавливается только внутри функции, он не изменяется постоянно, и поэтому его не нужно переустанавливать на старое значение. Кроме того, второй вызов функции "people_list" использует значение IFS по умолчанию, нет необходимости устанавливать второй аргумент.
Как была построена функция (eval), есть одно место, в котором var раскрывается без кавычек для разбора оболочки. Это позволяет нам получить "разделение слов", используя значение IFS. Но это также подвергает значения переменных (если только некоторые кавычки не предотвращают это): "расширение скобок", "расширение тильды", "расширение параметров, переменных и арифметических операций", "подстановка команды" и "расширение имени пути", в таком порядке. И подстановка процесса ()
в системах, которые ее поддерживают.
Пример каждой из них (кроме последней) содержится в этом простом echo (будьте осторожны):
a=failed; echo {1..3} ~/ ${a} $a $((2+2)) $(ls) ./*
То есть, любая строка, которая начинается с {~$`
или может соответствовать имени файла, или содержит ?*[]
, является потенциальной проблемой.
Если вы уверены, что переменные не содержат таких проблемных значений, то вы в безопасности. Если есть вероятность наличия таких значений, то способы ответа на ваш вопрос более сложны и требуют большего (даже более длинного) описания и объяснения. Использование read
является альтернативой.
Да, read
имеет свою долю "драконов".
read
может получить только одну строку. Многострочные, даже при установке опции -d
, требуют особой осторожности. Иначе весь ввод будет присвоен одной переменной. IFS
содержит пробел, ведущие и последующие пробелы будут удалены. Ну, полное описание должно включать некоторые подробности о tab
, но я их пропущу. |
данные для чтения. Если вы это сделаете, read будет находиться во вложенной оболочке. Все переменные, установленные во вложенной оболочке, не сохраняются при возврате в родительскую оболочку. Есть некоторые обходные пути, но, опять же, я пропущу подробности. Я не хотел включать предупреждения и проблемы чтения, но по многочисленным просьбам пришлось их включить, извините.
Вы можете использовать класс символов [:punct:]
, если не возражаете, что он также соответствует другим знакам пунктуации и специальным символам:
grep '[[:punct:]]' file
grep "[]:/?#@\!\$&'()*+,;=%[]"
В выражении, заключенном в квадратные скобки, [...]
, очень немногие символы являются «специальными» (, только очень небольшое подмножество, например ]
, -
и ^
, и три комбинации [=
, [:
и [.
). При включении ]
в [...]
]
должно стоять первым (, возможно, после^
). Я решил поставить ]
первым, а [
последним для симметрии.
Единственное, что следует помнить, это то, что строка в одинарных кавычках не может содержать одинарные кавычки, поэтому мы заключаем выражение в двойные кавычки. Поскольку мы используем строку в двойных кавычках, оболочка будет копаться в ней, чтобы что-то расширить. По этой причине мы экранируем $
как \$
, что заставит оболочку дать литерал $
в grep
, и мы также экранируем !
как \!
, так как это расширение истории вbash
(только в интерактивных bash
оболочках ).
Если вы хотите включить в набор обратную косую черту, вам нужно экранировать ее как \\
, чтобы оболочка давала одну обратную косую черту для grep
. Кроме того, если вы хотите включить обратную кавычку `
, она также должна быть экранирована как \`
, так как в противном случае она запускает подстановку команд.
Приведенная выше команда извлечет любую строку, содержащую хотя бы один из символов в выражении, заключенном в квадратные скобки.
Использование строки в одинарных кавычках вместо строки в двойных кавычках позволяет избежать большинства неприятностей, связанных с тем, какие символы интерпретирует оболочка:
grep '[]:/?#@!$&'"'"'()*+,;=%[]'
Здесь единственное, что нужно помнить, помимо размещения ]
, это то, что строка в одинарных кавычках не может включать одинарную кавычку, поэтому вместо этого мы используем конкатенацию трех строк:
'[]:/?#@!$&'
"'"
'()*+,;=%[]'
Другим подходом может быть использование класса символов POSIX [[:punct:]]
. Это соответствует одному символу из набора !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
, который является большим набором, чем то, что дано в вопросе (, он дополнительно содержит "-.<>^_`{|}~
), но это все «знаки пунктуации», которые определяет POSIX..
LC_ALL=C grep '[[:punct:]]'