Is it possible to break a for loop in bash? - bash

I have the below scenario.
I'm iterating through array elements in bash and comparing the values.
#!/bin/bash
for (( i = 0 ; i <=m-1 ; i++));
do for (( j = 0 ; j <=n-1 ; j++));
do
if [ "${sourcedisktype[i]}" == "SSD" -a "${targetdisktype[j]}" == "BSAS" ];
then echo "volume cannot be moved1";
elif [ "${sourcedisktype[i]}" == "SSD" -a "${targetdisktype[j]}" == "SAS" ];
then echo "volume cannot be moved2";
elif [ "${sourcedisktype[i]}" == "SAS" -a "${targetdisktype[j]}" == "BSAS" ];
then echo "volume cannot be moved3";else
echo "Volume can be moved for this combination of disk type";done;done;
I don't want to execute the else part even if one elif fails. Is there any way to do it. Is it possible to use break statement after elif?

Of course you can break out of your loops in bash, you can actually even break out of N nested loops if you use the statement break N where N is an integer >=1.
For more details please have a look at the documentation:
http://tldp.org/LDP/abs/html/loopcontrol.html
You can actually also use continue N but it can be quite tricky and hard for others to understand, so I would not recommend to use it.
PS: do not forget to indent your code properly in order for others to understand it quickly and even for yourself, I can affirm that it will be a pain if you try to reread your code in a couple of months/years. You will regret not having indented/commented it properly.

Related

Dynamic beginning of for loop

Create bash for loop with two alternative starts. I would like to either iterate through all numbers in a range or all elements in an array depending on a condition. The following unorthodox code shows the example:
#!/bin/bash
FROMVAL=1
TOVAL=5
VALARR=(1 3 5)
ISCONSEQ=1
if (( ISCONSEQ == 1 )); then
for (( counter=$FROMVAL; counter<=$TOVAL; counter++ ))
else
for counter in "${VALARR[#]}"
fi
do
echo $counter
done
Obviously, this does not work nor is it pretty. Is there a bash way to do this or should I create two for loops and execute only one depending on the content of ISCONSEQ? Or should I use a for...in loop for both cases and simply assign the values of my sequence to the array?
This code does work but relies only on the array:
if (( ISCONSEQ == 1 )); then
VALARR=($(seq $FROMVAL 1 $TOVAL))
fi
for counter in "${VALARR[#]}"
do
echo $counter
done
This is a good use case for a function. Create function for the echo and call it from each for loop. It would look like this.
#!/bin/bash
FROMVAL=1
TOVAL=5
VALARR=(1 3 5)
ISCONSEQ=1
function dostuff() {
echo $*
}
if (( ISCONSEQ == 1 )); then
for (( counter=$FROMVAL; counter<=$TOVAL; counter++ ))
do
dostuff $counter
done
else
for counter in "${VALARR[#]}"
do
dostuff $counter
done
fi
First, I agree with the previous comments that 2 for loops is not as offensive as it may look.
Second, you can't conditionally kick off a for loop in the manner of your example. But, you can manipulate your array with a conditional.
So to satisfy your question, here would be a simple solution:
FROMVAL=1
TOVAL=5
VALARR=(1 3 5)
ISCONSEQ=0
if (( ISCONSEQ != 1))
then
VALARR=($(seq $FROMVAL 1 $TOVAL))
fi
for counter in "${VALARR[#]}"
do
echo "$counter"
done

Struggling with while loop with two conditions and a for loop

