Question

I am writing a shell script which works on my local /bin/sh fine (dash on Ubuntu 13.04), but I unltimately need to run it on a dumb box where I'm getting an error because of an operation on variables:

$((n2 - n1 + 1))

doesn't work, I get an error like:

syntax error: you disabled math support for $((arith)) syntax

I don't know a lot about the sh on there but I think this thing is busybox. How can I do maths on this dumb shell?


edit with list of applets

~ # busybox --list
[
arp
ash
cat
chgrp
chmod
chown
chroot
chvt
clear
cmp
cp
cut
date
dd
deallocvt
df
dmesg
du
echo
env
false
find
freeramdisk
ftpget
ftpput
grep
gunzip
gzip
hexdump
hwclock
ifconfig
ln
losetup
ls
md5sum
mkdir
mkfifo
mknod
mkswap
more
mount
mv
nslookup
ping
ping6
ps
pwd
renice
reset
rm
rmdir
route
seq
sh
sha1sum
sha256sum
sleep
sort
swapoff
swapon
switch_root
sync
tar
taskset
tee
telnet
test
tftp
time
top
touch
true
umount
uname
uniq
uptime
usleep
vconfig
vi
wget
whoami
yes
Was it helpful?

Solution 2

Another specific solution to your problem (n2 - n1 + 1) based on seq, sort -nr and uniq -u (POSIX-compliant).

foo()
{
    {
        seq 1 "$2"
        seq 0 "$1"
    } \
        | sort -n \
        | uniq -u \
        | grep -n "" \
        | sort -nr \
        | { read num; echo "${num%:*}"; }
}

$ foo 100 2000
1901

OTHER TIPS

Generic addition/subtraction/multiplication/division with seq+grep+sort

Notes:

  • All of these are POSIX-compliant, but there is a slightly faster non-POSIX subtract_nonposix which relies on a grep supporting -w and -B (non-POSIX, but even busybox' grep supports them)
  • add/subtract support only unsigned integers as input
  • multiply/divide support signed integers as input
  • subtract/multiply/divide can deal with negative results
  • depending on the input multiply/divide might be very costly (see comments)
  • subtract/multiply may pollute your namespace (they use $__x and $__y respectively) if not used in a subshell

arith.sh:

#!/bin/sh

is_uint()
{
    case "$1" in
        ''|*[!0-9]*) return 1
                     ;;
    esac
    [ "$1" -ge 0 ]
}

is_int()
{
    case "${1#-}" in
        ''|*[!0-9]*) return 1
                     ;;
    esac
}

# requires seq, grep -n, sort -nr
# reasonably fast
add()
{
    if   ! is_uint "$1" \
      || ! is_uint "$2"; then
        echo "Usage: add <uint1> <uint2>"
        return 1
    fi
    [ "$1" -eq 0 ] && { echo "$2"; return; }
    [ "$2" -eq 0 ] && { echo "$1"; return; }

    {
        seq 1 "$1"
        seq 1 "$2"
    } \
        | grep -n "" \
        | sort -nr \
        | { read num; echo "${num%[-:]*}"; }
}

# requires seq, grep -n, sort -nr, uniq -u
# reasonably fast
subtract()
{
    if   ! is_uint "$1" \
      || ! is_uint "$2"; then
        echo "Usage: subtract <uint1> <uint2>"
        return 1
    fi

    if [ "$1" -ge "$2" ]; then
        __x="$1"
        __y="$2"
    else
        __x="$2"
        __y="$1"
    fi

    {
        seq 0 "${__x}"
        seq 0 "${__y}"
    } \
        | sort -n \
        | uniq -u \
        | grep -n "" \
        | sort -nr \
        | \
        {
            read num
            : ${num:=0}
            [ "${__x}" = "$2" ] && [ "$1" -ne "$2" ] && minus='-'
            echo "${minus}${num%:*}"
        }
}

# requires seq, grep -wB
# faster than subtract(), but requires non-standard grep -wB
subtract_nonposix()
{
    if   ! is_uint "$1" \
      || ! is_uint "$2"; then
        echo "Usage: subtract <uint1> <uint2>"
        return 1
    fi

    if [ "$1" -ge "$2" ]; then
        __x="$1"
        __y="$2"
    else
        __x="$2"
        __y="$1"
    fi
    seq 0 "${__x}" \
        | grep -w -B "${__y}" "${__x}" \
        | \
        {
            read num
            [ "${__x}" = "$2" ] && [ "$1" -ne "$2" ] && minus='-'
            echo "${minus}${num}"
        }
}

