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

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

foo()
{
    ...
}

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

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

foo()
(
    ...
)

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

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

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

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

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

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

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

EDIT: Take-aways из ответов

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

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

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

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

3 ответов


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

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

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

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

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

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

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

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

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

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

foo() {
   (
       subshell commands;
   )
}

Это действительно важно. Поскольку функции 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's 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's 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

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

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

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

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

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

myfun() {
    local x=123
}

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

мелочи: в качестве примечания, считают, что баш на самом деле всегда рассматривает функцию как составную команду фигурной скобки. В нем просто иногда есть круглые скобки:

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