Подтвердить что ты не робот

Bash функции: включение тела в фигурные скобки против круглых скобок

Обычно функции bash определяются с помощью фигурных скобок, чтобы заключить тело:

foo()
{
    ...
}

При работе с оболочкой script сегодня, когда мы широко используем функции, я столкнулся с проблемами с переменными, которые имеют одно и то же имя в вызываемом как в вызывающей функции, а именно, что эти переменные одинаковы. Я узнал, что это можно предотвратить, указав локальные переменные внутри функции как локальные: local var=xyz.

Затем в какой-то момент я обнаружил поток (Определение тела функции bash с использованием скобок вместо скобок), в котором объясняется, что он просто как действительный для определения функции с помощью круглых скобок, таких как:

foo()
(
    ...
)

Эффект от этого заключается в том, что тело функции выполняется в подоболочке, что имеет преимущество, что функция имеет свой собственный диапазон переменных, что позволяет мне определять их без локального. Поскольку наличие функционально-локальной области видимости имеет гораздо больший смысл и намного безопаснее, чем все глобальные переменные, я сразу же спрашиваю себя:

  • Почему скобки, используемые по умолчанию, заключают тело функции вместо круглых скобок?

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

  • Существуют ли другие основные недостатки (*) для использования скобок вместо скобок (что может объяснить, почему фигурные скобки кажутся предпочтительными)?

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

По-видимому, оба стиля имеют свои преимущества и недостатки. Поэтому я надеюсь, что некоторые из вас более опытные пользователи bash могут дать мне общее руководство:

  • Когда я должен использовать фигурные скобки, чтобы заключить тело функции, и когда целесообразно переключиться на круглые скобки?

РЕДАКТИРОВАТЬ: Принимать ответы из ответов

Спасибо за ваши ответы, теперь моя голова немного понятна. Так что я отнимаю ответы:

  • Придерживайтесь обычных фигурных скобок, хотя бы для того, чтобы не путать потенциальных других пользователей/разработчиков script (и даже использовать фигурные скобки, если все тело завернуто в круглые скобки).

  • Единственным реальным недостатком фигурных скобок является то, что любая переменная в родительской области может быть изменена, хотя в некоторых ситуациях это может быть преимуществом. Это можно легко обойти, объявив переменные как local.

  • С помощью круглых скобок, с другой стороны, могут возникнуть серьезные нежелательные эффекты, такие как испорчивание выходов, приводящие к проблемам с убийством script и выделение области переменных.

4b9b3361

Ответ 1

Почему скобки, используемые по умолчанию, заключают тело функции вместо круглых скобок?

Тело функции может быть любой составной командой. Обычно это { list; }, но технически допустимы три другие формы составных команд: (list), ((expression)) и [[ expression ]].

C и языки в семействе C, такие как С++, Java, С# и JavaScript, все используют фигурные скобки для разграничения тел функций. Кудрявые скобки являются наиболее естественным синтаксисом для программистов, знакомых с этими языками.

Существуют ли другие основные недостатки (*) для использования скобок вместо скобок (что может объяснить, почему скобки предпочтительнее)?

Да. Есть много вещей, которые вы не можете сделать из суб-оболочки, включая:

  • Изменение глобальных переменных. Изменения переменных не распространяются на родительскую оболочку.
  • Выйдите из script. Оператор exit выйдет только из суб-оболочки.

Запуск суб-оболочки также может стать серьезным ударом. Каждый раз, когда вы вызываете функцию, вы запускаете новый процесс.

Вы также можете получить странное поведение, если ваш script будет убит. Сигналы, получаемые родительской и дочерней оболочками, будут изменены. Это тонкий эффект, но если у вас есть обработчики trap или вы kill ваши script те части не работают так, как вы хотите.

Когда я должен использовать фигурные скобки, чтобы заключить тело функции, и когда целесообразно переключаться в круглые скобки?

Я бы посоветовал вам всегда использовать фигурные скобки. Если вы хотите явную под-оболочку, добавьте набор скобок внутри фигурных скобок. Использование только скобок - очень необычный синтаксис и смущает многих людей, читающих ваш script.

foo() {
   (
       subshell commands;
   )
}

Ответ 2

Это действительно важно. Поскольку функции bash не возвращают значения, а используемые переменные из глобальной области (то есть они могут получить доступ к переменным из "вне" своей области), обычным способом обработки вывода функции является сохранение значение в переменной, а затем вызвать его.

Когда вы определяете функцию с (), вы правы: она создаст суб-оболочку. Эта под-оболочка будет содержать те же самые значения, которые были у оригинала, но не сможет их модифицировать. Таким образом, вы теряете этот ресурс изменения глобальных переменных области.

См. пример:

$ cat a.sh
#!/bin/bash

func_braces() { #function with curly braces
echo "in $FUNCNAME. the value of v=$v"
v=4
}

func_parentheses() (
echo "in $FUNCNAME. the value of v=$v"
v=8
)


v=1
echo "v=$v. Let start"
func_braces
echo "Value after func_braces is: v=$v"
func_parentheses
echo "Value after func_parentheses is: v=$v"

Позвольте выполнить его:

$ ./a.sh
v=1. Let start
in func_braces. the value of v=1
Value after func_braces is: v=4
in func_parentheses. the value of v=4
Value after func_parentheses is: v=4   # the value did not change in the main shell

Ответ 3

Я хочу использовать подоболочку, когда хочу менять каталоги, но всегда из одного и того же исходного каталога, и не может быть обеспокоен использованием pushd/popd или самостоятельно управлять каталогами.

for d in */; do
    ( cd "$d" && dosomething )
done

Это будет работать также и из тела функции, но даже если вы определите функцию с фигурными фигурными скобками, все же можно использовать ее из подоболочки.

doit() {
    cd "$1" && dosomething
}
for d in */; do
    ( doit "$d" )
done

Конечно, вы все равно можете сохранить переменную область внутри фигурной скобки определенной функции, используя declare или local:

myfun() {
    local x=123
}

Итак, я бы сказал, явным образом определяю вашу функцию как подоболочку только в том случае, если не являться подоболочкой, наносит ущерб очевидному правильному поведению этой функции.

Общая информация. Как замечание, считайте, что bash на самом деле всегда обрабатывает эту функцию как составную команду curly-brace. В нем иногда есть круглые скобки:

$ f() ( echo hi )
$ type f
f is a function
f () 
{ 
    ( echo hi )
}