1

I wrote the following code:

case "$2"  in  '+') ((res = $1 + $2 ));;

               '-') ((res = $1 - $2 ));;
esac

echo $res

Usage example: ./"*filename*" 2 + 3

If I use double parentheses,

((res = $1 + $2 ))

then the result gets printed. However, if I use single parentheses

(res = $1 + $2 )

then nothing gets printed. What is the difference between ( ) and (( ))?

jub0bs
  • 60,866
  • 25
  • 183
  • 186
amit
  • 29
  • 2
  • Which shell are you using? – melpomene Sep 26 '15 at 09:33
  • `((...))` is for [arithmetic expansion](http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_04), whereas `(...)` runs the enclosed command in a subshell, which does not affect its parent shell. – jub0bs Sep 26 '15 at 09:37
  • Also, note that, in your example the second parameter (`$2`) is the operator, not the second operand. Accordingly, you probably want `((res = $1 + $3))` and `((res = $1 - $3))`, here. – jub0bs Sep 26 '15 at 09:43
  • @Jubobs Those comments could be posted as an answer. – melpomene Sep 26 '15 at 09:47
  • 2
    What about reading the shell manual? It explains `( ... )` and `(( ... ))`. On Linux that would probably be `man bash`. – Jens Sep 26 '15 at 10:03

1 Answers1

2

The double parentheses (( ... )) are for arithmetic expansion, whereas the single parentheses ( ... ) run the enclosed command in a subshell, which has its own scope and doesn't affect the environment of its parent shell.

Here, with (res = $1 + $2), even if variable res is successfully assigned a value in the subshell, res remains unset in the parent shell, which explains why nothing gets printed. You want to use (( ... )), in this particular case.

Besides, note that, in your example, the second parameter, $2, is the operator, not the second operand. Accordingly, you need to use ((res = $1 + $3)) and ((res = $1 - $3)), instead.

Additionally, for robustness, you may want to make sure that

  • the number of arguments is valid,
  • the first and third arguments are valid integers,
  • the second parameter is a valid arithmetic operator (+ or -, here),
  • etc.

Finally, for improved portability across different shells, prefer printf over echo.

Corrected and improved code

#!/bin/sh

# foo

# Custom error function
die() {
  printf "%s\n" "$1" 1>&2 ;
  exit 1;
}

# Function that returns an exit status of
#   0 if its first argument is a valid integer,
#   1 otherwise.
is_integer() {
  [ "$1" -eq 0 -o "$1" -ne 0 ] >/dev/null 2>&1
}

# Check that the number of argument is 3
[ "$#" -eq 3 ] || die "invalid number of arguments"

# Check that the operands are valid integers
is_integer "$1" || die "invalid first operand"
is_integer "$3" || die "invalid second operand"

# If the second argument is a valid operator, assign res accordingly;
# otherwise, die.
case "$2" in
  '+')
    ((res = $1 + $3))
    ;;
  '-')
    ((res = $1 - $3))
    ;;
  *)
    die "invalid operator"
    ;;
esac

printf "%s\n" "$res"

Tests

After making the script (called "foo") executable, by running

chmod u+x foo

I get the following results

$ ./foo -18 + 5
-13
$ ./foo 9 - 4
5
$ ./foo a + 2
invalid first operand
$ ./foo -18 + c
invalid second operand
$ ./foo 3 / 4
invalid operator
jub0bs
  • 60,866
  • 25
  • 183
  • 186