Почему расширение фигурных скобок bash работает в некоторых арифметических выражениях, но не в других?

#bash #brace-expansion

#bash #расширение фигурных скобок

Вопрос:

Я работаю над очень простым сценарием bash, и у меня возникла проблема с недооценкой того, почему устаревший $[] работает безупречно, в то время как $ (()), похоже, нарушает все это.

Код, на который я ссылаюсь, это:

 for i in {1..10};
do 
    printf M $[{1..10}*i]
    echo
done
 

В этой версии у меня нет проблем, но я не хотел бы использовать устаревшие элементы bash, поэтому я хотел переключиться на $ (()) .

К сожалению, как только я меняю свой код на:

 printf M $(({1..10}*i))
 

Я получаю сообщение об ошибке:

 ./script_bash.sh: line 8: {1..10}*i: syntax error: argument expected (error token is "{1..10}*i")
 

Я был бы благодарен за некоторую помощь с этим…

Комментарии:

1. $(()) является арифметическим выражением. Там не выполняется обычное расширение подстановочных знаков или фигурных скобок.

Ответ №1:

Настройка машины возврата к 1990 году.

В Bash реализован $[] синтаксис для POSIX P1003.2d9 (около 1990 года), который был черновиком выпущенного P1003.2-1992. За два года между черновиком и стандартом POSIX вместо этого остановился на $(()) синтаксисе и поведении ksh88. Чет Рами (сопровождающий bash) сказал это еще в 2012 году:

Bash … реализовал $[…], потому что в то время не было другого синтаксиса, и чтобы получить некоторый опыт работы с арифметическим расширением в оболочке. Bash-1.14… перечислены обе формы арифметического расширения, но к моменту выпуска bash-2.0 в 1995 году в руководстве упоминалась только форма $((…)) .

Это наводит меня на мысль, что $[] форма была экспериментальной, и у нее было определенное поведение (например, расширение фигурных скобок), которое было указано в забвении, когда POSIX принял $(()) синтаксис. Эти экспериментальные модели поведения были оставлены, поскольку в дикой природе уже были сценарии, полагающиеся на них (помните, прошло более 2 лет).

В том же потоке Чет дает понять, что $[] форма устарела, но не устарела:

Теперь вряд ли стоит продолжать перетаскивать синтаксис $[…]. Это занимает всего несколько десятков байт кода. Я не планирую его удалять.

В текущем стандарте POSIX, C.2.6 Расширения слов> Арифметическое расширение, упоминается синтаксис (выделение мое):

В ранних предложениях использовалась форма $[expression] . Это было функционально эквивалентно «$ (())» текущего текста, но были выдвинуты возражения, что в KornShell 1988 года уже реализован «$ (())», и не было веских причин изобретать еще один синтаксис. Кроме того, синтаксис «$ []» имел незначительную несовместимость с шаблонами в операторах case.

Таким образом, реализованное поведение в bash не совсем соответствует спецификации, но, поскольку удалять его не планируется, я не вижу причин отказываться от его преимуществ, если оно аккуратно решает вашу проблему. Однако, как указано в комментарии @Barmar’s, было бы неплохо прокомментировать код и связать его здесь, чтобы будущие разработчики знали, что, черт возьми, вы имеете в виду!

Комментарии:

1. Хотя оно явно не является устаревшим (существует ли на самом деле список устаревших функций?), Тот факт, что оно даже не упоминается в руководстве, является убедительным признаком того, что его следует избегать. Даже если это никогда не исчезнет, другие программисты этого не поймут. Я программирую сценарии оболочки в течение 3 десятилетий, и я никогда не видел этого раньше, я уверен, что я не одинок. Сравните с другими функциями, которые есть на странице bash-hackers, на которую вы ссылались, они все еще есть в руководстве.

2. @Barmar Это упоминается в текущей спецификации POSIX, и, предположительно , некоторые дистрибутивы исправляют справочную страницу bash, чтобы упомянуть об этом. Тем не менее, я подробно остановился на моей «рекомендации» по использованию.

Ответ №2:

$(()) предназначено для арифметических выражений, а расширение фигурных скобок не выполняется в арифметике.

Создайте массив с помощью цикла:

 for i in {1..10}
do
    vals=()
    for j in {1..10}
    do
        vals =($((i*j)))
    done
    printf "M" ${vals[@]}
done
 

Комментарии:

1. Но, с другой стороны, почему расширение фигурных скобок выполняется в $[] ?

2. Это первое, о чем я когда-либо слышал $[] . Я не могу найти его в руководстве по bash.

3. Это устаревший псевдо-эквивалент $(( , см. wiki.bash-hackers.org/scripting/obsolete

4. Итак, я предполагаю, что технически ваше первое предложение неверно, поскольку якобы $[] оно также предназначено для «арифметических выражений».

5. Но я думаю, что в то время, когда они его создавали, они не рассматривали арифметическое расширение как совершенно другой контекст со своим собственным синтаксисом.

Ответ №3:

printf M $(({1..10}*i))

не работает из-за порядка, в котором расширяются параметры bash . Расширение фигурных скобок ( {} ) выполняется раньше, чем арифметическое расширение ( $(()) ) by bash . Ваш код определенно работал бы, если бы все было наоборот.

От man bash :

Порядок расширений таков: расширение фигурных скобок; расширение тильды, расширение параметров и переменных, арифметическое расширение и подстановка команд (выполняется слева направо); разделение слов; и расширение имени пути.

Ответ №4:

Наткнулся на такой bash. Этот вопрос требует объяснения, а не решения, но здесь был бы $ (()) -способ выразить это.

 for i in {1..10}; do
  printf M $(eval echo '$(('{1..10}'*i))')
  echo
done
 

Расширение фигурных скобок запрещено внутри арифметического расширения, как и для расширения параметров.

(руководство по bash)

Чтобы избежать конфликтов с расширением параметров, строка ‘${’ не считается подходящей для расширения фигурных скобок и запрещает расширение фигурных скобок до закрывающего ‘}’.