Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add/subtract variables in a really dumb shell

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
like image 631
wim Avatar asked May 02 '13 05:05

wim


1 Answers

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>
like image 154
Adrian Frühwirth Avatar answered Sep 27 '22 16:09

Adrian Frühwirth