我正在编写一个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
答案 0 :(得分:4)
seq
+ grep
+ sort
注意:
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)
如果您的busybox内置了head
,tail
和wc
,您可以尝试以下操作:
head -c $n2 /dev/zero | tail -c +$n1 | wc -c
第一个将生成n2
个零字节序列。第二个将从位置n1
开始,从1开始计数,因此它将跳过n1 - 1
个字节。因此,结果序列具有n2 - n1 + 1
个字节。可以使用wc -c
计算此计数。
尝试使用我的busybox,虽然它的配置可能与您的不同。我不确定wc
是否比expr
更可能。如果您有head
和tail
但没有wc
,那么您可以将结果写入临时文件,然后使用stat
或ls
获取作为字符串的大小。下面列出了这方面的例子。
如果您有wc
但不是head
和tail
,那么您可以替换seq
代替:
seq $n1 $n2 | wc -l
由于您的评论表明您没有wc
但确实有seq
,如果您有足够的完整ls
和tr
,甚至可能{{}},这里有另一种选择1}}。唉,我刚才注意到stat
也不在您的applet列表中。然而,为了将来参考,这里是:
tr
这将创建一系列seq $n1 $n2 | tr -d [0-9] > tempfilename
stat -c%s tempfilename
行,然后删除所有数字,只留下那些写入文件的新行。然后我们打印它的大小。
但是由于你没有n2 - n1 + 1
,你需要一些不同的东西。 tr
可能符合您的需求,因为您可以使用它dd
或head
。
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
应该是包含大小的列。除非我错过了什么,否则这一切都应该可以使用。
如果其他一切都失败了,您仍然可以使用大量的案例区分来实现自己的数字减法算法。但这需要大量工作,因此您可能更好地发布静态链接的$6
二进制文件,或者专门为您的用例设计的内容,而不是脚本方法。
答案 2 :(得分:3)
基于n2 - n1 + 1
,seq
和sort -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
,也没有wc
,head
或{ {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 + 1
和seq
发布到您的问题(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
获取第一行(基本上模仿head -n 1
)和