Summary: Either quote your expressions (preferably with single quotes) or use something other than (
and )
for grouping.
For an answer to "why does 0.5*(1+2)
work?", go to the end. (Hint: it's because you don't have a file named 0.5
.)
Parentheses are what the bash manual refers to as metacharacters. (Posix no longer uses this term; instead, it refers to such characters as "operators". But the basic effect is the same.) Unless quoted, metacharacters are always tokens by themselves (or, in cases like <<
and &&
, along with the rest of the operator they start), and they have syntactic significance.
This is different from braces ({
and }
) which are reserved words, not metacharacters, and so do not delimit tokens. As reserved words, they only have special significance when they are tokens by themselves and are the first token in a command:
{echo x # The command to be executed is `{echo`, which probably doesn't exist
echo {x # No problem. Prints the string '{x'
echo { } # Also no problem. Prints '{ }'
{ echo x; } # A compound command. The ; is necessary.
(echo x) # Also a compound command but ; and whitespace are optional
[
and ]
are somewhat similar. [
is a command (not even a reserved word), while [[
is a reserved word which starts a conditional compound command but only if it is the first word in a command.
So you could use brackets or braces as grouping operators without worrying about quoting, because the arguments to your function are never going to be the first words in the command.
As a side-note, the difference between [
(a command) and [[
(a reserved word) is shown by the fact that only the first one can be preceded by a variable assignment (in this case, the assignment has no useful effect):
$ foo=3 [ -z "$foo" ] && echo yes
yes
$ foo=3 [[ -z "$foo" ]] && echo yes
[[: command not found
$ [[ -z "$foo" ]] && echo yes
yes
The precise syntactic significance of parentheses depends, as usual, on the syntax in which they appear. In the case of (
, this might be:
A function definition:
func () { echo "$@"; }
A compound command executed in a subshell
(sleep 1; echo "Hello..."; sleep 5; echo "World!")&
Surrounding the pattern in a case clause:
case "$word"; in
(Hello) echo "Hi" ;;
Bye) echo "Seeya" ;; # The open parenthesis is optional in this syntax
esac
In bash, it may also be used as part of array assignment:
local numbers=(one two three)
and it can form part of the ((
operator, used in arithmetic conditional compound commands and arithmetic for
statements.
Parentheses might also appear as part of a longer construct not starting with a parenthesis, such as command substitution: $(
. But if a parenthesis is recognised as a token and it doesn't fit any of the syntactic constructs which include parentheses, a syntax error will be signalled:
$ echo a b(c)
bash: syntax error near unexpected token `('
That leaves us with a small mystery: how do we explain the following:
$ echo a+(b+4)
a+(b+4)
$ echo a-(b+4)
bash: syntax error near unexpected token `('
$ echo a*(b+4)
a*(b+4)
$ echo a/(b+4)
bash: syntax error near unexpected token `('
The answer is that I have shopt -s extglob
in my bash start-up files. And you probably do, too, because many distributions do that for you by default. If "extended glob" patterns are available, then the following are patterns:
?(pattern-list)
Matches zero or one occurrence of the given patterns
*(pattern-list)
Matches zero or more occurrences of the given patterns
+(pattern-list)
Matches one or more occurrences of the given patterns
@(pattern-list)
Matches one of the given patterns
!(pattern-list)
Matches anything except one of the given patterns
A pattern-list can contain only a single pattern, so b+4
is a valid pattern, and a+(b+4)
will therefore match a file whose name starts with an a
and is followed by one or more instances of the characters b+4
:
$ touch ab+4b+4b+4
$ echo a+(b+4)
ab+4b+4b+4
Like any other filename pattern, if no filename is matched, the pattern is not substituted:
$ rm ab+4b+4b+4
$ echo a+(b+4)
a+(b+4)
Unless you have other shell options set:
$ shopt -s failglob
$ echo a+(b+4)
bash: no match: a+(b+4)