95

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?

A.B.
  • 90,397
Fat Mind
  • 2,445
  • 4
  • 25
  • 41

5 Answers5

113

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
    

Documentation

A.B.
  • 90,397
glenn jackman
  • 17,900
  • 17
    After 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! – l0b0 Apr 08 '15 at 07:33
  • 10
    Using the local keyword goes a long way to cleaning up that pollution. – glenn jackman Apr 08 '15 at 10:54
  • 2
    Yeah, but you have to remember to declare every variable local, and it clutters the code. – l0b0 Apr 08 '15 at 15:00
  • 6
    Hint: 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; – Juve Jul 08 '15 at 15:11
  • 2
    to the shell... foo() (:;) is equivalent to foo() { (:;); } That is how it reports it if you ask! – anthony Nov 15 '16 at 06:01
  • It's also worth noting that '{}' imposes different syntactic requirements, e.g. (true) works, but with braces you need { true; } – Britton Kerin Feb 20 '24 at 19:19
35

From the official bash documentation:

()

( list )
<p>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.</p>

{}

{ list; }
<p>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.</p>
Digital Trauma
  • 2,445
  • 15
  • 24
11

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 '()'.

FNE
  • 103
  • 2
6

(...) are used to run code in a sub-shell. Code used bewteen {...} won't be used in a sub-shell.

A.B.
  • 90,397
Antoine Orsoni
  • 415
  • 1
  • 4
  • 11
3

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
Lectrode
  • 341