32

I want to take the absolute of a number by the following code in bash:

#!/bin/bash
echo "Enter the first file name: "
read first

echo "Enter the second file name: "
read second

s1=$(stat --format=%s "$first")
s2=$(stat -c '%s' "$second")

res= expr $s2 - $s1

if [ "$res" -lt 0 ]
then
        res=$res \* -1
fi

echo $res

Now the problem I am facing is in the if statement, no matter what I changes it always goes in the if, I tried to put [[ ]] around the statement but nothing.

Here is the error:

./p6.sh: line 13: [: : integer expression expected
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Ali Sajid
  • 3,964
  • 5
  • 18
  • 33

9 Answers9

75

You might just take ${var#-}.

${var#Pattern} Remove from $var the shortest part of $Pattern that matches the front end of $var. tdlp


Example:

s2=5; s1=4
s3=$((s1-s2))

echo $s3
-1

echo ${s3#-}
1
Suuuehgi
  • 4,547
  • 3
  • 27
  • 32
  • 5
    This is great for getting a positive number out of `$(( 2**63 ))`. – ltrainpr Apr 12 '18 at 14:15
  • 1
    I think this point by @ltrainpr is that bash overflows as a signed int at this number: care should be taken whether or not the absolute magnitude is computed. I mean: if you've already overflowed, you've already got a problem. – Jack Wasey Jun 19 '22 at 18:40
7
$ s2=5 s1=4
$ echo $s2 $s1
5 4
$ res= expr $s2 - $s1
1
$ echo $res

What's actually happening on the fourth line is that res is being set to nothing and exported for the expr command. Thus, when you run [ "$res" -lt 0 ] res is expanding to nothing and you see the error.

You could just use an arithmetic expression:

$ (( res=s2-s1 ))
$ echo $res
1

Arithmetic context guarantees the result will be an integer, so even if all your terms are undefined to begin with, you will get an integer result (namely zero).

$ (( res = whoknows - whocares )); echo $res
0

Alternatively, you can tell the shell that res is an integer by declaring it as such:

$ declare -i res
$ res=s2-s1

The interesting thing here is that the right hand side of an assignment is treated in arithmetic context, so you don't need the $ for the expansions.

kojiro
  • 74,557
  • 19
  • 143
  • 201
  • 2
    @AliSajid no, the _error_ is at `if`. The _problem_ is before `if`, when you never actually assign a value to `res`. – kojiro Mar 24 '15 at 01:47
  • 6
    abs $res => `${res/#-/}` :) – rici Mar 24 '15 at 02:02
  • @rici heh. I thought of that, but ruled it out because OP said _by the following code_. But yeah… – kojiro Mar 24 '15 at 02:08
  • @kojiro `$ s2=5 s1=4` `$ echo $s2 $s1` `5 4` `$ res= expr $s1 - $s2` `-1` `$ echo $res` `{empty line}` – Vitalii Diravka Oct 08 '19 at 08:53
  • Sorry dude) Looks like you answer is outdated. Anyway rici answer helped me: `${VAR1#-}` works fine – Vitalii Diravka Oct 09 '19 at 17:40
  • Nah, my answer is not outdated. It's perfectly fine and helps OP understand the error they were getting. The _logic_ of OP's solution is perfectly fine for getting an absolute value. There are other solutions and that's cool too. – kojiro Oct 09 '19 at 20:24
7

I know this thread is WAY old at this point, but I wanted to share a function I wrote that could help with this:

abs() { 
    [[ $[ $@ ] -lt 0 ]] && echo "$[ ($@) * -1 ]" || echo "$[ $@ ]"
}

This will take any mathematical/numeric expression as an argument and return the absolute value. For instance: abs -4 => 4 or abs 5-8 => 3

bng44270
  • 339
  • 2
  • 6
  • `$(( ... ))` is the preferred syntax for arithmetic contexts rather than `$[ ... ]`. And this can be significantly simplified into a single arithmetic context: `abs() { echo $(( $1 > 0 ? $1 : -$1 )); }` – dimo414 Apr 02 '23 at 06:37
2

A workaround: try to eliminate the minus sign.

  1. with sed
x=-12
x=$( sed "s/-//" <<< $x )
echo $x

12
  1. Checking the first character with parameter expansion
x=-12
[[ ${x:0:1} = '-' ]] && x=${x:1} || :
echo $x

12

This syntax is a ternary opeartor. The colon ':' is the do-nothing instruction.

  1. or substitute the '-' sign with nothing (again parameter expansion)
x=-12
echo ${x/-/}

12

Personally, scripting bash appears easier to me when I think string-first.

MarcoP
  • 1,438
  • 10
  • 17
2

I translated this solution to bash. I like it more than the accepted string manipulation method or other conditionals because it keeps the abs() process inside the mathematical section

abs_x=$(( x * ((x>0) - (x<0)) ))

x=-3
abs_x= -3 * (0-1) = 3

x=4
abs_x= 4 * (1-0) = 4
Martin
  • 2,316
  • 1
  • 28
  • 33
0

For the purist, assuming bash and a relatively recent one (I tested on 4.2 and 5.1):

abs() {
  declare -i _value
  _value=$1
  (( _value < 0 )) && _value=$(( _value * -1 ))
  printf "%d\n" $_value
}
Wayne Walker
  • 2,316
  • 3
  • 23
  • 25
0

If you don't care about the math and only the result matters, you may use

echo $res | awk -F- '{print $NF}'
aschkant
  • 17
  • 4
0

The simplest solution:

res="${res/#-}"

Deletes only one / occurrence if - is at the first # character.

Mario Palumbo
  • 693
  • 8
  • 32
0

This simple one works for floating point numbers:

echo "sqrt($var^2)" | bc

HQY
  • 1
  • 1