Действительно ли 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 "