7

我正在编写一个可以在本地/bin/sh正常运行的 shell 脚本(Ubuntu 13.04 上的破折号),但我最终需要在一个由于对变量的操作而出现错误的哑盒上运行它:

$((n2 - n1 + 1))

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

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

sh对那里的了解不多,但我认为这东西是busybox。我怎样才能在这个愚蠢的外壳上做数学?


使用小程序列表进行编辑

~ # 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
4

7 回答 7

4

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

笔记:

  • 所有这些都符合 POSIX,但是有一个稍快的非 POSIX subtract_nonposix,它依赖于grep支持-w-B(非 POSIX,但甚至busybox'grep支持它们)
  • add/subtract仅支持无符号整数作为输入
  • multiply/divide支持有符号整数作为输入
  • subtract//可以处理负面multiply结果divide
  • 取决于输入multiply/divide可能非常昂贵(见评论)
  • subtract/如果不在子shell中使用,multiply可能会污染您的命名空间(它们分别使用$__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>
于 2013-05-03T14:05:00.717 回答
3

头、尾和厕所

如果您的 busybox 有head,tail并且wc是内置的,您可以尝试以下操作:

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

第一个将生成一个n2零字节序列。第二个将从 position 开始n1,从 1 开始计数,因此它会跳过n1 - 1字节。因此得到的序列有n2 - n1 + 1字节。这个计数可以使用 来计算wc -c

头部、尾部和 ls 或 stat

用我的busybox试过这个,虽然它的配置可能与你的不同。我不确定是否wc会比expr. 如果你有headtail没有wc,那么你可能会将结果写入一个临时文件,然后使用statorls来获取字符串的大小。下面包括了这方面的例子。

seq 和 wc

如果你有wc但没有headand tail,那么你可以seq代替:

seq $n1 $n2 | wc -l

seq、tr 和 stat

正如您的评论表明您没有wc但确实有seq,这里有一个替代方案,前提是您已经足够完整lstr甚至可能stat。唉,我刚刚注意到它tr也不在你的小程序列表中。不过,为了将来参考,这里是:

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

这将创建一系列n2 - n1 + 1行,然后删除所有数字,只留下那么多换行符,并将其写入文件。然后我们打印它的大小。

dd 和 ls

但是因为你没有tr,你需要一些不同的东西。dd可能适合您的需求,因为您可以使用它有点像heador 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这会创建一个空字节序列,然后跳过n1它的第一个。它附加一个换行符以将 1 添加到其大小。然后它用于ls打印该文件的大小,并根据其输出设置位置变量$1, $2, ...。$6应该是包含大小的列。除非我错过了什么,否则这一切都应该可供您使用。

替代busybox

如果其他一切都失败了,您可能仍然会使用很多大小写区分来实现自己的数字减法算法。但这需要大量工作,因此您最好提供静态链接的expr二进制文件,或者专门为您的用例设计的东西,而不是脚本方法。

于 2013-05-02T12:41:55.597 回答
3

您的问题的另一个特定解决方案 ( n2 - n1 + 1) 基于seq,sort -nruniq -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
于 2013-05-17T11:38:20.317 回答
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 脚本。

于 2013-05-03T06:52:17.283 回答
1

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

我有 BusyBox 1.19.4,数学评估工作得很好。

于 2013-05-02T15:49:37.687 回答
1

仅使用加/减数字printf

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

我确实设法使用以下技术(计算 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

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

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=10058000
n2=10010000

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

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

于 2016-01-12T12:47:36.227 回答
0

n2 - n1 + 1这是我基于seqand发布到您的问题 () 的原始解决方案grep

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

$ foo 100 2000
1901

这个怎么运作:

  • 0首先我们生成一个从到的数字序列n2
  • 然后我们grep在输出中n2包含引导n1行。第一行现在保存了我们的结果。我们添加行号,因此从零开始的序列占+1(行号和实际数字将相差一)
  • 然后我们用read(基本上 emulating head -n 1) 和
  • 从输出中丢弃实际数字 - 行号是正确的结果
于 2013-05-17T11:42:45.477 回答