Shell Script: How do I add the digits of a number? - shell

I am making a shell script that takes a single number (length is unimportant) from the command line and adds the digits of it together. I thought I had it, but it won't work and either displays "0+3+4+5" if the command input is 345 or it displays the variables when I use expr to add them.
#!/bin/bash
sum=0
i="$(expr length $1)"
s=$1
for i in $(seq 0 $((${#s} - 1))); do
value=${s:$i:1}
typeset -i value
sum=$sum+$value
done
echo $sum
Also doesn't work when I replace it with sum='expr $sum + $value'
any ideas?

What you are looking for is sum=$(($sum+$value)).

#!/bin/bash
expr $(echo $1| sed 's/./& + /g;s/..$//')
For example, if the argument is 12345, this translates it to the string 1 + 2 + 3 + 4 + 5 and uses expr to evaluate it.

Related

How can I get printf to produce "+ 123" instead of " +123"?

I want to print a number with a certain field width for the digits, have the digits right-aligned, and print a sign indicator - not right before the digits, but rather before the spacing. Thus
$ magic -123 7
- 123
rather than
$ magic -123 7
-123
Can I do that with the GNU coreutils version of the printf utility? Other versions of it perhaps?
Note: To be clear, the solution should work for any field spacing and any value, e.g.:
There might be zero, one or many spaces
The number might "overflow" the specified width
Simply transform the output:
printf %+d 12 | sed 's/[+-]/\0 /'
+ 12
To directly answer your question, I do not believe that you can, with the GNU coreutils version of the printf, have space padding be inserted between the sign character and the nonzero digits of the number. printf seems to always group the sign with the unpadded digits, placing any additional space padding to the left of the sign.
You can use a function called magic like this using pure shell utilities:
magic() {
# some sanity checks to make sure you get $1 and $2
[[ $2 -lt 0 ]] && printf "-" || printf "+"
printf "%${1}s\n" "${2#[+-]}"
}
Now use it as:
$> magic 5 120
+ 120
$> magic 5 120234
+120234
$> magic 5 -120234
-120234
$> magic 5 -120
- 120
$> magic 5 1
+ 1
$> magic 5 +120
+ 120
Based on #KarolyHorvath's suggestion, I suppose this should work:
printf "%+7d" 123 | sed -r 's/^( *)([+-])/\2\1/'
magic () {
local sign="+" number=$1 width=$2
if ((number < 0)); then
sign="-"
((number *= -1))
fi
printf '%s%*d\n' "$sign" "$((width - 1))" "$number"
}
or
magic () {
printf '%+*d\n' "$2" "$1" | sed -r 's/^( *)([+-])/\2\1/'
}
Uses the * in the format specification to take the field width from the arguments.

update number at the end of a line

I try to add a number at the end of each line for each files in a dir and update it.
Let's say: starting with 1 for the first file, 2 for the second file and so on.
The script I used is as follow:
#!/bin/bash
n=1
for filename in $HOME/path/to/direc/*
do
sed -i 's/$/ $(($n + 1))/g' $filename
done
The command I tough naively works like that: add a space and a number at the end of each line of file $filename. The number is updated each time with the condition $(($n + 1)).
Instead it print out at the end of each line of each file: " $(($n + 1))" ...
The 'space', at the beginning it's ok, but not the number.
Many thanks.
Bash doesn't interpolate strings in single quotes. Try double quotes instead.
Also, the /g is not needed, as $ can only match once per line.
To update the variable value, you need to assign the new value to it
$(( n = n + 1 ))
or, shorter
$(( n += 1 ))
or, even shorter
$(( n++ ))

How to sum a row of numbers from text file-- Bash Shell Scripting

I'm trying to write a bash script that calculates the average of numbers by rows and columns. An example of a text file that I'm reading in is:
1 2 3 4 5
4 6 7 8 0
There is an unknown number of rows and unknown number of columns. Currently, I'm just trying to sum each row with a while loop. The desired output is:
1 2 3 4 5 Sum = 15
4 6 7 8 0 Sum = 25
And so on and so forth with each row. Currently this is the code I have:
while read i
do
echo "num: $i"
(( sum=$sum+$i ))
echo "sum: $sum"
done < $2
To call the program it's stats -r test_file. "-r" indicates rows--I haven't started columns quite yet. My current code actually just takes the first number of each column and adds them together and then the rest of the numbers error out as a syntax error. It says the error comes from like 16, which is the (( sum=$sum+$i )) line but I honestly can't figure out what the problem is. I should tell you I'm extremely new to bash scripting and I have googled and searched high and low for the answer for this and can't find it. Any help is greatly appreciated.
You are reading the file line by line, and summing line is not an arithmetic operation. Try this:
while read i
do
sum=0
for num in $i
do
sum=$(($sum + $num))
done
echo "$i Sum: $sum"
done < $2
just split each number from every line using for loop. I hope this helps.
Another non bash way (con: OP asked for bash, pro: does not depend on bashisms, works with floats).
awk '{c=0;for(i=1;i<=NF;++i){c+=$i};print $0, "Sum:", c}'
Another way (not a pure bash):
while read line
do
sum=$(sed 's/[ ]\+/+/g' <<< "$line" | bc -q)
echo "$line Sum = $sum"
done < filename
Using the numsum -r util covers the row addition, but the output format needs a little glue, by inefficiently paste-ing a few utils:
paste "$2" \
<(yes "Sum =" | head -$(wc -l < "$2") ) \
<(numsum -r "$2")
Output:
1 2 3 4 5 Sum = 15
4 6 7 8 0 Sum = 25
Note -- to run the above line on a given file foo, first initialize $2 like so:
set -- "" foo
paste "$2" <(yes "Sum =" | head -$(wc -l < "$2") ) <(numsum -r "$2")

KornShell Script: List all even numbers in a range

What I am trying to do is list all the numbers that are even, between the two numbers the user enters via a KornShell (ksh) script. So if user enters for the first digit 2 then the second digit 25 it would display
2,4,6,8,10,12,14,16,18,20,22,24
first=2 # from user
last=25 # from user
seq $first 2 $last
This should work with ksh93 and bash, doesn't require seq or perl which might not be installed depending on the OS used.
function evens {
for((i=($1+($1%2));i<($2-3);i+=2));do printf "%s," $i;done
echo $((i+2))
}
$ evens 2 25
2,4,6,8,10,12,14,16,18,20,24
$ evens 3 24
4,6,8,10,12,14,16,18,20,24
$ evens 0 9
0,2,4,8
In ksh, assuming you have used variables start and end:
set -A evens # use an array to store the numbers
n=0
i=$start
(( i % 2 == 1 )) && (( i+=1 )) # start at an even number
while (( i <= end )); do
evens[n]=$i
(( n+=1 ))
(( i+=2 ))
done
IFS=,
echo "${evens[*]}" # output comma separated string
outputs
2,4,6,8,10,12,14,16,18,20,22,24
there are many ways to do it in shell, shell script, awk, seq etc...
since you tagged question with vi, I added one with vim:
fun! GetEven(f,t)
let ff=a:f%2?a:f+1:a:f
echom join(range(ff,a:t,2),",")
endf
source that function, and type :call GetEven(2,25) you will see your expected output.
It currently echoes in command area, if you want it to be shown in file, just use put or setline, easy too.
Using perl:
perl -e 'print join q{,}, grep { $_ % 2 == 0 } (shift .. shift)' 2 25
It yields:
2,4,6,8,10,12,14,16,18,20,22,24
EDIT to fix the trailing newline:
perl -e 'print join( q{,}, grep { $_ % 2 == 0 } (shift .. shift) ), "\n"' 2 25
By setting first=$(($1+($1%2))) and using the -s option to format the output you can use seq:
first=$(($1+($1%2)))
last=$2
seq -s, $first 2 $last
Save as a script called evens and call with even values of $first:
$ ./evens 2 25
2,4,6,8,10,12,14,16,18,20,22,24
Or odd values of $first:
$ ./evens 3 25
4,6,8,10,12,14,16,18,20,22,24

Pick and print one of three strings at random in Bash script

How can print a value, either 1, 2 or 3 (at random). My best guess failed:
#!/bin/bash
1 = "2 million"
2 = "1 million"
3 = "3 million"
print randomint(1,2,3)
To generate random numbers with bash use the $RANDOM internal Bash function:
arr[0]="2 million"
arr[1]="1 million"
arr[2]="3 million"
rand=$[ $RANDOM % 3 ]
echo ${arr[$rand]}
From bash manual for RANDOM:
Each time this parameter is
referenced, a random integer between 0
and 32767 is generated. The sequence
of random numbers may be initialized
by assigning a value to RANDOM. If
RANDOM is unset,it loses its
special properties, even if it is
subsequently reset.
Coreutils shuf
Present in Coreutils, this function works well if the strings don't contain newlines.
E.g. to pick a letter at random from a, b and c:
printf 'a\nb\nc\n' | shuf -n1
POSIX eval array emulation + RANDOM
Modifying Marty's eval technique to emulate arrays (which are non-POSIX):
a1=a
a2=b
a3=c
eval echo \$$(expr $RANDOM % 3 + 1)
This still leaves the RANDOM non-POSIX.
awk's rand() is a POSIX way to get around that.
64 chars alpha numeric string
randomString32() {
index=0
str=""
for i in {a..z}; do arr[index]=$i; index=`expr ${index} + 1`; done
for i in {A..Z}; do arr[index]=$i; index=`expr ${index} + 1`; done
for i in {0..9}; do arr[index]=$i; index=`expr ${index} + 1`; done
for i in {1..64}; do str="$str${arr[$RANDOM%$index]}"; done
echo $str
}
~.$ set -- "First Expression" Second "and Last"
~.$ eval echo \$$(expr $RANDOM % 3 + 1)
and Last
~.$
Want to corroborate using shuf from coreutils using the nice -n1 -e approach.
Example usage, for a random pick among the values a, b, c:
CHOICE=$(shuf -n1 -e a b c)
echo "choice: $CHOICE"
I looked at the balance for two samples sizes (1000, and 10000):
$ for lol in $(seq 1000); do shuf -n1 -e a b c; done > shufdata
$ less shufdata | sort | uniq -c
350 a
316 b
334 c
$ for lol in $(seq 10000); do shuf -n1 -e a b c; done > shufdata
$ less shufdata | sort | uniq -c
3315 a
3377 b
3308 c
Ref: https://www.gnu.org/software/coreutils/manual/html_node/shuf-invocation.html

Resources