I have the following code to loop through a question session with a user:
#loop counter
COUNTER=1
# Initial question
echo ""
echo -n "Would you like a cup of tea? (y/n)" # Ask initial question
read answer # Check answer
while [ $COUNTER -le 5 ] # while counter is less than or equal to 5
do
if [ "$answer" != "${answer#[Yy]}" ]; then # if answer is 'y'
echo "" # skip a line for cleanliness
echo "Great, I'll make it now" # Satisfaction
echo ""
break # End the loop
else # if answer is anything other than 'y'
echo "" # skip a line for cleanliness
echo -n "Are you sure? (y/n)" # Ask again
read answer # Check the answer again
let COUNTER=COUNTER+1 # increment COUNTER
fi
done # finish
The code should act as follows:
Ask user if they would like tea.
If user says yes, then print out something, and end loop
If user says no, go for another 4 attempts before ending loop.
If user says yes, after saying no, print out something, and end loop.
The code works fine for points 1,2, and 4.
On point 3 - the code loops to a 5th loop before ending.
I can't seem to be able to identify the error.
Edit: Changed to COUNTER = 0, and $COUNTER -le 4. However, code doesn't implement point 4, on 4th attempt (i.e. 1 initial no, followed by 3 extra nos, before a yes).
You could simply limit counter to less than 5, instead of less than or equal to.
Use -lt instead of -le. That is [ $COUNTER -lt 5]
OR
You can say less than or equal to 4 and thus you can maintain [ $COUNTER -le 4]
Instead of having 2 lines,
echo -n "Are you sure? (y/n)" # Ask again
read answer # Check the answer again
you can shorten it to one:
read -p "Are you sure? (y/n): " answer
However, I'm not entirely sure by what you mean by point 4. Could you provide an example or a little more description?
Related
I am using a nested function to partition and making the filesystem for drives attached to a new Linux box.
I am having a strange issue trying to break out of all loops.
I am keeping track of the nested loop index and using "break n".
When the user replies "n" to the question "Do you have any additional drives to partition?" i expect to break out of all nested loops and continue with the script, but what happens is that the question gets asked again.
Can you help me figure this out?
INIT_STARTED=0
chooseDisks()
{
INIT_STARTED=$((INIT_STARTED+1))
# Choosing which drive to work on
read -p "Please type the name of the disk you want to partition: " DISK
while true; do
read -p "Are you sure you want to continue ? y (partition)/n (choose another drive) /x (continue) " ynx
case $ynx in
[Yy]* )
containsElement "$DISK"
if [ $? == 1 ]; then
initializeDisk $DISK
# remove element from found disk to prevent trying to partition it again.
delete=($DISK)
FOUNDDISKS=( "${FOUNDDISKS[#]/$delete}" )
else
echo "${red}$DISK is not a valid choice, please select a valid disk.${reset}"
chooseDisks
fi
break;;
[Nn]* )
chooseDisks
break $((INIT_STARTED));;
[Xx]* )
return
break;;
* ) echo "Please answer y or n. x to continue the script.";;
esac
done
# Any additional disks to partition?
while true; do
read -p "Do you have any additional drives to partition ? y/n " yn
case $yn in
[Yy]* )
#chooseDisks $FOUNDDISKS
chooseDisks
break $((INIT_STARTED));;
[Nn]* )
return
break $((INIT_STARTED));;
* ) echo "Please answer y or n";;
esac
done
}
I expect this:
break $((INIT_STARTED));;
to end the nth loop and exiting the function.
Don't play with nested logic break, just use some variable like $userStop and instead of while true; do put
userStop = false
while[!${userStop}]
do
#...
# replace break $((INIT_STARTED));; by
# userStop = true
I ended up changing the code to avoid breaking within a loop.
Thanks guys for directing me the right way.
David
i expect to break out of all nested loops and continue with the script
You can run the function in a subshell and use exit.
chooseDisks()
{
if [ "$1" -eq 0 ]; then
echo "The user entered it all!"
exit 0
fi
echo "The user is still entering... $1"
( chooseDisks $(($1 - 1)) )
}
# Imagine the user 5 times enters something
( chooseDisks 5 )
But the best would be to refactor your code to just have a big while true; do loop in the beginning. There is no need to make this function recursive.
As the title says, I'm trying to make a program that has a user input a number 1-7, then displays the appropriate command for each number.
The problem I'm having is finding a good way to set each number to a command.
At first, I thought about doing something like this.
OSI=$(uname -a)
echo $OSI
But the problem with that is actually implementing it into a loop. Let's say the user is prompted like so:
"Enter a number:"
The user enters the number 1, and number 1 is the OSI. Well if a user picks the number 2, it needs to display a different command and so forth.
This is a little bit too complicated for a beginner like myself. I've read through forums and different posts, but I cannot figure out the right commands to make this happen.
I tried doing something like this and it failed miserably:
#!/bin/bash
read -p "Enter a number:" n1 n2 n3 n4 n5 n6 n7
if n1=1; then
uname - a
else n2=2; "different command"
fi
I realize I'm completely garbage at bash. I'm not asking for anyone to solve this, just give me some pointers in a way that makes sense to me.
Thanks.
Give this tested version a try:
#!/bin/bash --
printf "menu items:\n 1) uname -a\n 2) date\n q) exit\n"
read -p "Enter your choice: " response
if [ -z "$response" ] ; then
printf "Choice invalid\n"
exit 1
fi
if [ "$response" = q ] ; then
exit 0
fi
if [ "$response" = 1 ] ; then
uname -a
elif [ "$response" = 2 ] ; then
date
else
printf "Choice invalid\n"
fi
As written by #EdMorton case is a better option.
This is a shortened-version of a script for reading 8mm tapes from a EXB-8500 with an autoloader (only 10 tapes at a time maximum) attached. It dd's in tape data (straight binary) and saves it to files that are named after the tape's 4-digit number (exmaple D1002.dat) in both our main storage and our backup. During this time it's logging info and displaying its status in the terminal so we can see how far along it is.
#!/bin/bash
echo "Please enter number of tapes: [int]"
read i
j=1
until [ $i -lt $j ]
do
echo "What is the number of tape $j ?"
read Tape_$j
(( j += 1 ))
done
echo "Load tapes into the tower and press return when the drive is ready"
read a
j=1
until [ $i -lt $j ]
do
k="Tape_$j"
echo "tower1 $j D$(($k)) `date` Begin"
BEG=$j" "D$(($k))" "`date`" ""Begin"
echo "tower1 $j D$(($k)) `date` End"
END=$j" "D$(($k))" "`date`" ""End"
echo "$BEG $END"
echo "$BEG $END"
sleep 2
(( j += 1 ))
done
echo "tower1 done"
Everything was hunky-dory until we got under 1000 (startig at 0999). Error code was ./tower1: 0999: Value too great for base (error token is "0999"). Now I already realize that this is because the script is forcing octal values when I type in the leading 0, and I know I should insert a 10# somewhere in the script, but the question is: Where?
Also is there a way for me to just define Tape_$j as a string? I feel like that would clear up a lot of these problems
To get the error, run the script, define however many tapes you want (at least one, lol), and insert a leading 0 into the name of the tape
EXAMPLE:
./test
Please enter number of tapes: [int]
1
What is the number of tape 1?
0999
./test: 0999: Value too great for base (error token is "0999")
You don't want to use $k as a number, but as a string. You used the numeric expression to evaluate a variable value as a variable name. That's very bad practice.
Fortunately, you can use variable indirection in bash to achieve your goal. No numbers involved, no error thrown.
echo "tower1 $j ${!k} `date` Begin"
BEG=$j" "D${!k}" "`date`" ""Begin"
And similarly in other places.
I know how to check the status of the previously executed command using $?, and we can make that status using exit command. But for the loops in bash are always returning a status 0 and is there any way I can break the loop with some status.
#!/bin/bash
while true
do
if [ -f "/test" ] ; then
break ### Here I would like to exit with some status
fi
done
echo $? ## Here I want to check the status.
The status of the loop is the status of the last command that executes. You can use break to break out of the loop, but if the break is successful, then the status of the loop will be 0. However, you can use a subshell and exit instead of breaking. In other words:
for i in foo bar; do echo $i; false; break; done; echo $? # The loop succeeds
( for i in foo bar; do echo $i; false; exit; done ); echo $? # The loop fails
You could also put the loop in a function and return a value from it. eg:
in() { local c="$1"; shift; for i; do test "$i" = "$c" && return 0; done; return 1; }
Something like this?
while true; do
case $RANDOM in *0) exit 27 ;; esac
done
Or like this?
rc=0
for file in *; do
grep fnord "$file" || rc=$?
done
exit $rc
The real question is to decide whether the exit code of the loop should be success or failure if one iteration fails. There are scenarios where one make more sense than the other, and other where it's not at all clear cut.
The bash manual says:
while list-1; do list-2; done
until list-1; do list-2; done
[..]The exit status of the while and until commands is the exit status
of the last command executed in list-2, or zero if none was executed.[..]
The last command that is executed inside the loop is break. And the exit value of break is 0 (see: help break).
This is why your program keeps exiting with 0.
The break builtin for bash does allow you to accomplish what you are doing, just break with a negative value and the status returned by $? will be 1:
while true
do
if [ -f "./test" ] ; then
break -1
fi
done
echo $? ## You'll get 1 here..
Note, this is documented in the help for the break builtin:
help break
break: break [n] Exit for, while, or until loops.
Exit a FOR, WHILE or UNTIL loop. If N is specified, break N enclosing
loops.
Exit Status: The exit status is 0 unless N is not greater than or
equal to 1.
You can break out of n number of loops or send a negative value for breaking with a non zero return, ie, 1
I agree with #hagello as one option doing a sleep and changing the loop:
#!/bin/bash
timeout=120
waittime=0
sleepinterval=3
until [[ -f "./test" || ($waittime -eq $timeout) ]]
do
$(sleep $sleepinterval)
waittime=$((waittime + sleepinterval))
echo "waittime is $waittime"
done
if [ $waittime -lt $sleepinterval ]; then
echo "file already exists"
elif [ $waittime -lt $timeout ]; then
echo "waited between $((waittime-3)) and $waittime seconds for this to finish..."
else
echo "operation timed out..."
fi
I think what you should be asking is: How can I wait until a file or a directory (/test) gets created by another process?
What you are doing up to now is polling with full power. Your loop will allocate up to 100% of the processing power of one core. The keyword is "polling", which is ethically wrong by the standards of computer scientists.
There are two remedies:
insert a sleep statement in your loop; advantage: very simple; disadvantage: the delay will be an arbitrary trade-off between CPU load and responsiveness. ("Arbitrary" is ethically wrong, too).
use a notification mechanism like inotify (see: man inotify); advantage: no CPU load, great responsiveness, no delays, no arbitrary constants in your code; disadvantage: inotify is a kernel API – you need some code to access it: inotify-tools or some C/Perl/Python code. Have a look at inotify and bash!
I would like to submit an alternative solution which is simpler and I think more elegant:
(while true
do
if [ -f "test" ] ; then
break
fi
done
Of course of this is part of a script then you could user return 1 instead of exit 1
exit 1
)
echo "Exit status is: $?"
Git 2.27 (Q2 2020), offers a good illustration of the exit status in a loop, here within the context of aborting a failing test early (e.g. by exiting a loop), which is to say "return 1".
See commit 7cc112d (27 Mar 2020) by Junio C Hamano (gitster).
(Merged by Junio C Hamano -- gitster -- in commit b07c721, 28 Apr 2020)
t/README: suggest how to leave test early with failure
Helped-by: Jeff King
Over time, we added the support to our test framework to make it easy to leave a test early with failure, but it was not clearly documented in t/README to help developers writing new tests.
The documentation now includes:
Be careful when you loop
You may need to verify multiple things in a loop, but the following does not work correctly:
test_expect_success 'test three things' '
for i in one two three
do
test_something "$i"
done &&
test_something_else
'
Because the status of the loop itself is the exit status of the test_something in the last round, the loop does not fail when "test_something" for "one" or "two" fails.
This is not what you want.
Instead, you can break out of the loop immediately when you see a failure.
Because all test_expect_* snippets are executed inside a function, "return 1" can be used to fail the test immediately upon a failure:
test_expect_success 'test three things' '
for i in one two three
do
test_something "$i" || return 1
done &&
test_something_else
'
Note that we still &&-chain the loop to propagate failures from earlier commands.
Use artificial exit code 🙂
Before breaking the loop set a variable then check the variable as the status code of the loop, like this:
while true; do
if [ -f "/test" ] ; then
{broken=1 && break; };
fi
done
echo $broken #check the status with [[ -n $broken ]]
I'm trying to do something like these:
while[$read!="0"];
In this program
#!/bin/sh
i=0
cont=0
while[$read!="0"]; do
read number
cont=`expr $cont + $number`
i++
done
cont=`expr $cont / $i -1`
echo
I want to stop suming the entries when I give it a 0
tnx
The variable you're reading into is $number, so reference that rather than $read in your loop.
Whitespace is significant, so make sure to include spaces before, after, and between all of the items in your loop. (Confusingly, you must not include spaces in an assignment statement like i=0. i = 0 is wrong.)
For good measure, use double quotes around the variable. That's a good practice so that if the user hits enter without typing a number your script doesn't barf on the empty string.
while [ "$number" != "0" ]; do
Also, your i++ isn't right. There are various ways to write that, the simplest being:
let i++
In this, an infinite loop would be appropriate, since you know the condition on which you want to (hint) break out of the loop. The way to get an infinite loop in sh is: while true; do ...; done
Also, read has a -p option that lets you have a prompt (so you know what you're being asked to enter): read -p "Enter a number: " number