# requires seq, sort -nr, add()
# very slow if multiplicand or multiplier is large
multiply()
{
    if   ! is_int "$1" \
      || ! is_int "$2"; then
        echo "Usage: multiply <int1> <int2>"
        return 1
    fi
    [ "$2" -eq 0 ] && { echo 0; return; }
    # make sure to use the smaller number for the outer loop
    # to speed up things a little if possible
    if [ $1 -ge $2 ]; then
        __x="$1"
        __y="$2"
    else
        __x="$2"
        __y="$1"
    fi
    __x="${__x#-}"
    __y="${__y#-}"

    seq 1 "${__y}" \
        | while read num; do
            sum="$(add "${sum:-0}" "${__x}")"
            echo "${sum}"
        done \
        | sort -nr \
        | \
        {
            read num
            if   [ "$1" -lt 0 -a "$2" -gt 0 ] \
              || [ "$2" -lt 0 -a "$1" -gt 0 ]; then
                minus='-'
            fi
            echo "${minus}${num}"
        }
}

# requires subtract()
# very costly if dividend is large and divisor is small
divide()
{
    if   ! is_int "$1" \
      || ! is_int "$2"; then
        echo "Usage: divide <int1> <int2>"
        return 1
    fi
    [ "$2" -eq 0 ] && { echo "division by zero"; return 1; }

    (
        sum="${1#-}"
        y="${2#-}"
        count=
        while [ "${sum}" -ge "${y}" ]; do
            sum="$(subtract "${sum}" "${y}")"
            # no need to use add() for a simple +1 counter,
            # this is way faster
            count="${count}."
        done

        if   [ "$1" -lt 0 -a "$2" -gt 0 ] \
          || [ "$2" -lt 0 -a "$1" -gt 0 ]; then
            minus='-'
        fi
        echo "${minus}${#count}"
    )
}

echo "10 4 14
4 10
10 10
2 -2
-2 -2
0 0
x y" | while read x y; do
    for op in add subtract subtract_nonposix multiply divide; do
        printf -- "${x} ${y} %-17s = %s\n" "${op}" "$("${op}" "${x}" "${y}")"
    done
    echo
done

Example run:

$ ./arith.sh
10 4 add               = 14
10 4 subtract          = 6
10 4 subtract_nonposix = 6
10 4 multiply          = 40
10 4 divide            = 2

4 10 add               = 14
4 10 subtract          = -6
4 10 subtract_nonposix = -6
4 10 multiply          = 40
4 10 divide            = 0

10 10 add               = 20
10 10 subtract          = 0
10 10 subtract_nonposix = 0
10 10 multiply          = 100
10 10 divide            = 1

2 -2 add               = Usage: add <uint1> <uint2>
2 -2 subtract          = Usage: subtract <uint1> <uint2>
2 -2 subtract_nonposix = Usage: subtract <uint1> <uint2>
2 -2 multiply          = -4
2 -2 divide            = -1

-2 -2 add               = Usage: add <uint1> <uint2>
-2 -2 subtract          = Usage: subtract <uint1> <uint2>
-2 -2 subtract_nonposix = Usage: subtract <uint1> <uint2>
-2 -2 multiply          = 4
-2 -2 divide            = 1

0 0 add               = 0
0 0 subtract          = 0
0 0 subtract_nonposix = 0
0 0 multiply          = 0
0 0 divide            = division by zero

x y add               = Usage: add <uint1> <uint2>
x y subtract          = Usage: subtract <uint1> <uint2>
x y subtract_nonposix = Usage: subtract <uint1> <uint2>
x y multiply          = Usage: multiply <int1> <int2>
x y divide            = Usage: divide <int1> <int2>

head, tail and wc

If your busybox has head, tail and wc built in, you might try the following:

head -c $n2 /dev/zero | tail -c +$n1 | wc -c

The first will generate a sequence of n2 zero bytes. The second will start at position n1, counting from 1, so it will skip n1 - 1 bytes. Therefore the resulting sequence has n2 - n1 + 1 bytes. This count can be computed using wc -c.

head, tail and ls or stat

Tried this with my busybox, although its configuration might differ from yours. I'm not sure whether wc will be that more likely than expr. If you have head and tail but no wc, then you could probably write the result to a temporary file, and then use stat or ls to obtain the size as a string. Examples for this are included below.

seq and wc

If you have wc but not head and tail, then you could substitute seq instead:

seq $n1 $n2 | wc -l

seq, tr and stat

As your comment indicates you have no wc but do have seq, here is an alternative provided you have sufficuently complete ls and also tr, perhaps even stat. Alas, I just noticed that tr isn't in your list of applets either. Nevertheless, for future reference, here it is:

seq $n1 $n2 | tr -d [0-9] > tempfilename
stat -c%s tempfilename

This creates a sequence of n2 - n1 + 1 lines, then removes all digits, leaving only that many newlines, which it writes to a file. We then print its size.

