Add / subtract variables in a really dumb shell

I am writing a shell script that works on my local /bin/sh order (dash on Ubuntu 13.04), but I don’t need to run it in a dead end, where I get an error message due to a variable operation:

 $((n2 - n1 + 1)) 

does not work, I get an error:

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

I don't know much about sh , but I think this thing is busy. How can I do the math in this dumb shell?


edit with applet list

 ~ # 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 
+7
source share
7 answers

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

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

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

Notes:

  • They are all POSIX compatible, but there is a slightly faster non-POSIX subtract_nonposix that relies on grep supporting -w and -B (non-POSIX, but even busybox ' grep supports them)
  • add / subtract only support unsigned integers as input
  • multiply / divide support for signed integers as input
  • subtract / multiply / divide may deal with negative results
  • depending on the input, multiply / divide can be very expensive (see comments)
  • subtract / multiply can pollute your namespace (they use $__x and $__y respectively) if they are 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 xy" | while read xy; do for op in add subtract subtract_nonposix multiply divide; do printf -- "${x} ${y} %-17s = %s\n" "${op}" "$("${op}" "${x}" "${y}")" done echo done 

Execution Example:

 $ ./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 xy add = Usage: add <uint1> <uint2> xy subtract = Usage: subtract <uint1> <uint2> xy subtract_nonposix = Usage: subtract <uint1> <uint2> xy multiply = Usage: multiply <int1> <int2> xy divide = Usage: divide <int1> <int2> 
+4
source

head, tail and wc

If your busybox has head , tail and wc , you can try the following:

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

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

head, tail and ls or stat

Tried this with my boot field, although its configuration may be different from yours. I'm not sure if wc will be more likely than expr . If you have head and tail , but no wc , you can write the result to a temporary file, and then use stat or ls to get the size as a string. Examples for this are given below.

seq and wc

If you have wc but not head and tail , you can replace seq instead:

 seq $n1 $n2 | wc -l 

seq, tr and stat

Your comment states that you do not have wc , but you have seq , here is an alternative if you complete ls as well as tr , enough, possibly even stat . Alas, I just noticed that tr also not on your list of applets. However, for future reference here:

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

This creates a sequence of lines n2 - n1 + 1 , and then deletes all the digits, leaving only the many lines of the new line that it writes to the file. Then we print its size.

dd and ls

But since you do not have tr , you will need something else. dd can suit your needs as 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, and then skips the first n1 . It adds one new row to add 1 to its size. He then uses ls to print the size of this file and sets the positional variables $1 , $2 , ... based on his output. $6 should be a column containing the size. If I did not miss anything, all of this will be available to you.

Busybox alternative

If all else fails, you can still implement your own algorithm for subtracting numbers, using many differences in the case. But this will require a lot of work, so you might be better off sending a statically linked binary expr or something specifically designed for your use case instead of a script.

+3
source

A really strange idea - it is used only if there is a network connection:

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

so that you can perform calculations over the network. You must configure one simple web server to get PATH_INFO from the request and return only the result.

the server side (very simplified - without any error handling, etc.) can be like the following 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 plackup -p 6000 app.psgi

or can use any other simple CGI or php script.

+2
source

Alternatively, if you can reconfigure and rebuild the BusyBox and enable "bash -compatible extensions", which should enable you to do the math. You will have to cross-compile your BusyBox and replace the old binaries with the new one in your target (provided that you have an environment for this). The BusyBox executable is only one binary, so you just need to replace one file.

I have BusyBox 1.19.4, and the math score works fine.

+1
source

Add / Subtract Numbers Using Only printf

For me, the previous answers did not work, since I do not have seq , nor grep , nor wc , head or tail , even dd .
My bash syntax does not support the mathematical syntax $ ((n1 + n2)) and even the range syntax {1..N}. therefore, it was definitely a difficult situation.

I managed to perform basic operations of adding / subtracting with small numbers (up to several thousand) using the following method (calculation 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 

The same technique can also be used to add numbers (continued from the 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 large numbers (up to ten million), and it cannot work with this method, since it really needs to print millions of spaces, and this takes forever.
Instead, since I do not need an integer, but only 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 figure out what I need with smaller numbers (of course, you need to consider more parameters for cases like n1 = 10158000 and n2 = 10092000)

+1
source

Here is the original solution that I sent 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 n1 at the beginning of the line. The first line has our result. We add a line number, so a sequence based on a null expression takes +1 into account (line numbers and actual numbers will be latches)
  • Then we fetch the first line using read (basically emulating head -n 1 ) and
  • discard the actual output number - the line number is the correct result
0
source

All Articles