265

I have the script below to subtract the counts of files between two directories but the COUNT= expression does not work. What is the correct syntax?

#!/usr/bin/env bash

FIRSTV=`ls -1 | wc -l`
cd ..
SECONDV=`ls -1 | wc -l`
COUNT=expr $FIRSTV-$SECONDV  ## -> gives 'command not found' error
echo $COUNT
codeforester
  • 39,467
  • 16
  • 112
  • 140
toop
  • 10,834
  • 24
  • 66
  • 87
  • 1
    possible duplicate of [How can I add numbers in a bash script](http://stackoverflow.com/questions/6348902/how-can-i-add-numbers-in-a-bash-script) – Sorin Sep 08 '14 at 20:53

9 Answers9

444

Try this Bash syntax instead of trying to use an external program expr:

count=$((FIRSTV-SECONDV))

BTW, the correct syntax of using expr is:

count=$(expr $FIRSTV - $SECONDV)

But keep in mind using expr is going to be slower than the internal Bash syntax I provided above.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
anubhava
  • 761,203
  • 64
  • 569
  • 643
  • 4
    This form is magnitudes quicker than using the expr external program. – nsg Jul 28 '13 at 02:37
  • This works without the backticks, but may I know why? +1 for the answe.r – Amal Murali Feb 15 '14 at 10:50
  • 3
    Thanks. Backtick is old shell syntax. BASH supports new `$(command)` syntax for command substitution. Also since BASH support arithmetic operations in `$(( ... ))` it is better to not to use an external utility `expr` – anubhava Feb 15 '14 at 10:53
  • 1
    I never new you could reference variables without the "$", very interesting. This works on Ubuntu 12,14 just FYI. – MadHatter Jun 20 '16 at 14:05
  • @anubhava - Can you please explain the dollar with the double parenthesis notion? Thanks. – AlikElzin-kilaka Feb 23 '17 at 06:50
  • 1
    @AlikElzin-kilaka: In bash `$(( ... ))` is use for evaluating arithmetic expressions. – anubhava Feb 23 '17 at 10:26
251

You just need a little extra whitespace around the minus sign, and backticks:

COUNT=`expr $FIRSTV - $SECONDV`

Be aware of the exit status:

The exit status is 0 if EXPRESSION is neither null nor 0, 1 if EXPRESSION is null or 0.

Keep this in mind when using the expression in a bash script in combination with set -e which will exit immediately if a command exits with a non-zero status.

Community
  • 1
  • 1
Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88
  • 3
    This answer also works in posix `sh` shell. For portability, you may want to use this answer. – dinkelk Oct 03 '16 at 21:35
  • 1
    It's worth noting that according to Shellcheck, expr is a codesmell due to it being antiquated and difficult to use: https://github.com/koalaman/shellcheck/wiki/SC2003 – John Hamelink Feb 05 '20 at 11:19
  • I concur; Anubhava's answer should be the accepted one. `expr` only makes sense if you need compatibility back to the dark ages when Solaris `sh` was popular and HP-UX and AIX were still a thing. POSIX `sh` too has supported arithmetic expressions for many years now. – tripleee Oct 23 '22 at 10:28
31

You can use:

((count = FIRSTV - SECONDV))

to avoid invoking a separate process, as per the following transcript:

pax:~$ FIRSTV=7
pax:~$ SECONDV=2
pax:~$ ((count = FIRSTV - SECONDV))
pax:~$ echo $count
5
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
17

This is how I always do maths in Bash:

count=$(echo "$FIRSTV - $SECONDV"|bc)
echo $count
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
AncientSwordRage
  • 7,086
  • 19
  • 90
  • 173
13

White space is important, expr expects its operands and operators as separate arguments. You also have to capture the output. Like this:

COUNT=$(expr $FIRSTV - $SECONDV)

but it's more common to use the builtin arithmetic expansion:

COUNT=$((FIRSTV - SECONDV))
Karoly Horvath
  • 94,607
  • 11
  • 117
  • 176
8

For simple integer arithmetic, you can also use the builtin let command.

 ONE=1
 TWO=2
 let "THREE = $ONE + $TWO"
 echo $THREE
    3

For more info on let, look here.

Elder Geek
  • 1,425
  • 2
  • 14
  • 18
Shawn Chin
  • 84,080
  • 19
  • 162
  • 191
  • @another.anon.coward Your link's better than mine +1. (... and stealing link) – Shawn Chin Dec 05 '11 at 14:28
  • Had a lot of trouble in getting this working. Finally this worked - `let "sanity_check_duration=sanity_check_duration_end_time_delay_sec - sanity_check_duration_start_time_delay_sec"` (removing dollar sign from variables) – Sandeepan Nath Feb 28 '17 at 14:56
2

Alternatively to the suggested 3 methods you can try let which carries out arithmetic operations on variables as follows:

let COUNT=$FIRSTV-$SECONDV

or

let COUNT=FIRSTV-SECONDV

Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121
another.anon.coward
  • 11,087
  • 1
  • 32
  • 38
0

Diff Real Positive Numbers

diff_real () {
  echo "df=($1 - $2); if (df < 0) { df=df* -1}; print df" | bc -l;
}

Usage

var_a=10
var_b=4

output=$(diff_real $var_a $var_b)
# 6

#########


var_a=4
var_b=10

output=$(diff_real $var_a $var_b)
# 6
metaory
  • 91
  • 1
  • 3
-2

Use BASH:

#!/bin/bash
# home/victoria/test.sh

START=$(date +"%s")                                     ## seconds since Epoch
for i in $(seq 1 10)
do
  sleep 1.5
  END=$(date +"%s")                                     ## integer
  TIME=$((END - START))                                 ## integer
  AVG_TIME=$(python -c "print(float($TIME/$i))")        ## int to float
  printf 'i: %i | elapsed time: %0.1f sec | avg. time: %0.3f\n' $i $TIME $AVG_TIME
  ((i++))                                               ## increment $i
done

Output

$ ./test.sh 
i: 1 | elapsed time: 1.0 sec | avg. time: 1.000
i: 2 | elapsed time: 3.0 sec | avg. time: 1.500
i: 3 | elapsed time: 5.0 sec | avg. time: 1.667
i: 4 | elapsed time: 6.0 sec | avg. time: 1.500
i: 5 | elapsed time: 8.0 sec | avg. time: 1.600
i: 6 | elapsed time: 9.0 sec | avg. time: 1.500
i: 7 | elapsed time: 11.0 sec | avg. time: 1.571
i: 8 | elapsed time: 12.0 sec | avg. time: 1.500
i: 9 | elapsed time: 14.0 sec | avg. time: 1.556
i: 10 | elapsed time: 15.0 sec | avg. time: 1.500
$
Victoria Stuart
  • 4,610
  • 2
  • 44
  • 37