I'm studying shell scripting with bash and I need to know the difference between (...)
and {...}
. How does one select between the two when writing a script?
-
1See wiki.bash-hackers.org– 0x2b3bfa0Apr 7, 2015 at 14:34
-
3You meant in the context of command grouping only?– heemaylApr 7, 2015 at 14:35
5 Answers
If you want the side-effects of the command list to affect your current shell, use {...}
If you want to discard any side-effects, use (...)
For example, I might use a subshell if I:
- want to alter
$IFS
for a few commands, but I don't want to alter$IFS
globally for the current shell cd
somewhere, but I don't want to change the$PWD
for the current shell
It's worthwhile to note that parentheses can be used in a function definition:
normal usage: braces: function body executes in current shell; side-effects remain after function completes
$ count_tmp() { cd /tmp; files=(*); echo "${#files[@]}"; } $ pwd; count_tmp; pwd /home/jackman 11 /tmp $ echo "${#files[@]}" 11
unusual usage: parentheses: function body executes in a subshell; side-effects disappear when subshell exits
$ cd ; unset files $ count_tmp() (cd /tmp; files=(*); echo "${#files[@]}") $ pwd; count_tmp; pwd /home/jackman 11 /home/jackman $ echo "${#files[@]}" 0
-
16After many years of shell development I didn't know you could use parentheses to run functions in subshells. What a great idea to avoid polluting the global namespace!– l0b0Apr 8, 2015 at 7:33
-
10Using the
local
keyword goes a long way to cleaning up that pollution. Apr 8, 2015 at 10:54 -
2Yeah, but you have to remember to declare every variable local, and it clutters the code.– l0b0Apr 8, 2015 at 15:00
-
6Hint: If you want side-effect-free functions but avoid the unusual function declaration syntax (which code editors may not be aware of) then just use parentheses on the function call rather than the declaration:
pwd; (count_tmp); pwd;
– JuveJul 8, 2015 at 15:11 -
2to the shell... foo() (:;) is equivalent to foo() { (:;); } That is how it reports it if you ask!– anthonyNov 15, 2016 at 6:01
From the official bash documentation:
()
( list )
Placing a list of commands between parentheses causes a subshell environment to be created, and each of the commands in list to be executed in that subshell. Since the list is executed in a subshell, variable assignments do not remain in effect after the subshell completes.
{}
{ list; }
Placing a list of commands between curly braces causes the list to be executed in the current shell context. No subshell is created. The semicolon (or newline) following list is required.
Code in '{}' is executed in the current thread/process/environment and changes are preserved, to put it more succinctly, the code is run in the current scope.
Code in '()' is run inside a separate, child process of bash that is discarded after execution. This child process is often referred to as a
sub-shell and can be thought of as a new, child-like scope.
As an example consider the following...
~ # { test_var=test ; }
~ # echo $test_var
test
~ # ( test_var2=test2 )
~ # echo $test_var2
~ #
Notice in the first example with '{}' the variable is still set even after the closing '}', whereas in the example with '()' the variable is not set outside the scope of the '()'.
Also note the '{}' and '()' syntax difference. The ";" delimiter is always required for code in '{}', but not for code in '()'.
(...)
are used to run code in a sub-shell. Code used bewteen {...}
won't be used in a sub-shell.
In addition to the other answers here, I'd like to expand on the main trade-offs between ()
and {}
:
- speed
- code readability
- thread pollution
Specific pros and cons with examples
The following 3 functions do almost exactly the same thing: they define a variable, and then clear it:
funct1(){ local myvar; myvar="boo"; }
- Pro: very fast
- Pro: does not overwrite global variables with the same name
- Con: bug risk: other changes in this function (like
cd mydirectory/
) remain after function has finished - Con: readability: requires variables be initiated with
local myvar myvar2 ...
- Con: bug risk: forgetting to initiate with
local
can lead to overwriting global variables
funct2(){ myvar="boo"; unset myvar; }
- Pro: very fast (about the same as funct1, possibly negligibly faster)
- Con: bug risk: always overwrites global variables using same name
- Con: bug risk: other changes in this function (like
cd mydirectory/
remain after function has finished) - Con: readability: requires variables be unset with
unset myvar myvar2 ...
- Con: bug risk: forgetting to unset the variables can lead to global variables with unintended values
funct3()(myvar="boo")
- Con: takes ~40 to 45 times as long to execute as funct1 or funct2
- Pro: does not overwrite global variables with the same name
- Pro: other changes in this function (like
cd mydirectory/
are cleared after function has finished) - Pro: no explicit variable cleanup commands required
- Pro: simple and less prone to bugs
Benchmark
You can use this script to compare those 3 functions. Each test runs each function 1000 times, and the test is run 5 times. The average time for each function is output afterward.
#!/bin/bash
funct1(){ local myvar; myvar="boo"; }
funct2(){ myvar="boo"; unset myvar; }
funct3()(myvar="boo")
typeset -A tests
tests=(
[funct1]=0
[funct2]=0
[funct3]=0
)
for n in {0..5}; do
for f in funct1 funct2 funct3; do
start=$(date +%s.%N)
for i in {1..1000}; do $f; done
dur=$(echo "$(date +%s.%N) - $start" | bc)
tests[$f]="$(echo "$(printf "%.6f" "$dur") + ${tests[$f]}"|bc)"
done
done
for i in $(printf "%s\n" "${!tests[@]}"|sort); do
echo "$i average: $(printf "%.6f" "$(echo "${tests[$i]} / 5"|bc -l)")"
done
exit
Example output from running that script:
funct1 average: 0.014117
funct2 average: 0.012473
funct3 average: 0.558457