65

I have d1="11" and d2="07". I want to convert d1 and d2 to integers and perform d1-d2. How do I do this in UNIX?

d1 - d2 currently returns "11-07" as result for me.

Sohail Si
  • 2,750
  • 2
  • 22
  • 36
qwarentine
  • 1,003
  • 2
  • 9
  • 7

5 Answers5

92

The standard solution:

 expr $d1 - $d2

You can also do:

echo $(( d1 - d2 ))

but beware that this will treat 07 as an octal number! (so 07 is the same as 7, but 010 is different than 10).

mklement0
  • 382,024
  • 64
  • 607
  • 775
William Pursell
  • 204,365
  • 48
  • 270
  • 300
  • 1
    Given that bash has syntax for specifying numeric base, why not demonstrate it here (albeit with a portability caveat)? – Charles Duffy Jun 12 '15 at 01:33
  • 4
    You can base-convert: `echo $((d1-10#$d2))` Beware: it is not the same than `echo $((d1-10#d2))` – Luchostein Mar 01 '16 at 18:42
  • Given that bash is NOT the standard and isn't guaranteed on all targets...how about skipping the bash-isms since they didn't ask how to do it in bash...? – Svartalf Oct 31 '19 at 20:42
  • Be careful if your variables can hold non-integer strings or if you are using options `nounset` and `errexit` (which you should always use). I have written many tests for different scenarios in my answer https://stackoverflow.com/a/59781257/117471 – Bruno Bronosky Jan 17 '20 at 04:41
  • You should definitely be wary of non-integer values, and using `nounset` is often a good idea. But you should *never* use `errexit`. – William Pursell Jan 17 '20 at 17:55
24

Any of these will work from the shell command line. bc is probably your most straight forward solution though.

Using bc:

$ echo "$d1 - $d2" | bc

Using awk:

$ echo $d1 $d2 | awk '{print $1 - $2}'

Using perl:

$ perl -E "say $d1 - $d2"

Using Python:

$ python -c "print $d1 - $d2"

all return

4
Levon
  • 138,105
  • 33
  • 200
  • 191
  • 1
    Not sure if it is a bug or a feature of `expr`, `awk`, `bc`, and `dc`, but all of those tools handle the case `d1=09` by treating 09 as the decimal value 9. However, `perl` and `python` treat it as a syntax error. You get different results for the case `d1=010` – William Pursell Apr 04 '14 at 13:35
  • @WilliamPursell Interesting .. I'll have to look into this, useful info, thanks. – Levon Apr 04 '14 at 13:44
  • 4
    You can do it straight away with pure Bash: `echo $((d1-10#$d2))` – Luchostein Mar 01 '16 at 18:45
10

An answer that is not limited to the OP's case

The title of the question leads people here, so I decided to answer that question for everyone else since the OP's described case was so limited.

TL;DR

I finally settled on writing a function.

  1. If you want 0 in case of non-int:
int(){ printf '%d' ${1:-} 2>/dev/null || :; }
  1. If you want [empty_string] in case of non-int:
int(){ expr 0 + ${1:-} 2>/dev/null||:; }
  1. If you want find the first int or [empty_string]:
int(){ expr ${1:-} : '[^0-9]*\([0-9]*\)' 2>/dev/null||:; }
  1. If you want find the first int or 0:
# This is a combination of numbers 1 and 2
int(){ expr ${1:-} : '[^0-9]*\([0-9]*\)' 2>/dev/null||:; }

If you want to get a non-zero status code on non-int, remove the ||: (aka or true) but leave the ;

Tests

# Wrapped in parens to call a subprocess and not `set` options in the main bash process
# In other words, you can literally copy-paste this code block into your shell to test
( set -eu;
    tests=( 4 "5" "6foo" "bar7" "foo8.9bar" "baz" " " "" )
    test(){ echo; type int; for test in "${tests[@]}"; do echo "got '$(int $test)' from '$test'"; done; echo "got '$(int)' with no argument"; }

    int(){ printf '%d' ${1:-} 2>/dev/null||:; };
    test

    int(){ expr 0 + ${1:-} 2>/dev/null||:; }
    test

    int(){ expr ${1:-} : '[^0-9]*\([0-9]*\)' 2>/dev/null||:; }
    test

    int(){ printf '%d' $(expr ${1:-} : '[^0-9]*\([0-9]*\)' 2>/dev/null)||:; }
    test

    # unexpected inconsistent results from `bc`
    int(){ bc<<<"${1:-}" 2>/dev/null||:; }
    test
)

Test output

int is a function
int ()
{
    printf '%d' ${1:-} 2> /dev/null || :
}
got '4' from '4'
got '5' from '5'
got '0' from '6foo'
got '0' from 'bar7'
got '0' from 'foo8.9bar'
got '0' from 'baz'
got '0' from ' '
got '0' from ''
got '0' with no argument

int is a function
int ()
{
    expr 0 + ${1:-} 2> /dev/null || :
}
got '4' from '4'
got '5' from '5'
got '' from '6foo'
got '' from 'bar7'
got '' from 'foo8.9bar'
got '' from 'baz'
got '' from ' '
got '' from ''
got '' with no argument

int is a function
int ()
{
    expr ${1:-} : '[^0-9]*\([0-9]*\)' 2> /dev/null || :
}
got '4' from '4'
got '5' from '5'
got '6' from '6foo'
got '7' from 'bar7'
got '8' from 'foo8.9bar'
got '' from 'baz'
got '' from ' '
got '' from ''
got '' with no argument

int is a function
int ()
{
    printf '%d' $(expr ${1:-} : '[^0-9]*\([0-9]*\)' 2>/dev/null) || :
}
got '4' from '4'
got '5' from '5'
got '6' from '6foo'
got '7' from 'bar7'
got '8' from 'foo8.9bar'
got '0' from 'baz'
got '0' from ' '
got '0' from ''
got '0' with no argument

int is a function
int ()
{
    bc <<< "${1:-}" 2> /dev/null || :
}
got '4' from '4'
got '5' from '5'
got '' from '6foo'
got '0' from 'bar7'
got '' from 'foo8.9bar'
got '0' from 'baz'
got '' from ' '
got '' from ''
got '' with no argument

Note

I got sent down this rabbit hole because the accepted answer is not compatible with set -o nounset (aka set -u)

# This works
$ ( number="3"; string="foo"; echo $((number)) $((string)); )
3 0

# This doesn't
$ ( set -u; number="3"; string="foo"; echo $((number)) $((string)); )
-bash: foo: unbound variable
Bruno Bronosky
  • 66,273
  • 12
  • 162
  • 149
0
let d=d1-d2;echo $d;

This should help.

mklement0
  • 382,024
  • 64
  • 607
  • 775
mykeaka
  • 11
  • This is effectively the same as `d=$(( d1 - d2 )); echo "$d"`, as covered by the accepted answer (with the same caveat that `0`-prefixed numbers are interpreted as _octals_). – mklement0 Dec 14 '16 at 23:01
-10

Use this:

#include <stdlib.h>
#include <string.h>

int main()
{
    const char *d1 = "11";
    int d1int = atoi(d1);
    printf("d1 = %d\n", d1);
    return 0;
}

etc.