Я запутался в скрипте bash.
У меня есть следующий код:
function grep_search() {
magic_way_to_define_magic_variable_$1=`ls | tail -1`
echo $magic_variable_$1
}
Я хочу иметь возможность создать имя переменной, содержащей первый аргумент команды и содержащей значение, например, последней строки ls
.
Итак, чтобы проиллюстрировать, что я хочу:
$ ls | tail -1
stack-overflow.txt
$ grep_search() open_box
stack-overflow.txt
Итак, как я должен определить / объявить $magic_way_to_define_magic_variable_$1
и как я должен вызвать его в сценарии?
Я попытался eval
, ${...}
, \$${...}
, но я до сих пор путаю.
Ответы:
Используйте ассоциативный массив с именами команд в качестве ключей.
Если вы не можете использовать ассоциативные массивы (например, вы должны поддерживать
bash
3), вы можете использоватьdeclare
для создания динамических имен переменных:и использовать косвенное расширение параметра для доступа к значению.
См. BashFAQ: косвенное обращение - оценка косвенных / ссылочных переменных .
источник
-a
объявляет индексированный массив, а не ассоциативный массив. Если аргумент to неgrep_search
является числом, он будет рассматриваться как параметр с числовым значением (по умолчанию 0, если параметр не установлен).4.2.45(2)
и заявляю, что не перечисляю его как опциюdeclare: usage: declare [-afFirtx] [-p] [name[=value] ...]
. Похоже, работает правильно, однако.declare -h
в 4.2.45 (2) для меня показываетdeclare: usage: declare [-aAfFgilrtux] [-p] [name[=value] ...]
. Вы можете перепроверить, что вы используете 4.x, а не 3.2.declare $varname="foo"
?${!varname}
намного проще и широко совместимоЯ искал лучший способ сделать это в последнее время. Ассоциативный массив звучит как излишнее для меня. Смотри, что я нашел:
...а потом...
источник
prefix_${middle}_postfix
(т. е. ваше форматирование не будет работатьvarname=$prefix_suffix
)Помимо ассоциативных массивов, в Bash есть несколько способов достижения динамических переменных. Обратите внимание, что все эти методы представляют риски, которые обсуждаются в конце этого ответа.
В следующих примерах я буду предполагать, что
i=37
и что вы хотите присвоить псевдоним переменной,var_37
чье начальное значение равноlolilol
.Способ 1. Использование переменной «указатель»
Вы можете просто сохранить имя переменной в косвенной переменной, в отличие от указателя Си. Bash затем имеет синтаксис для чтения псевдонима переменной:
${!name}
расширяется до значения переменной, имя которой равно значению переменнойname
. Вы можете думать об этом как о двухэтапном расширении:${!name}
расширяется до$var_37
, которое расширяется доlolilol
.К сожалению, нет никакого аналога синтаксиса для изменения псевдонима переменной. Вместо этого вы можете выполнить задание с помощью одного из следующих приемов.
1a. Назначение с
eval
eval
это зло, но это также самый простой и самый портативный способ достижения нашей цели. Вы должны тщательно избегать правой части задания, так как оно будет оцениваться дважды . Простой и систематический способ сделать это - предварительно оценить правую часть (или использоватьprintf %q
).И вы должны вручную проверить, является ли левая часть допустимым именем переменной или именем с индексом (что, если это было
evil_code #
?). В отличие от всех других методов, приведенных ниже, применять его автоматически.Недостатки:
eval
это злоeval
это злоeval
это зло1б. Назначение с
read
read
Встроенное позволяет присвоить значение переменной из которых вы даете имя, факт , который может быть использован в сочетании с здесь-строками:IFS
Часть и опции-r
убедитесь , что значение присваивается как есть, в то время как опция-d ''
позволяет задавать значения нескольких строк. Из-за этой последней опции команда возвращается с ненулевым кодом выхода.Обратите внимание, что, поскольку мы используем здесь-строку, к значению добавляется символ новой строки.
Недостатки:
1c. Назначение с
printf
Начиная с Bash 3.1 (выпущен в 2005 году),
printf
встроенный модуль также может присваивать свой результат переменной, имя которой дано. В отличие от предыдущих решений, это просто работает, никаких дополнительных усилий не требуется, чтобы избежать чего-то, предотвратить расщепление и так далее.Недостатки:
Способ 2. Использование «ссылочной» переменной
Начиная с Bash 4.3 (выпущен в 2014 году),
declare
встроенная функция имеет опцию-n
для создания переменной, которая является «ссылкой на имя» на другую переменную, во многом как ссылки на C ++. Как и в методе 1, ссылка хранит имя переменной с псевдонимом, но при каждом обращении к ссылке (либо для чтения, либо для назначения) Bash автоматически разрешает косвенное обращение.Кроме того, Bash имеет специальное и очень запутанный синтаксис для получения значения самой ссылки, судьи сами:
${!ref}
.Это не помогает избежать ошибок, описанных ниже, но, по крайней мере, упрощает синтаксис.
Недостатки:
риски
Все эти методы сглаживания представляют несколько рисков. Первый - выполнение произвольного кода каждый раз, когда вы разрешаете косвенное обращение (либо для чтения, либо для назначения) . Действительно, вместо имени скалярной переменной, например
var_37
, вы можете использовать псевдоним массива, напримерarr[42]
. Но Bash оценивает содержимое квадратных скобок каждый раз, когда это необходимо, поэтому псевдонимыarr[$(do_evil)]
будут иметь неожиданные последствия ... Как следствие, используйте эти методы только тогда, когда вы контролируете происхождение псевдонима .Второй риск - создание циклического псевдонима. Поскольку переменные Bash идентифицируются по имени, а не по области действия, вы можете непреднамеренно создать псевдоним для себя (полагая, что это будет псевдоним переменной из входящей области). Это может произойти, в частности, при использовании общих имен переменных (например
var
). Как следствие, используйте эти методы только тогда, когда вы управляете именем переменной с псевдонимом .Источник:
источник
${!varname}
метод требует промежуточной переменной дляvarname
.Пример ниже возвращает значение $ name_of_var
источник
echo
s с подстановкой команд (которая пропускает кавычки) не требуется. Плюс, опция-n
должна быть предоставленаecho
. И, как всегда,eval
небезопасно. Но все это не нужно , поскольку Bash имеет более безопасный, более четкий и более короткий синтаксис для этой цели:${!var}
.Это должно работать:
источник
Это тоже будет работать
В твоем случае
источник
Согласно BashFAQ / 006 , вы можете использовать
read
с здесь строки синтаксис для назначения косвенных переменных:Использование:
источник
использование
declare
Нет необходимости использовать префиксы, как в других ответах, ни массивы. Используйте только
declare
, двойные кавычки , а также расширение параметра .Я часто использую следующую уловку для разбора списков
one to n
аргументов, содержащих аргументы, отформатированные какkey=value otherkey=othervalue etc=etc
, например:Но расширение списка argv вроде
Дополнительные советы
источник
printf
илиeval
Вау, большая часть синтаксиса ужасна! Вот одно решение с более простым синтаксисом, если вам нужно косвенно ссылаться на массивы:
Для более простых случаев использования я рекомендую синтаксис, описанный в Advanced Bash-Scripting Guide .
источник
foo_1
иfoo_2
свободны от пробелов и специальных символов. Примеры проблемных записей:'a b'
создадут две записи внутриmine
.''
не будет создавать запись внутриmine
.'*'
развернем до содержимого рабочего каталога. Вы можете предотвратить эти проблемы, цитируя:eval 'mine=( "${foo_'"$i"'[@]}" )'
[@]
конструкции."${array[@]}"
всегда будет расширяться до правильного списка записей без проблем, таких как разбиение слов или расширение*
. Кроме того, проблему разделения слов можно обойти, толькоIFS
если вы знаете любой ненулевой символ, который никогда не появляется внутри массива. Кроме того, буквальное обращение*
не может быть достигнуто установкойIFS
. Либо вы устанавливаетеIFS='*'
и разделяете на звезды, либо вы устанавливаетеIFS=somethingOther
и*
расширяетесь.|
илиLF
как IFS. Опять же, общая проблема в циклах заключается в том, что токенизация происходит по умолчанию, так что заключение в кавычки является специальным решением, позволяющим использовать расширенные строки, содержащие токены. (Это либо расширение, либо расширение параметров, либо заключенные в кавычки расширенные строки, но не оба.) Если для чтения переменной var требуется 8 кавычек, shell - неправильный язык.Для индексированных массивов вы можете ссылаться на них так:
На ассоциативные массивы можно ссылаться аналогичным образом, но вместо
-A
включения нужно включить .declare
-a
источник
Дополнительный метод, который не зависит от того, какая у вас версия оболочки / bash, - это использование
envsubst
. Например:источник
script.sh
файл:Тест:
Согласно
help eval
:Вы также можете использовать
${!var}
косвенное расширение Bash , как уже упоминалось, однако оно не поддерживает получение индексов массивов.Для дальнейшего чтения или примеров, проверьте BashFAQ / 006 о косвенности .
Тем не менее, вы должны пересмотреть использование косвенного обращения согласно следующим примечаниям.
источник
для
varname=$prefix_suffix
формата просто используйте:источник