在一个非常愚蠢的shell中添加/减去变量

时间:2013-05-02 05:43:57

标签: shell math busybox dash-shell

我正在编写一个shell脚本,可以在我的本地/bin/sh上工作(在Ubuntu 13.04上点击),但是我非常需要在一个愚蠢的盒子上运行它,因为我在操作时遇到错误变量:

$((n2 - n1 + 1))

不起作用,我收到如下错误:

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

我对那里的sh了解不多,但我认为这件事是忙碌的。我怎么能在这个哑壳上做数学呢?


使用小程序列表

编辑

~ # 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 个答案:

答案 0 :(得分:4)

通用加法/减法/乘法/除法与seq + grep + sort

注意:

  • 所有这些都符合POSIX标准,但有一个稍微快一点的非POSIX subtract_nonposix依赖grep支持-w-B(非POSIX) ,但即使busybox'grep支持他们)
  • add / subtract仅支持无符号整数作为输入
  • multiply / divide支持已签名的整数作为输入
  • subtract / multiply / divide可以处理否定结果
  • 取决于输入multiply / divide可能会非常昂贵(请参阅评论)
  • subtract / multiply可能污染您的命名空间(如果未在子shell中使用,则分别使用$__x$__y

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

示例运行:

$ ./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>

答案 1 :(得分:3)

头,尾和wc

如果您的busybox内置了headtailwc,您可以尝试以下操作:

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

第一个将生成n2个零字节序列。第二个将从位置n1开始,从1开始计数,因此它将跳过n1 - 1个字节。因此,结果序列具有n2 - n1 + 1个字节。可以使用wc -c计算此计数。

头部,尾部和ls或stat

尝试使用我的busybox,虽然它的配置可能与您的不同。我不确定wc是否比expr更可能。如果您有headtail但没有wc,那么您可以将结果写入临时文件,然后使用statls获取作为字符串的大小。下面列出了这方面的例子。

seq和wc

如果您有wc但不是headtail,那么您可以替换seq代替:

seq $n1 $n2 | wc -l

seq,tr和stat

由于您的评论表明您没有wc但确实有seq,如果您有足够的完整lstr,甚至可能{{}},这里有另一种选择1}}。唉,我刚才注意到stat也不在您的applet列表中。然而,为了将来参考,这里是:

tr

这将创建一系列seq $n1 $n2 | tr -d [0-9] > tempfilename stat -c%s tempfilename 行,然后删除所有数字,只留下那些写入文件的新行。然后我们打印它的大小。

dd和ls

但是由于你没有n2 - n1 + 1,你需要一些不同的东西。 tr可能符合您的需求,因为您可以使用它ddhead

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 空字节序列,然后跳过它的第一个n2。它附加一个换行符,以便为其大小添加1。然后,它使用n1打印该文件的大小,并根据其输出设置位置变量ls$1,.... $2应该是包含大小的列。除非我错过了什么,否则这一切都应该可以使用。

busybox

的替代方案

如果其他一切都失败了,您仍然可以使用大量的案例区分来实现自己的数字减法算法。但这需要大量工作,因此您可能更好地发布静态链接的$6二进制文件,或者专门为您的用例设计的内容,而不是脚本方法。

答案 2 :(得分:3)

基于n2 - n1 + 1seqsort -nr(符合POSIX标准)的问题(uniq -u)的另一个具体解决方案。

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

$ foo 100 2000
1901

答案 3 :(得分:2)

真的奇怪的想法 - 只有在你有网络连接时才可用:

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

因此您可以通过网络进行计算。您必须设置一个简单的Web服务器才能从请求中获取PATH_INFO并仅返回结果。

服务器部分(非常简化 - 没有任何错误处理等)可以像下一个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" ] ];
};

使用plackup -p 6000 app.psgi

运行

或者可以使用任何其他简单的CGI或php脚本。

答案 4 :(得分:1)

或者,如果您可以重新配置和重建BusyBox并启用“bash兼容扩展”,这将使您能够进行数学运算。您将不得不再次交叉编译BusyBox并将旧二进制文件替换为目标上的新二进制文件(假设您有这样做的环境)。 BusyBox可执行文件只有一个二进制文件,因此您只需要处理一个文件的简单替换。

我有BusyBox 1.19.4并且数学评估工作正常。

答案 5 :(得分:1)

仅使用printf

添加/减去数字

对我来说,之前的答案没有用,因为我没有seq,也没有grep,也没有wchead或{ {1}},甚至不是tail 我的bash语法不支持数学语法$((n1 + n2)),甚至不支持范围语法{1..N}。所以这绝对是一个艰难的环境。

我确实设法使用以下技术(计算n1-n2)进行基本的加/减运算(小数(最多几千)):

dd

同样的技术也可用于添加数字(从上一个示例继续):

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

现在,在我的情况下,我不得不使用更大的数字(高达数十万),并且它无法使用这种方法,因为它实际上需要打印数百万个空格,而且需要永远。
相反,由于我不需要整数,只是其中的一部分,我使用子串语法取数字的中间位置:

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

然后用较小的数字计算我需要的东西(当然需要考虑更多的参数,例如n1 = 10158000和n2 = 10092000)

答案 6 :(得分:0)

以下是我根据n2 - n1 + 1seq发布到您的问题(grep)的原始解决方案。

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

$ foo 100 2000
1901

工作原理:

  • 首先,我们生成从0n2
  • 的数字序列
  • 然后我们grepn2并在输出中包含前导n1行。第一行现在保留我们的结果。我们添加行号,以便基于零的序列占+1(行号和实际数字将是一对一的)
  • 然后我们使用read获取第一行(基本上模仿head -n 1)和
  • 从输出中丢弃实际数字 - 行号是正确的结果