234

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
  • 28,846
  • 11
  • 78
  • 104
toop
  • 9,524
  • 22
  • 63
  • 84
  • 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

8 Answers8

391

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
  • 28,342
  • 21
  • 95
  • 123
anubhava
  • 664,788
  • 59
  • 469
  • 547
  • 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
  • 2
    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
236

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
  • 24,484
  • 9
  • 56
  • 82
  • 2
    This answer also works in posix `sh` shell. For portability, you may want to use this answer. – dinkelk Oct 03 '16 at 21:35
  • 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
30

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
  • 772,407
  • 210
  • 1,477
  • 1,841
12

This is how I always do maths in Bash:

count=$(echo "$FIRSTV - $SECONDV"|bc)
echo $count
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Pureferret
  • 6,477
  • 14
  • 72
  • 149
  • 5
    that's only necessary if you're dealing with floating point numbers. – glenn jackman Dec 06 '11 at 00:34
  • 2
    I realise that, but I'd rather make a habit of catching those cases with a `|bc` type command than miss it once or twice. Different strokes for different folks as they say. – Pureferret Dec 06 '11 at 10:27
12

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
  • 88,860
  • 11
  • 107
  • 169
6

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
  • 767
  • 1
  • 8
  • 15
Shawn Chin
  • 74,316
  • 17
  • 152
  • 184
  • @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
  • 21,499
  • 16
  • 88
  • 101
another.anon.coward
  • 10,167
  • 1
  • 28
  • 35
0

Use Python:

#!/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
  • 3,146
  • 2
  • 28
  • 26