New to bash on my course, generally enjoying it but as soon as we've been given some coursework it's thrown a spanner in the works.
The assignment is essentially to take an arbitrary amount of numbers from the user (using the read command), add them up, and return the result.
The previous task included a fixed amount of 10 numbers, for which i wrote:
#!/bin/sh
echo "Please enter 10 numbers"
read num1
read num2
read num3
read num4
read num5
read num6
read num7
read num8
read num9
read num10
result=$((num1 + num2 + num3 + num4 + num5 + num6 + num7 + num8 + num9 + num10))
echo The result is $result
Now this works fine, but I just know I'm making it harder for myself/too long, and seen as the task I'm struggling with is supposed to come from editing that script that takes 10 numbers, clearly im missing something pretty basic to take in numbers from the user & add them together without writing it 10 different times
A little guidance on user input with arbitrary numbers would be great
You can loop over a prompt and sum one number at a time to the total.
total=0
for((i=1; i<=10; i++)); do
read -p "Please enter a number: " -r num
((total+=num))
done
echo "The total is $total"
The "C-style" for loop syntax is a Bash extension; if you need your script to be portable to POSIX sh you can do something like
for i in $(seq 10); do
:
or if you can't rely on seq being installed, the age-old fugly
i=1
while [ "$i" -le 10 ]; do
:
i=$(expr "$i" + 1)
done
Related
I am trying to learn bash so I was working on a "guess the number" type game. I am at the point of trying to generate random numbers based on the user's input for lower and upper bounds. But some test cases seem to create numbers that don't make sense; some being outside my bounds and others not being what I would expect.
Here is my program
echo "Enter the lower bound: "
echo -n "> "
read lowerBound
while [ "$lowerBound" -lt 0 ]
do
echo "Lower bound must be >= 0. Please try again: "
echo -n "> "
read lowerBound
done
echo
echo "Enter the upper bound: "
echo -n "> "
read upperBound
lowerLimit=$(($lowerBound + 1))
while [ "$upperBound" -lt "$lowerLimit" ]
do
echo "Upper bound must be larger than lower bound. Please try again: "
echo -n "> "
read upperBound
done
echo
randNum=$(( $RANDOM % ( ($upperBound - $lowerBound) + 1 ) ))
echo "RANDOM=$RANDOM randNum=$randNum"
Here is an output that produced unexpected results. I would expect 6: (9237 % (10-1+1) = 7 but output is 5)
Enter the lower bound:
> 1
Enter the upper bound:
> 10
RANDOM=9237 randNum=5
Finally here is some output where the generated number lies outside my bounds. I understand this from doing the math myself, but thought this equation was supposed to generate random numbers in a range. Here 26921 % (126-123+1) = 9237. Most likely just a coincidence that 9237 appeared in subsequent runs.
Enter the lower bound:
> 123
Enter the upper bound:
> 126
RANDOM=26921 randNum=0
I'm not sure if my equation is wrong or if it is something I am doing wrong in Bash. Any pointers would be appreciated.
Note: I am not new to programming, just new to Bash.
Each reference of $RANDOM generates a new number.
You can see this with something as simple as:
$ echo "$RANDOM : $RANDOM : $RANDOM"
297 : 20330 : 14461
In your code you get one $RANDOM value when you calculate randNum, and a second (and likely different) $RANDOM value in the echo; net result is randNum=5 was not generated based on $RANDOM=9237.
If you want to reference the same $RANDOM value more than once you should first store it in a variable and then (re)use said variable as needed, eg:
ranx=$RANDOM
randNum=$(( $ranx % ( ($upperBound - $lowerBound) + 1 ) ))
echo "RANDOM=$ranx randNum=$randNum"
#!/bin/bash
a=0
b=1
echo "give a number:"
read n
clear
echo "the fibonacci sequence until $n:"
for (( i=0; i<n; i++ ))
do
echo -n "$a "
c=$((a + b))
a=$b
b=$c
done
If I interpret it well, this code echoes a $a value after every i++ jumps, then switches the variables as you can see, then on the next i++ loop jump it happens again until "i" reaches "n".
Question: if we want in every loop jump the value of the new "c" why shall we echo $a? I see the connection that: a=$b, b=$c, and c=$((a + b)) but i don't get it why do we refer to $a when doing echo?
Is there a more elegant solution?
You mean, “never ever calculate anything needlessly, ever”? It is possible, of course, but it depends on how much ugliness in the control logic you are willing to tolerate. In the example below, fibonacci1 calculates at most one extra element of the series that may not get printed out and fibonacci2 never calculates any extra series elements and everything makes it to the standard output.
Is any of that “elegant”? Probably not. This is actually a common problem most people encounter when coding (in languages other than purely functional ones): Most high(er)-level languages (unlike e.g. assemblers) provide predefined control structures that work great in typical and obvious cases (e.g. one control variable and one operation per iteration) but may become “suboptimal” in more complex scenarios.
A notoriously common example is a variable that stores a value from the previous iteration. Let’s assume you assign it at the very end of the loop. That works fine, but… Could you avoid the very last assignment (because it is useless), instead of leaving it to the compiler’s wisdom? Yes, you could, but then (e.g.) for ((init; condition; step)); do ...; ((previous = current)); done becomes (e.g.) for ((init;;)); do ...; ((step)); ((condition)) || break; ((previous = current)); done.
On one hand, a tiny bit of something (such as thin air) may have been “saved”. On the other hand, the code became assembler-like and harder to write, read and maintain.
To find a balance there^^^ and {not,} optimize when it {doesn’t,does} matter is a lifelong struggle. It may be something like CDO, which is like OCD, but sorted correctly.
fibonacci1() {
local -ai fib=(0 1)
local -i i
for ((i = $1; i > 2; i -= 2)) {
printf '%d %d ' "${fib[#]}"
fib=($((fib[0] + fib[1])) $((fib[0] + 2 * fib[1])))
}
echo "${fib[#]::i}"
}
fibonacci2() {
trap 'trap - return; echo' return
local -i a=0 b=1 i="$1"
((i)) || return 0
printf '%d' "$a"
((--i)) || return 0
printf ' %d' "$b"
for ((;;)); do
((--i)) || return 0
printf ' %d' "$((a += b))"
((--i)) || return 0
printf ' %d' "$((b += a))"
done
}
for ((i = 0; i <= 30; ++i)); do
for fibonacci in fibonacci{1,2}; do
echo -n "${fibonacci}(${i}): "
"$fibonacci" "$i"
done
done
Background
Beginner here trying to learn some Bash basics.
My question is my attempt at modifying an existing textbook example to include simple input validation. The validation works if one or more inputs are inappropriate but when all three inputs are appropriate, I instead get a syntax error warning and then an answer.
Code is at the end, but below are my thoughts. Appreciate any corrections wherever they are wrong.
Much thanks.
Thoughts
I have three conditions to check for. If any one of them fails, a message is displayed and the program terminates. This works. However, if all 3 conditions are met, then I receive a syntax error.
I thought the error might be related to expansion so I manually ran the commands and supplied hard inputs using the command line. E.g. bc <<< "0.5 > 0" and these seemed to work as intended.
Subsequently, it seems that my problems only arise when I involve the $interest variable with its decimal points. However, I used BC because my understanding is that Bash only does Integers. What else have I missed out?
Code
# loan-calc: script to calculate monthly loan payments
# formulae may be mathematically wrong, example copied from textbook
# reference only for bash scripting, not math
PROGNAME="${0##*/}"
usage () {
cat <<- EOF
Usage: $PROGNAME PRINCIPAL INTEREST MONTHS
Where:
PRINCIPAL is the amount of the loan.
INTEREST is the APR as a number (7% = 0.07)
MONTHS is the length of the loan's term.
EOF
}
read -p "Enter principal amount > " principal
read -p "Enter interest rate (E.g. 7.5% = 0.075) > " interest
read -p "Enter term length in months > " months
# Principal, interest rate, and months must be more than 0.
if (( "$principal <= 0" | bc )) || (( "$months <= 0" | bc )) || (( "$interest <= 0" | bc )); then
usage
exit 1
fi
result=$(bc <<- EOF
scale = 10
i = $interest / 12
p = $principal
n = $months
p * ((i * ((1 + i) ^ n)) / (((1 + i) ^ n) - 1))
EOF
)
printf "%0.2f\n" $result
Shell Arithmetic
(( 1 | bc )) does not pipe 1 into bc. When you are evaluating expressions inside of (( expression )), | is the bitwise OR operator, not a pipe.
(( bc )) evaluates to 1 every time, so all of your conditional tests are just OR'ing the number with 1, not piping the number into bc.
Your expression inside of the parentheses should be the output from echoing the mathematical string into bc using a pipe, e.g. (( $(echo "$variable <= 0"| bc) )).
This can be wrapped in a named function so the if statement is more readable.
notValid() {
(( $(echo "$1 <= 0" | bc) ))
}
# Principal, interest rate, and months must be more than 0.
if notValid "$principal" || notValid "$months" || notValid "$interest"; then
usage
exit 1
fi
while true
do
if [ $userinput = 1 ];
then
guesses=10
(( answer = RANDOM % 20 ))
read -p "Guess the number between 1-20 if you can $answer : " input
if [ $input != $answer ];
then
(( guesses=guesses-1 ))
echo "Wrong answer! You got ${guesses} left!"
else
echo "Correct answer! You had ${guesses} left. Lucky you!"
read -p "${name}, would you like to continue playing or not [Yes/No]? " decide
if [ $decide = "Yes" ];
then
continue
else
echo -e "${Red}Bye bye!"
Example:
guess=10
User inputs 2 guesses then program has to minus those 2 guesses from total of 10 guesses, in that case, 10-2=8 guesses left. How to do this?
The only necessary change is moving guesses=10 out of your loop, such that it's run only once (when your script is starting).
As for best-practice decrement forms, a terser bash-only approach (albeit no more or less valid than your existing implementation) would look like:
(( guesses-- ))
...whereas a more portable approach (compatible with all POSIX-family shells) is:
guesses=$(( guesses - 1 ))
If you are specifically using the Bash shell, then the let builtin is what you want.
The let builtin is a clean way to perform integer arithmetic in Bash. You should read the output of help let to get a better picture of how it works.
The two lines you circled in your linked picture could be written as follows:
let guesses=10 # Set "guesses" to 10
let guesses-=1 # Decrement "guesses" by 1
Most proficient Bash hackers would write these operations in this manner. You could also use the post-decrement operator.
let guesses-- # Also decrement "guesses" by 1
This may look a little cleaner to you.
I have a homework assignment that is asking to shift a decimal number by a specified amount of digits. More clearly this bash script will take two input arguments, the first is the number(maximum 9 digits) that the shift will be performed on and the second is the number(-9 to 9) of digits to shift. Another requirement is that when a digit is shifted off the end, it should be attached to the other end of the number. One headache of a requirement is that we cannot use control statements of any kind: no loops, no if, and switch cases.
Example: 12345 3 should come out to 345000012 and 12345 -3 should be 12345000
I know that if I mod 12345 by 10^3 I get 345 and then if I divide 12345 by 10^3 I get 12 and then I can just concatenate those two variables together to get 34512. I am not quite sure if that is exactly correct but that is the closest I can get as of now. As far as the -3 shift, I know that 10^-3 is .001 and would work however when I try using 10^-3 in bash I get an error.
I am just lost at this point, any tips would be greatly appreciated.
EDIT: After several hours of bashing (pun intended) my head against this problem, I finally came up with a script that for the most part works. I would post the code right now but I fear another student hopelessly lost might stumble upon it. I will check back and post what I came up with in a week or two. I was able to do it with mods and division. Thank you all for the responses, it really helped me to open up and think about the problem from different angles.
Here's a hint:
echo ${string:0:3}
echo ${#string}
Edit (2011-02-11):
Here's my solution. I added some additional parameters with defaults.
rotate-string ()
{
local s=${1:-1} p=${2:--1} w=${3:-8} c=${4:-0} r l
printf -vr '%0*d' $w 0 # save $w zeros in $r
r=${r//0/$c}$s # change the zeros to the character in $c, append the string
r=${r: -w} # save the last $w characters of $r
l=${r: -p%w} # get the last part of $r ($p mod %w characters)
echo "$l${r::w-${#l}}" # output the Last part on the Left and the Right part which starts at the beginning and goes for ($w minus the_length_of_the_Left_part) characters
}
usage: rotate-string string positions-to-rotate width fill-character
example: rotate-string abc -4 9 =
result: ==abc====
Arguments can be omitted starting from the end and these defaults will be used:
fill-character: "0"
width: 8
positions-to-rotate: -1
string: "1"
More examples:
$ rotate-string
00000010
$ rotate-string 123 4
01230000
Fun stuff:
$ for i in {126..6}; do printf '%s\r' "$(rotate-string Dennis $i 20 .)"; sleep .05; done; printf '\n'
$ while true; do for i in {10..1} {1..10}; do printf '%s\r' "$(rotate-string : $i 10 .)"; sleep .1; done; done
$ while true; do for i in {40..2} {2..40}; do printf '%s\r' "$(rotate-string '/\' $i 40 '_')"; sleep .02; done; done
$ d=0; while true; do for i in {1..10} {10..1}; do printf '%s\r' "$(rotate-string $d $i 10 '_')"; sleep .02; done; ((d=++d%10)); done
$ d=0; while true; do for i in {1..10}; do printf '%s\r' "$(rotate-string $d $i 10 '_')"; sleep .2; ((d=++d%10)); done; done
$ shape='▁▂▃▄▅▆▇█▇▆▅▄▃▂▁'; while true; do for ((i=1; i<=COLUMNS; i++)); do printf '%s\r' "$(rotate-string "$shape" $i $COLUMNS ' ')"; done; done
In the absence of control structures, you need to use recursion, with index values as "choice selections", which is how functional programming often works.
#!/bin/sh
#
# cshift NUMBER N
cshift() {
let num=10#$1
num=`printf '%09d' $num`
lshift="${num:1:8}${num:0:1}"
rshift="${num:8:1}${num:0:8}"
next=( "cshift $lshift $(($2 + 1))" "echo $num" "cshift $rshift $(( $2 - 1 ))" )
x=$(( $2 == 0 ? 1 : $2 < 0 ? 0 : 2 ))
eval "${next[x]}"
}
cshift $1 $2
and, the testing:
$ for ((i=-9;i<=9;i++)); do cshift 12345 $i ; done
000012345
500001234
450000123
345000012
234500001
123450000
012345000
001234500
000123450
000012345
500001234
450000123
345000012
234500001
123450000
012345000
001234500
000123450
000012345
You can also do some math on the indexes and avoid the recursion, but I don't mind making the computer work harder so I don't have to. It's easy to think of how to do the shift by one in either direction, and then I use an evaluated choice that is selected by the signum of the shift value, outputting a value and stopping when the shift value is zero.