I'm trying to learn how to use arrays in bash. I'm writing a script that asks the user for three numbers and figures out what the biggest number is. I want the script to force the user to enter only numeric values. Furthermore, I want the three numbers should be different. I'm using an array to store user input. This is what I have so far:
## declare variables
order=("first" "second" "third")
answers=()
RED='\033[0;31m'
NC='\033[0m' # No Color
numbersonly() {
if [[ ! $1 =~ ^[0-9]+$ ]]; then
echo "${RED}$1 is not a valid number.${NC}"
else
answers+=("$input")
break
fi
}
inarray(){
for e in ${answers[#]}; do
if [[ $1 == $e ]]; then
echo "${RED}Warning.${NC}"
fi
done
}
readnumber(){
for i in {1..3}; do
j=$(awk "BEGIN { print $i-1 }")
while read -p "Enter the ${order[$j]} number: " input ; do
inarray $input
numbersonly $input
done
done
}
displayanswers(){
echo "Your numbers are: ${answers[#]}"
}
biggestnumber(){
if (( ${answers[0]} >= ${answers[1]} )); then
biggest=${answers[0]}
else
biggest=${answers[1]}
fi
if (( $biggest <= ${answers[2]} )); then
biggest=${answers[2]}
fi
echo "The biggest number is: $biggest"
}
main(){
readnumber
displayanswers
biggestnumber
}
main
Right now, I can get the script to display a warning when the user enters a number that was previously entered, but I can't seem to find the proper syntax to stay in the while loop if the user input has already been entered. Thoughts?
I found a way around it. My problem was twofold: 1) I didn't realize that if you have a for loop nested in a while loop, you'll need two break statements to exit the while loop; 2) having two functions within the same while loop made it hard to control what was happening. By merging inarray() and numbersonly() into a new function, I solved the double conditional issue. The new function looks like this:
testing(){
for item in ${answers[*]}
do
test "$item" == "$1" && { inlist="yes"; break; } || inlist="no"
done
if [[ $inlist == "yes" ]]; then
echo "${RED}$1 is already in list.${NC}"
else
if [[ ! $1 =~ ^[0-9]+$ ]]; then
echo "${RED}$1 is not a valid number.${NC}"
else
answers+=("$input")
break
fi
fi
}
Without much study here is what leapt off the screen to me follows. Beware I haven't actually tested it... debugging is an exercise left to the student.
Recommend using newer "function" definitions as you can declare local variables. () definitions do not allow localized variables.
function inarray
{
local e; #don't muck up any variable e in caller
...
}
To calculate values avoid extra awk and use j=$(( i - 1 ));
biggestnumber should likely use a loop.
Overall comment:
nummax=3; #maximum value defined in just one place
# loop this way... showing optional {} trick for marking larger loops
for (( n = 0; n < nummax; ++n )); do
{
nx=$(( 1 + n )); #1 based index
} done;
Hint: should stop input loop once all input present. Could also add:
if [ "" == "${input:-}" ]; then break;
for (( a = 0; a < ${#answer[*]}; ++a )); do
Note the extensive use of double quotes to avoid syntax errors if the variable value is empty or contains many shell metacharacters, like spaces. I can't tell you how many bug reports I've fixed by adding the quotes to existing code.
[[ ... ]] expressions use file name tests, not regular expressions. The closest you can get to [[ ! $1 =~ ^[0-9]+$ ]]; is using [[ "$1" != [0-9]* ]] && [[ "$1" != *[^0-9]* ]].
But I suspect ! expr >/dev/null "$i" : '[0-9][0-9]*$'; is more what you want as "expr" does use regular expressions. Don't enclose in []s. Used [0-9][0-9]* rather than [0-9]+ as "+" has given me mixed successes across all dialects of UNIX regular expressions.

Shell program to find the Factorial of a Number is NOT working

I am relatively new to Shell Programming. I am trying to find the Factorial of a number passed as an Argument of my program. The script so far is:
#!/bin/bash
number=$1
factorial=1
i=$number
while [ $i != 1 && $i != 0 ]
do
factorial=`expr $factorial \* $i`
i=` expr $i – 1 `
done
echo “Factorial of $number is $factorial”
But as I am executing the program :
sh fact.sh 5
It says:
Fact.sh: 10: [: missing ]
“Factorial of 5 is 1”
What am I doing wrong here?
When you use [ in your shell scripts you are actually using the test command and you are not really going by its rules.
Try doing a man test to find out the syntax of the test command.
You have other problems in your script but overcoming these is all part of learning and I hope my little hint helps you on your path of learning.
You have some answers to the immediate problem.
A quick rewrite to demonstrate some bash features
#!/bin/bash
number=$1
declare -i factorial=1
declare -i i
for ((i=number; i != 1 && $i != 0; i--)); do
(( factorial = factorial * i ))
done
echo “Factorial of $number is $factorial”
Notes
use ((...)) for arithmetic evaluation
don't need to use $ to refer to variables within an arithmetic expression
don't use fancy quotes (”), they have no special meaning to the shell.
I'm using declare to inform the shell that these are integers
(I don't know how much this will improve performance)
using a for loop instead of a while loop for no particular reason.
Expression while [ $i != 1 && $i != 0 ] won't work.
Use while [ $i != 1] && [ $i != 0 ] instead or, if you're using BASH, while [[ $i != 1 && $i != 0 ]]

Shell script for word in $sentece How to change the value of word?

I need some help from this awesome community.
I'm trying to write a script that loops through each word of a sentence stored in a variable (for example SENTENCE).
Example:
for WORD in $SENTENCE
do
echo do something
done
The problem that I'm facing is that I need to change the value of WORD to restart the loop if a certain condition is true inside the loop.
Example:
for WORD in $SENTENCE
do
echo do something
if [[ $SOMETHING_HAPPENED == TRUE ]]; then
WORD=$FIRST_WORD_IN_SENTENCE
fi
done
Basically, I need to restart the loop if certain conditions are met (SOMETHING_HAPPENED), but I don't know how to do this properly.
If this was a normal C loop I would do it like this:
for (i=0;i<10;i++){
do_something();
if (SOMETHING_HAPPENED == TRUE){
i=0;
}
}
How do I do this in shell script?
Thank you.
You could use a loop around your for loop that executes the for loop until the something doesn't happen:
repeat=yes
while [ "$repeat" = yes ]; do
repeat=no
for WORD in $SENTENCE; do
# do something
if [[ $SOMETHING_HAPPENED == TRUE ]]; then
repeat=yes
break
fi
done
done
while [[ 1 ]]; do # infinite loop
for word in $sentence; do
…
if [[ $condition ]]; then
continue 2 # go to the next iteration of the *outer* loop
fi
done
break # escape the outer loop
done
To restart the loop you can use BASH arrays and run your loop like this:
arr=( $sentence )
for ((i=0; i<${#arr[#]}; i++)); do
word="${arr[$i]}"
# evaluate condition
if [[ "$SOMETHING_HAPPENED" == TRUE ]]; then
i=0
continue
fi
echo "$word"
done

Loop first run continue in shell script

How to break out of infinite while loop in a shell script?
I want to implement the following PHP code in shell script(s):
$i=1;
while( 1 ) {
if ( $i == 1 ) continue;
if ( $i > 9 ) break;
$i++;
}
break works in shell scripts as well, but it's better to check the condition in the while clause than inside the loop, as Zsolt suggested. Assuming you've got some more complicated logic in the loop before checking the condition (that is, what you really want is a do..while loop) you can do the following:
i=1
while true
do
if [ "$i" -eq 1 ]
then
continue
fi
# Other stuff which might even modify $i
if [ $i -gt 9 ]
then
let i+=1
break
fi
done
If you really just want to repeat something $count times, there's a much easier way:
for index in $(seq 1 $count)
do
# Stuff
done
i=1
while [ $i -gt 9 ] ; do
# do something here
i=$(($i+1))
done
Is one of the ways you can do it.
HTH

Resources