[sf-lug] curly braces {} in bash/shell: Re: Meeting notes 2.0

Michael Paoli Michael.Paoli at cal.berkeley.edu
Wed Feb 8 05:21:54 PST 2023


On 2023-02-07 08:15, aaronco36 wrote:
> Got onto the virtual meeting late; maybe a bit before 12Noon(?)
> IIRC, Victor was asking questions about ternary operators and the use
> of curly brackets {} in bash scripts.

So, curly braces {} in POSIX (and bash) shell.
Most notably they're used for two different things,
though in both cases, one can think of them as a form of grouping.

So, first, logical grouping of commands.
Ye olde manpage has:
{ list } list is simply executed.
The following words are only recognized as the first word of a command
and when not quoted.
if then else elif fi case in esac for while until do done { }
So, great conciseness and brevity (handy and generally preferred 
especially
for ye olde hardcopy terminal).  They essentially group
commands.  But note that unlike (), {} doesn't give you a subshell.
Also, unlike () and, e.g. |, the curly braces aren't special in and of
themselves to the shell by merely being in an unquoted context.
So, e.g.:
$ (echo)
runs the echo command in a subshell, whereas:
$ {echo}
attempts to run the command that's literally {echo} and will typically
result in something like:
sh: 1: {echo}: not found
One would instead typically want to do something like:
{ echo; }
That allows the shell to see each of those curly braces as the first
word of a command, and thus executes the list between - echo in our
case.
Of course then the next logical question would be gee, of what use is
that?  It's highly useful when our list is more non-trivial, e.g. when
our list is comprised of more than one command:
: && { echo yes; echo true; } ||
   { echo no; echo false; }
In the above, : command returns 0/true, then we have && - think of it as
logical and - we need to continue evaluating to determine our result,
next we encounter { as first word of command, so it executes the list
within {} up to the corresponding closing }, so it echos yes, and true.
Our echo true should return true/0, so then we next have || which works
as a logical or - we're already true, so no need to execute the part
after || to determine if true, so we stop there.
Though it's not the case, had : returned non-zero, that would be
considered false, the part after && and within the first {} wouldn't be
executed, at that point we've got non-zero/false, so after the ||, it
would then execute the part within the following {} pair.
If, however, we had:
: && echo yes; echo true ||
   echo no; echo false
That's something totally different.  The : gives us 0/true, so we
execute echo yes.  Then we echo true, which should return 0/true, so
then after || we don't execute the echo no.  But then we do execute the
echo false, as that's then just an independent list/command, no
conditional or grouping to prevent it from being executed.  In fact that
formatting is somewhat misleading, and probably ought be written more
like:
: && echo yes
echo true || echo no
echo false
Also, once you've got that opening { the shell will read to find the
closing } expecting to find/parse it where/as command - if it never
finds that, it will complain about syntax.  So, one could also
write like:
: &&
{
   echo yes
   echo true
} ||
{
   echo no
   echo false
}
In most contexts where one could have the shell interpret a ; to 
separate
lists/commands, generally a newline could also be used, and in either
case, there may also be leading and/or trailing whitespace.

And, then there's use of {} in context of parameter(/variable)
substitution.  Let's say we have:
foo=FOO
bar=BAR
foobar=SNAFU
echo ${foo}bar $foobar
Our echo command above then gives us:
FOObar SNAFU
Essentially, when doing named parameter substitution, the shell keeps
reading characters of the parameter name until it runs out of characters
that are valid for a named parameter, so $foobar substitutes the value
of the named parameter foobar, rather than that of foo followed by the
string bar.  If we want the value of foo followed by the string bar,
we need to do something like ${foo}bar or alternatively something like
$foo\bar or $foo'bar' or $foo"bar", etc.
Similarly, can think of it again as grouping on some other parameter
substitution, e.g.:
set -- 1; echo ${1-one} ${2-two}
Which gives us:
1 two
whereas:
set -- 1; echo $1-one $2-two
gives us:
1-one -two
Basically the shell sees $, then digit - so positional parameter, but
once it runs out of digit(s) that's no longer part of that positional
parameter interpolation - at least where we don't have the use of {},
whereas in the earlier case with {} the -one and -two parts are part of
that interpolation and substitution.

Caveats:
Not covered - (much of) quoting - in many/most of the above cases, "
quoting should be used, notably to have substituted parameter results
interpreted as a single "word", and not be subject to word splitting.
E.g.:
some_command "$foo"
has a single argument, regardless of what foo may be set to,
whereas:
some_command $foo
may have zero or more arguments, depending if foo is set and if so to
what.

See also:
https://www.mpaoli.net/~michael/unix/sh/



More information about the sf-lug mailing list