I trying to parse multiples files like this under bash-4.1
$cat hostname_abc.txt
host_type type_foo
SoftA version123
SoftB version456
to obtain an output where you can see how many times a version of Soft[A,B] is used, grouped by host type :
$./list_versions.sh
[type_foo] 11 times
SoftA:
[version123] 1 times
[version444] 5 times
[version567] 5 times
SoftB:
[version456] 9 times
[version777] 2 times
[type_bar] 6 times
SoftA:
[version444] 6 times
SoftB:
[version111] 4 times
[version777] 2 times
I don't know in advance the list of host_type and the versions.
So I tried to save in an associative array the count of each host_type and create dynamically the names of the associatives arrays which stored the count of each version of Soft[A,B] per host_type based base on a template host_type_Soft[A,B]
I tried many times with different variations of syntax and indirections so I remade below a more readable script that follow my aim :
#!/usr/bin/env bash
# ----- generated test conditions -----
echo -e "host_type typeA\nSoftA v2\nSoftB v1" > hostname_1.txt
echo -e "host_type typeB\nSoftA v1\nSoftB v1" > hostname_2.txt
echo -e "host_type typeB\nSoftA v1\nSoftB v0" > hostname_3.txt
echo -e "host_type typeA\nSoftA v0\nSoftB v0" > hostname_4.txt
echo -e "host_type typeA\nSoftA v3\nSoftB v2" > hostname_5.txt
echo -e "host_type typeB\nSoftA v3\nSoftB v1" > hostname_6.txt
echo -e "host_type typeB\nSoftA v2\nSoftB v2" > hostname_7.txt
echo -e "host_type typeA\nSoftA v1\nSoftB v2" > hostname_8.txt
echo -e "host_type typeC\nSoftA v0\nSoftB v4" > hostname_9.txt
list_hostname() {
for i in {1..9}; do
echo "hostname_${i}.txt"
done
}
declare -A list_host_type
while read f; do
#parse the hostname files
while read l; do
[[ $l = *"host_type"* ]] && host_type="$( echo $l | cut -d' ' -f2)"
[[ $l = *"SoftA"* ]] && versionA="$( echo $l | cut -d' ' -f2)"
[[ $l = *"SoftB"* ]] && versionB="$( echo $l | cut -d' ' -f2)"
done < <( cat "$f" )
#count the number of hosts by host_type
[[ ${list_host_type[$host_type]} ]] && ((list_host_type[$host_type]++)) || list_host_type[$host_type]='1'
#create associative arrays with a name only know at runtime
declare -A "${host_type}_SoftA"
declare -A "${host_type}_SoftB"
#count the number of host for the couple host_type and Soft[A,B], stored on the dynamically named assiociative array
[[ ${${host_type}_SoftA[$versionA]} ]] && ((${host_type}_SoftA[$versionA]++)) || ${host_type}_SoftA[$versionA]='1'
[[ ${${host_type}_SoftB[$versionB]} ]] && ((${host_type}_SoftB[$versionB]++)) || ${host_type}_SoftB[$versionB]='1'
done < <( list_hostname )
#print a non pretty-formated output
echo '==== result ====='
for m in "${!list_host_type[#]}"; do
echo "host type: $m count: ${list_model[$m]}"
for versionA in "${!${m}_softA[#]}"; do
echo " SoftA version: $versionA count: ${${m}_SoftA[$versionA]}"
done
for versionB in "${!${m}_softB[#]}"; do
echo " SoftB version: $versionB count: ${${m}_SoftB[$versionB]}"
done
done
I know they are others methods to achieve my goal but I want to know if I can use associative this way with bash-4.1.
I don't think you can use dynamic variable names with arrays in Bash.
(I tried a few things but couldn't figure out the syntax.)
Even if possible, I think it would be extremely difficult to understand.
A possible workaround could be using a single associative array,
with "composite keys".
That is, for example use a comma separated value of host type, soft and version:
while read f; do
line=0
while read col1 col2; do
if [[ $line = 0 ]]; then
host_type=$col2
else
soft=$col1
version=$col2
index=$host_type,$soft,$version
((list_host_type[$index]++))
fi
((line++))
done < <( cat "$f" )
done < <( list_hostname )
for m in "${!list_host_type[#]}"; do
echo $m = ${list_host_type[$m]}
done
For your sample data this would produce:
typeA,SoftA,v2 = 1
typeA,SoftA,v3 = 1
typeA,SoftA,v0 = 1
typeA,SoftA,v1 = 1
typeB,SoftA,v3 = 1
typeB,SoftA,v2 = 1
typeB,SoftA,v1 = 2
typeA,SoftB,v2 = 2
typeA,SoftB,v1 = 1
typeA,SoftB,v0 = 1
typeC,SoftB,v4 = 1
typeB,SoftB,v2 = 1
typeB,SoftB,v0 = 1
typeB,SoftB,v1 = 2
typeC,SoftA,v0 = 1
And then work with this associative array to compute the statistics you need. Here's a rough example implementation:
get_host_types() {
local names=(${!list_host_type[#]})
printf "%s\n" "${names[#]%%,*}" | sort -u
}
get_soft() {
local host_type=$1
local names=(${!list_host_type[#]})
for name in "${names[#]}"; do
[[ ${name%%,*} = $host_type ]] && echo $name
done | cut -d, -f2 | sort -u
}
get_versions() {
local prefix=$1
local names=(${!list_host_type[#]})
for name in "${names[#]}"; do
[[ ${name%,*} = $prefix ]] && echo $name
done | cut -d, -f3 | sort -u
}
indent=" "
for host_type in $(get_host_types); do
echo "[$host_type]"
for soft in $(get_soft $host_type); do
echo "$indent$soft:"
for version in $(get_versions $host_type,$soft); do
index=$host_type,$soft,$version
echo "$indent$indent[$version] ${list_host_type[$index]} times"
done
done
done
Producing as output:
[typeA]
SoftA:
[v0] 1 times
[v1] 1 times
[v2] 1 times
[v3] 1 times
SoftB:
[v0] 1 times
[v1] 1 times
[v2] 2 times
[typeB]
SoftA:
[v1] 2 times
[v2] 1 times
[v3] 1 times
SoftB:
[v0] 1 times
[v1] 2 times
[v2] 1 times
[typeC]
SoftA:
[v0] 1 times
SoftB:
[v4] 1 times
All in all, it would be better to implement this using a proper programming language.
Related
I have checked Looping over arrays, printing both index and value. The issue is I want to loop over output of command and not an array.
The code i came up with is:
array=($(seq 1 10))
for i in "${!array[#]}"; do
printf "%s\t%s\n" "$i" "${array[$i]}"
done
or,
ITER=0
for I in $(seq 1 10)
do
echo ${I} ${ITER}
ITER=$(expr $ITER + 1)
done
What i want to know is, is it possible to do it within the loop only (without array or ITER) outside the loop?
What i am looking for is something like:
for index,value in $(seq 1 10); do
echo $index $value
done
Let us know your actual requirement:
01)
#!/bin/bash
index=0
for filename in $(ls -atr)
do
indx=$(($indx+1))
echo "Index: $indx $filename"
done
output:
$ ./73412398.sh
Index: 1 ..
Index: 2 73412398.sh
Index: 3 .
One more try:
for index in $(ls -atr | grep -n $)
do
echo $index | sed "s/\([0-9]*\):/\1 /;"
done
output:
1 ..
2 73412398.sh
3 .
after modifying murugesan openssl's answer, the solution for me is:
for indexval in $(ls -atr | grep -n $)
do
echo index is "${indexval%%:*}"
echo value is "${indexval#*:}"
done
Here's what my script looks like
#/bin/bash
touch input.txt
touch output.txt
seq -w 0 999 >>input.txt
input=$(cat input.txt)
for i in $input
do
if [ $(($i%2)) -eq 0 ]; then
echo $i "even" >> output.txt
else
echo $i "odd" >> output.txt
fi
done
Here's the result of running the script and viewing the output.txt file created
000 even
001 odd
002 even
003 odd
004 even
005 odd
006 even
007 odd
I would like the script to do this for all 1,000 lines of the script, but I get an error message on line 9 saying
./tester.sh: line 9: 008: value too great for base (error token is "008")
My end goal is for the script to add each number on a line, and then tell if the number is even or odd, outputting to output.txt for all 1000 lines of the file.
End goal output file:
000 even
001 odd
002 even
003 odd
...
505 even
506 odd
507 even
508 odd
...
998 even
999 odd
From 000 all the way to 999
Use seq as seq and use printf to print your number in the format you like.
Bash arithmetic expansion interprets strings with leading zeros as octal numbers. You can force the number to be in 10th by prefixing it with 10# like (( 10#$i % 2)).
for i in $input
do
if [ $(( 10#$i % 2)) -eq 0 ]; then
echo $i "even" >> output.txt
else
echo $i "odd" >> output.txt
fi
done
Keep in mind that arithmetic expansion (( .. )) can do comparisions. It's clear to if (( 10#$i % 2 == 0 )); then.
I find printf "%03d" "$i" to be just clearer in this case.
No need to touch a file before >>, should create the file aumatically (this can be turned off with some bash set -o option, but I haven't seen anyone use it).
input=$(cat ...); for i in $input is just bad. Don't read lines with for
I don't like temp files.
How to read file line by line.
Your script is just:
seq 0 999 | xargs -i sh -c 'printf "%03d " "$1"; (( $1 % 2 == 0 )) && echo even || echo odd;' >>output.txt
If you prefer while read:
seq 0 999 | while IFS= read -r num; do
printf "%03d " "$num";
if (( num % 2 == 0 )); then
echo even
else
echo odd
fi
done >>output.txt
Or if you have to have your input.txt file containing 000\n001\n002\n and so on it's time for a tee:
seq -w 0 999 | tee -a input.txt | while IFS= read -r num; do
echo -n "$num "
if (( 10#$num % 2 == 0 )); then
echo even
else
echo odd
fi
done >>output.txt
This is a skeleton code for reading a text file line by line and acting upon the lines... Fill the missing part according to your own needs.
#!/bin/bash
{
while read -r line; do
if (( line % 2 == 0 )); then
# ...
else
# ...
fi
done < input.txt
} > output.txt
You may also apply pre-processing to the input file with <(cmd ...) notation:
#!/bin/bash
{
while read -r line; do
...
done < <(arbitrary-cmd input.txt | another-cmd ... )
} > output.txt
This form looks nicer but it spawns a "subshell" and makes it impossible for the code inside the while block to modify variables defined outside it, should you have any.
#!/bin/bash
{
arbitrary-cmd input.txt | another-cmd ... | while read -r line; do
...
done
} > output.txt
Your script could be something like
#!/bin/ksh
input=$(seq -w 0 999)
for i in $input
do
if [ $(($i%2)) -eq 0 ];then
echo $i "even" >> output.txt
else
echo $i "odd" >> output.txt
fi
done
then your output will be something like
000 even
001 odd
002 even
003 odd
004 even
005 odd
006 even
007 odd
Then you could grep "even" or "odd" and execute what you need or you could execute your command directly inside the if/else statement.
This works too:
#!/bin/bash
for x in `seq -w 0 999`; do
if [ $((10#$x%2)) == 0 ]; then
echo $x even
else
echo $x odd
fi
done > output.txt
so I'm dealing with column averaging problems
I have several columns of number,
1 2 3 4 5
2 3 4 5 6
8 4 5 6 7
9 8 7 6 5
1 9 9 9 2
What I want to do is to take an average of each column so that they will yield
4 5 5 6 5
The summing part is not a problem, I'm able to get the sum of each column. However, somehow I'm having trouble in counting the number of columns (variable $x)
Here is my code
while read line; do
x = 0
read -a array <<< "$line"
for i in "${!array[#]}"
do
column[${i}]=$((${column[${i}]}+${array[$i]}))
((x++))
done
done < $TMP
for sum in ${column[#]}
do
average=`expr $sum / $x`
remainder=`expr $sum % $x`
mult=`expr $remainder \* 10`
fracvalue=`expr $mult / $x`
echo $x
echo $average
echo $sum
echo $remainder
echo $mult
echo $fracvalue
done
The last several lines are for my testing purposes, here the $X keeps showing 1 instead of 5. That's why it screws up all the other variable. Does anybody know where's the flaw in this code? Really appreciate your help. Thanks
Another approach:
#!/bin/bash
declare -ia sum # declare array of integers
declare -i lines=0 # declare integer to count lines
# read file to array and sum values
while read -ra array; do
for ((i=0; i<${#array[#]};i++)); do
sum[$i]+=${array[$i]}
done
lines+=1
done < file
# print averages
for ((j=0; j<$i;j++)); do
echo -n "$((${sum[$j]}/$lines)) "
done
Output:
4 5 5 6 5
The problem is in the line
x = 0
should be
x=0 and make ((x+1)) as let "x+=1" it should work.
Maybe your file doesn't have the same number of lines in each column.
while read line; do
read -a array <<< "$line"
for i in "${!array[#]}"
do
column[${i}]=$((${column[${i}]}+${array[$i]}))
((x[${i}]++))
done
done < $TMP
for i in ${!column[#]}
do
sum=${column[$i]}
x=${x[${i}]}
#your calcul
done
The initial issue, aside from a few syntax peculiarities, was the resetting of x on each read which caused you to lose your line count. Additionally, your array is an indexed array, not an associative array.
Making the small adjustments and declareing the variables at the beginning (to hint to bash they are integers or arrays), it works as planned. note: there is no need for a here-string to assign the array, just use regular array syntax. Also note, for indexed arrays, there is no need to dereference the variable within [ ], simply ${array[i]} is fine.
While the use of expr is fine (old, slow, but portable and fine), you could use bash arithmetic syntax for your parameter calculations at the end. (you already have arrays). Finally, you are better served initializing each column[$i]=0 on the first read loop. You can do that with a simple flag:
#!/bin/bash
declare -i ncols=0
declare -i x=0
declare -a column
while read -r line; do
array=( $line )
[ $ncols -eq 0 ] && ncols=${#array[#]}
for ((i = 0; i < ncols; i++))
do
[ $x -eq 0 ] && column[$i]=0
column[$i]=$(( ${column[i]} + ${array[i]}))
done
((x++))
done < "$1"
for sum in ${column[#]}
do
average=`expr $sum / $x`
remainder=`expr $sum % $x`
mult=`expr $remainder \* 10`
fracvalue=`expr $mult / $x`
echo $x
echo $average
echo $sum
echo $remainder
echo $mult
echo $fracvalue
done
exit 0
Output
$ bash colavg.sh dat/colavg.txt
5
4
21
1
10
2
5
5
26
1
10
2
5
5
28
3
30
6
5
6
30
0
0
0
5
5
25
0
0
0
How can I speed this up? it's taking about 5 minutes to make one file...
it runs correctly, but I have a little more than 100000 files to make.
Is my implementation of awk or sed slowing it down? I could break it down into several smaller loops and run it on multiple processors but one script is much easier.
#!/bin/zsh
#1000 configs per file
alpha=( a b c d e f g h i j k l m n o p q r s t u v w x y z )
m=1000 # number of configs per file
t=1 #file number
for (( i=1; i<=4; i++ )); do
for (( j=i; j<=26; j++ )); do
input="arc"${alpha[$i]}${alpha[$j]}
n=1 #line number
#length=`sed -n ${n}p $input| awk '{printf("%d",$1)}'`
#(( length= $length + 1 ))
length=644
for ((k=1; k<=$m; k++ )); do
echo "$hmbi" >> ~/Glycine_Tinker/configs/config$t.in
echo "jobtype = energy" >> ~/Glycine_Tinker/configs/config$t.in
echo "analyze_only = false" >> ~/Glycine_Tinker/configs/config$t.in
echo "qm_path = qm_$t" >> ~/Glycine_Tinker/configs/config$t.in
echo "mm_path = aiff_$t" >> ~/Glycine_Tinker/configs/config$t.in
cat head.in >> ~/Glycine_Tinker/configs/config$t.in
water=4
echo $k
for (( l=1; l<=$length; l++ )); do
natom=`sed -n ${n}p $input| awk '{printf("%d",$1)}'`
number=`sed -n ${n}p $input| awk '{printf("%d",$6)}'`
if [[ $natom -gt 10 && $number -gt 0 ]]; then
symbol=`sed -n ${n}p $input| awk '{printf("%s",$2)}'`
x=`sed -n ${n}p $input| awk '{printf("%.10f",$3)}'`
y=`sed -n ${n}p $input| awk '{printf("%.10f",$4)}'`
z=`sed -n ${n}p $input| awk '{printf("%.10f",$5)}'`
if [[ $water -eq 4 ]]; then
echo "--" >> ~/Glycine_Tinker/configs/config$t.in
echo "0 1 0.4638" >> ~/Glycine_Tinker/configs/config$t.in
water=1
fi
echo "$symbol $x $y $z" >> ~/Glycine_Tinker/configs/config$t.in
(( water= $water + 1 ))
fi
(( n= $n + 1 ))
done
cat tail.in >> ~/Glycine_Tinker/configs/config$t.in
(( t= $t + 1 ))
done
done
done
One thing that is going to be killing you here is the sheer number of processes being created. Especially when they are doing the exact same thing.
Consider doing the sed -n ${n}p $input once per loop iteration.
Also consider doing the equivalent of awk as a shell array assignment, then accessing the individual elements.
With these two things you should be able to get the 12 or so processes (and the shell invocation via back quotes) down to a single shell invocation and the backquote.
Obviously, Ed's advice is far preferable, but if you don't want to follow that, I had a couple of thoughts...
Thought 1
Rather than run echo 5 times and cat head.in onto the Glycine file, each of which causes the file to be opened, seeked (or sought maybe) to the end, and appended, you could do that in one go like this:
# Instead of
hmbi=3
echo "$hmbi" >> ~/Glycine_thing
echo "jobtype = energy" >> ~/Glycine_thing
echo "somethingelse" >> ~/Glycine_thing
echo ... >> ~/Glycine_thing
echo ... >> ~/Glycine_thing
cat ... >> ~/Glycine_thing
# Try this
{
echo "$hmbi"
echo "jobtype = energy"
echo "somethingelse"
echo
echo
cat head.in
} >> ~/Glycine_thing
# Or, better still, this
echo -e "$hmbi\njobtype = energy\nsomethingelse" >> Glycine_thing
# Or, use a here-document, as suggested by #mklement0
cat -<<EOF >>Glycine
$hmbi
jobtype = energy
next thing
EOF
Thought 2
Rather than invoke sed and awk 5 times to find 5 parameters, just let awk do what sed was doing, and also do all 5 things in one go:
read symbol x y z < <(awk '...{printf "%.10f %.10f %.10f" $2,$3,$4}' $input)
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
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>
Another specific solution to your problem (n2 - n1 + 1) based on seq, sort -nr and uniq -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
head, tail and wc
If your busybox has head, tail and wc built in, you might try the following:
head -c $n2 /dev/zero | tail -c +$n1 | wc -c
The first will generate a sequence of n2 zero bytes. The second will start at position n1, counting from 1, so it will skip n1 - 1 bytes. Therefore the resulting sequence has n2 - n1 + 1 bytes. This count can be computed using wc -c.
head, tail and ls or stat
Tried this with my busybox, although its configuration might differ from yours. I'm not sure whether wc will be that more likely than expr. If you have head and tail but no wc, then you could probably write the result to a temporary file, and then use stat or ls to obtain the size as a string. Examples for this are included below.
seq and wc
If you have wc but not head and tail, then you could substitute seq instead:
seq $n1 $n2 | wc -l
seq, tr and stat
As your comment indicates you have no wc but do have seq, here is an alternative provided you have sufficuently complete ls and also tr, perhaps even stat. Alas, I just noticed that tr isn't in your list of applets either. Nevertheless, for future reference, here it is:
seq $n1 $n2 | tr -d [0-9] > tempfilename
stat -c%s tempfilename
This creates a sequence of n2 - n1 + 1 lines, then removes all digits, leaving only that many newlines, which it writes to a file. We then print its size.
dd and ls
But as you don't have tr, you'll need something different. dd might suite your needs, since 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, then skips the first n1 of it. It appends a single newline to add 1 to its size. Then it uses ls to print the size of that file, and sets the positional variables $1, $2, … based on its output. $6 should be the column containing the size. Unless I missed something, this should all be available to you.
Alternative to busybox
If everything else fails, you might still implement your own digit-wise subtraction algorithm, using a lot of case distinctions. But that would require a lot of work, so you might be better of shipping a statically linked expr binary, or something specifically designed for your use case, instead of a scripted approach.
Really weird idea - usable only if you have network connection:
a=2,3
b=2.7
res=`wget -q -O - "http://someyourserver:6000/($a+$b)*5/2"`
echo $res
so you can do calculations over the network. You must setup one simple web server will get the PATH_INFO from the request and return only the result.
the server part (very simplified - without any error handling etc.) can be like next 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 with plackup -p 6000 app.psgi
or can use any other simple CGI or php script.
Alternatively, if you can reconfigure and rebuild BusyBox and enable "bash-compatible extensions", which should give you the ability to do mathematics. You will have to cross-compile your BusyBox again and replace the old binaries with your new one on your target (assuming you have the environment to do so). The BusyBox executable is only one binary, so you will only need to deal with simple replacement of one file.
I have BusyBox 1.19.4 and the mathematics evaluation works just fine.
Add/Subtract numbers using only printf
For me, the previous answers didn't work since I don't have seq, nor grep, nor wc, head or tail, not even dd.
My bash syntax doesn't support the math syntax $((n1+n2)), and not even the range syntax {1..N}. so it definitely was a tough environment.
I did manage to have basic add/subtract operations with small numbers (up to few thousands) using the following technique (calculate 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
Same technique can be used also for adding numbers (continue from 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 bigger numbers (up to ten of millions), and it cannot work with this method like that since it actually needs to print millions of spaces, and it takes forever.
Instead, since I don't need to whole number, but just a 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 calculate what I need with smaller numbers (of course needs to take more parameters in consideration for cases like n1=10158000, and n2=10092000)
Here is the original solution I posted 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 the leading n1 lines in the output. The first line holds our result now. We add the line number so the zero-based sequence accounts for the +1 (the line numbers and actual numbers will be off-by-one)
Then we fetch the first line with read (basically emulating head -n 1) and
discard the actual number from the output - the line number is the proper result