dd and ls

But as you don't have tr, you'll need something different. dd might suite your needs, since you can use it a bit like head or tail.

dd if=/dev/zero of=tmp1 bs=1 count=$n2 #   n2
dd if=tmp1 of=tmp2 bs=1 skip=$n1       # - n1
echo >> tmp2                           # +  1
set -- dummy `ls -l tmp2`
echo $6
rm tmp1 tmp2

This creates a sequence of n2 null bytes, then skips the first n1 of it. It appends a single newline to add 1 to its size. Then it uses ls to print the size of that file, and sets the positional variables $1, $2, … based on its output. $6 should be the column containing the size. Unless I missed something, this should all be available to you.

Alternative to busybox

If everything else fails, you might still implement your own digit-wise subtraction algorithm, using a lot of case distinctions. But that would require a lot of work, so you might be better of shipping a statically linked expr binary, or something specifically designed for your use case, instead of a scripted approach.

Really weird idea - usable only if you have network connection:

a=2,3
b=2.7
res=`wget -q -O - "http://someyourserver:6000/($a+$b)*5/2"`
echo $res

so you can do calculations over the network. You must setup one simple web server will get the PATH_INFO from the request and return only the result.

the server part (very simplified - without any error handling etc.) can be like next app.psgi:

my $app = sub {
    my $env = shift;
    my $calc = $env->{PATH_INFO};
    $calc =~ s:^/::; #remove 1.st slash
    $calc =~ s:[^\d\(\)\+\*/\-\.\,]::g; #cleanup, only digits and +-*/()., allowed
    $calc =~ s/,/\./g; #change , to .
    my $res = eval $calc;
        return [ 200, ['Content-Type' => 'text/plain'], [ "$res" ] ];
};

run with plackup -p 6000 app.psgi

or can use any other simple CGI or php script.

Alternatively, if you can reconfigure and rebuild BusyBox and enable "bash-compatible extensions", which should give you the ability to do mathematics. You will have to cross-compile your BusyBox again and replace the old binaries with your new one on your target (assuming you have the environment to do so). The BusyBox executable is only one binary, so you will only need to deal with simple replacement of one file.

I have BusyBox 1.19.4 and the mathematics evaluation works just fine.

Add/Subtract numbers using only printf

For me, the previous answers didn't work since I don't have seq, nor grep, nor wc, head or tail, not even dd.
My bash syntax doesn't support the math syntax $((n1+n2)), and not even the range syntax {1..N}. so it definitely was a tough environment.

I did manage to have basic add/subtract operations with small numbers (up to few thousands) using the following technique (calculate n1-n2):

n1=100
n2=20
str_n1=`printf "%${n1}s"` # "prints" 100 spaces, and store in str_n1
str_n2=`printf "%${n2}s"` # "prints" 20 spaces, and store in str_n2

if [ n1 -gt n2 ]    # if the n1 > n2, then:
then
    str_sub=${str_n1%$str_n2}   #delete str_n2 from str_n1
else
    str_sub=${str_n2%$str_n1}   #delete str_n1 from str_n2
fi

# until now we created a string with 100 spaces, then a second string with 20 spaces, then we deleted the 20 of 2nd string from 1st string, so now all we left is to:

sub_result=${#str_sub}   #check the length of str_sub

Same technique can be used also for adding numbers (continue from last example):

str_add=$str_n1$str_n2  # concat the two string to have 120 spaces together
add_result=${#str_add}  # check the length of add_result

Now, in my case, I had to work with bigger numbers (up to ten of millions), and it cannot work with this method like that since it actually needs to print millions of spaces, and it takes forever.
Instead, since I don't need to whole number, but just a part of it, i took the middle of the number using the substring syntax:

n1=10058000
n2=10010000

n1=${n1:3:3}  # -> 580 (takes 3 chars from the 3rd char)
n2=${n2:3:3}  # -> 100

Then calculate what I need with smaller numbers (of course needs to take more parameters in consideration for cases like n1=10158000, and n2=10092000)

Here is the original solution I posted to your problem (n2 - n1 + 1) based on seq and grep.

foo()
{
  seq 0 "$2" \
    | grep -nw -B "$1" "$2" \
    | { read num; echo "${num%[-:]*}"; }
}

$ foo 100 2000
1901

How it works:

  • First we generate a sequence of numbers from 0 to n2
  • Then we grep for n2 and include the leading n1 lines in the output. The first line holds our result now. We add the line number so the zero-based sequence accounts for the +1 (the line numbers and actual numbers will be off-by-one)
  • Then we fetch the first line with read (basically emulating head -n 1) and
  • discard the actual number from the output - the line number is the proper result
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top