7

We can use arithmetic operations inside shell script function:

function mess
{
   if (( "$1" > 0 )) ; then
      total=$1
   else
      total=100
   fi
   tail -$total /var/adm/messages | more
}

I try to do this arithmetic operations on the function args:

#!/bin/bash
byte="Bytes"
kilo="KB"
mega="MB"
giga="GB"
function bytesToUnites() {
if (( "$1" < 1000 )) 
then
    echo $1" "$byte
elif (( $1 < 1000000 ))
then
    let $1/=1000
    echo $1" "$kilo
fi
}

bytesToUnites 28888

But I get this error:

line 12: let: 28888/=1000: attempted assignment to non-variable (error token is "/=1000")
28888 KB

How can I fix this?

user79550
  • 175
  • 1
    Your placement of " characters is not optimal. In almost all cases, expansion of $var should be inside ", not outside of it. So for example echo $1" "$byte is bad, echo "$1 $byte" is good. – kasperd Aug 03 '14 at 10:38

3 Answers3

7

The problem is that you are attempting to perform parameter substitution by preceding the first positional parameter name with $.

You can accomplish what you want like so:

...
elif [ $1 -lt 1000000 ]
then
    arg="$1"
    let arg/=1000
    echo $arg" "$kilo
fi

As far as I can tell, you can't use the positional parameter directly by saying:

let 1/=1000

because this would be a syntax error.

Incidentally, from your error message, I can see that $1 was set to 28888. You should note that Bash doesn't do floating point arithmetic. You will find arg set to 28 (the quotient of integer division of 28888 by 1000) instead of 28.888. Please see this wonderful Q&A on how to do floating point arithmetic in scripts.

Joseph R.
  • 39,549
  • Thanks I wanted to upvote it but I can't – user79550 Aug 03 '14 at 00:16
  • @user79550 No worries. If the answer properly solves your problem, consider accepting it (click the tick button under the vote buttons) so that others know this question is solved. – Joseph R. Aug 03 '14 at 00:19
3

The only way to assign values to positional parameters in bash is via the set builtin:

set a b

assigns a to $1 and b to $2 (note that it resets the whole positional parameter list, so $3, $4... are lost).

So here, you could do:

set -- "$(($1 / 1000))"

To assign the value of $1 divided by 1000 to $1.

By contrast, with zsh (a more advanced Bourne-like shell, not part of the GNU project, found on all Unices though often not by default), you can assign individual positional parameters like:

1=$(($1 / 1000))

Or:

argv[1]=$(($1 / 1000))

Or:

(( argv[1] /= 1000 ))

($argv is a special array tied to the positional parameters there)

You still cannot do:

let 1/=1000
(( 1/=1000 ))

though.

zsh also supports floating point arithmetics, so you can do:

((argv[1] /= 1000.))

(the use a of a floating point constant (1000.) forces floating point arithmetic).

You may want to display it like:

printf "%.2f$kilo\n" $1
  • I have tried printf "%.2f$kilo\n" $1 as you suggested but it printed 28.00 instead of 28.88 @Stéphane Chazelas – user79550 Aug 03 '14 at 12:57
  • @user79550, in zsh? After ((argv[1] /= 1000.)) ? – Stéphane Chazelas Aug 03 '14 at 15:46
  • Is there other ways that I can get the floating point without zsh – user79550 Aug 03 '14 at 16:38
  • @user79550, you can call any other application (shell or not) that can do floating point arithmetic from your dumb shell (zsh, ksh93, bc, dc, awk, perl, php... see http://unix.stackexchange.com/questions/40786/how-to-do-integer-float-calculations-in-bash-or-other-languages-frameworks) – Stéphane Chazelas Aug 03 '14 at 16:41
0

For my two cents, I'll recommend you use the % modulo. You can just walk up the unit list and save your remainder every step. Here:

(   
    gb=$(((mb=(kb=1024*(b=1))*kb)*kb))                 #define units 
    set -- 8934752983457                               #your $1 - just a random test
    for u in '' k m g                                  #this part is neat
    do  [ $((c=$1/${u}b)) -ge 1 ] || break             #math eval varnames
        [ "$u" != g ] && c=$(($c%$kb))                 #if not yet g only save %
        set -- $(($1-$c)) "$c${u}b${2:+.$2}"           #decrement $1 - build $2
    done ; shift                                       #done - toss $1
    IFS=. ; printf %s\\n $*                            #split $2 andd print
)

OUTPUT

8321gb
140mb
454kb
417b
mikeserv
  • 58,310