Действительно ли bash делает "$ @" неправильно внутри $ {var+...}?
В настоящее время я получаю 2 мнения от 3 снарядов:
$ bash -c 'set bar; set foo${1+ "[email protected]"}; echo "$# $*"'
1 foobar
$ ash -c 'set bar; set foo${1+ "[email protected]"}; echo "$# $*"'
2 foo bar
$ dash -c 'set bar; set foo${1+ "[email protected]"}; echo "$# $*"'
2 foo bar
Или я упустил некоторые определения POSIX, которые показывают мой пример как поведение, определяемое реализацией?
Обратите внимание, что только "$ @", похоже, вызывает различия. Следующее работает одинаково для всех 3 оболочек:
$ bash -c 'set bar; set foo${1+ $*}; echo "$# $*"'
2 foo bar
$ ash -c 'set bar; set foo${1+ $*}; echo "$# $*"'
2 foo bar
$ dash -c 'set bar; set foo${1+ $*}; echo "$# $*"'
2 foo bar
К сожалению, $ * не совсем то же самое, что "$ @", если аргументы должны содержать пробелы.
BTW, я использую Bash версии 4.4.12 (1) -release.
Ответы
Ответ 1
Я склонен не доверять пробелам внутри расширения без кавычек. Я не могу объяснить, почему это не работает, но я могу дать вам решение, которое делает: переместить пространство за пределы расширения параметра. Команда set
не возражает против пробела.
$ bash -c 'set bar; set foo ${1+"[email protected]"}; echo "$# $*"' # 5.0.2(1)
2 foo bar
$ dash -c 'set bar; set foo ${1+"[email protected]"}; echo "$# $*"' # 0.5.10.2
2 foo bar
$ ash -c 'set bar; set foo ${1+"[email protected]"}; echo "$# $*"' # /bin/sh on FreeBSD 7.3
2 foo bar
$ ksh -c 'set bar; set foo ${1+"[email protected]"}; echo "$# $*"' # 93u+ 2012-08-01
2 foo bar
$ zsh -c 'set bar; set foo ${1+"[email protected]"}; echo "$# $*"' # 5.7
2 foo bar
(Я запустил все на последнем тестировании Debian, кроме ash
)
Ответ 2
Хотя это кажется ошибкой, я сталкивался с некоторыми странными крайними случаями с "... $ @" и раньше, и это соответствует ожидаемому поведению, по крайней мере, исторически. " [email protected]"
(с висящими пробелами) - неопределенное поведение, потому что вы в основном запрашиваете, чтобы все члены массива [email protected]
рассматривались как аргументы для синтаксического анализатора оболочки плюс один символ пробела. Может быть немного сложно обернуть голову, и некоторые из вещей, которые вы сказали, вводят в заблуждение, поэтому я решил, что предоставлю больше информации и совет для достижения лучшего поведения.
То, как вы используете "$ @" и пытаетесь заменить его на "$ *", может вызвать проблемы. Между поведением $*
и [email protected]
существует существенная разница, которая отражает их предполагаемое использование. Если вы используете [email protected]
, это потому, что вы хотите сделать что-то вроде этого:
wrapper_fxn() {
wrapped_cmd "[email protected]"
}
Когда вы заключаете $ @в кавычки (и любой другой массив в этом отношении), bash оценивает его так, как если бы каждый аргумент/член массива был сначала заключен в двойные кавычки. Тем не менее, здесь задействовано немного больше магии, так как он на самом деле не заключает в двойные кавычки, но удаляет двойные кавычки, но указывает на изменение поведения при разборе на более позднем этапе.
Причина, по которой существует специальное поведение "[email protected]"
состоит в том, чтобы помочь справиться с неприятными проблемами quoting
и вмешательства в argv
.
Итак, подумайте о них так:
- $ @: несколько аргументов/для распространения аргументов при сохранении цитирования пробелов
- $ *: один аргумент/для объединения всех строк в массиве в один аргумент
Мой совет: не делайте "[email protected]"
; это неопределенное поведение. Как только вы цитируете как "[email protected]"
, вы пытаетесь выполнить расширение аргумента и добавить еще один аргумент или массив аргументов, просто сделайте это так:
# Making sure to have quotes around each new arg
somecmd "$newFirstArg" "[email protected]" "${additionalArray[@]}" "$newLastArg"
... это лучше сообщит о намерениях и почти никогда не приведет к неожиданному поведению.
Больше в глубине
С учетом всего вышесказанного, странное поведение края "$ @" имеет отношение к тому, что вы используете его как: "... [email protected]"
, как и "$ @". Bash объединит один из элементов массива в зависимости от позиции ...
с дополнительными символами.
Когда вы добавляете что-либо справа или слева от [email protected]
в выражении, указанном в кавычках, например, "extra stuff [email protected]"
или "[email protected] stuff"
, то, что сделает bash, это просто добавит extra stuff
в первый или последний член массив соответственно. Исключением является случай, когда в операторе с двойными кавычками есть другой массив, возможно, [email protected]
дублируется так: "[email protected][email protected]"
. Фактически он объединит последний элемент первого массива с первым элементом последнего массива. Таким образом, если у [email protected]
три члена, последний argv[]
для "[email protected][email protected]"
будет иметь 5 аргументов, а не 6.
Вот полезная функция, помогающая совать поведение:
# example function
print-args ()
{
local i=0;
for arg in "[email protected]";
do
printf "[${i}]:%s\n" "\"$arg\"";
let i++;
done
}
# good example arg-set covering many of the important edge cases
set "one \"one" two three
# One case, modify it to see the various permutations of the edge cases
print-args " [email protected] [email protected] "
#>[0]:" one "one"
#>[1]:"two"
#>[2]:"three one "one"
#>[3]:"two"
#>[4]:"three "