BASH Shell Infinite loop with IF ELSE Statment - bash

Ok I know I've asked a similar question, I understand how to do an infinate loop:
while [ 1 ]
do
foo
bar
then
sleep 10
done
But if I want to run some (quite a few) IF ELSE Statements in this loop how would I get the script to carry on looping once they had completed how would I go about this?

while :; do
if cond1; then
whatever
elif cond2; then
something else
else
break
fi
done
You do infinite loop using while true or using true's shorter alias :. [ 1 ] is needlessly complicated and is not what you think it is ([ 0 ] is also true!). Remember, the condition in if and while is arbitrary command whose exit status (zero = true, nonzero = false) is used as the condition value and [ is just alias for special test command (both built-in in most shells, but they don't have to be).
Any shell construct is allowed between do/done including conditionals, more loops and cases.
Use break to terminate innermost loop from inside (just like most other languages).

An if in bash is just: if [ some test ]; then some_command; fi:
while [ 1 ]
do
    if [ some test ]
then
some command
else
other cmd
fi
sleep 10
done
Bash Scripting Guide

Correction to chown case added brake to finish the loop
Corrected
while [ 1 ]
do
if [ some test ]
then
some command
brake
else
other cmd
fi
sleep 10
done

Related

What does if <command>; [ $? = 4 ]; then <...> do?

I stumbled upon this snippet of code and asked myself about it's significance:
if `getopt -T >/dev/null 2>&1` ; [ $? = 4 ]
then
#do a thing
else
#do the other thing
fi
What irritates me is the [ $? = 4 ] part. It looks like a test for the exit code of the last command which would make sense since "#do a thing" and "#do the other thing" relate to how to deal with different versions of getopt, but is it even evaluated? If so, how? I have never seen such a statement after the if keyword.
Thanks!
Let's review the output of help if:
if: if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]... [ else COMMANDS; ] fi
The `if COMMANDS' list is executed. If its exit status is zero, then the
`then COMMANDS' list is executed. Otherwise, each `elif COMMANDS' list is
executed in turn, and if its exit status is zero, the corresponding
`then COMMANDS' list is executed and the if command completes. Otherwise,
the `else COMMANDS' list is executed, if present. The exit status of the
entire construct is the exit status of the last command executed, or zero
if no condition tested true.
In light of the above, consider the following:
if COMMANDS; then ... notably accepts COMMANDS -- a list, which can consist of multiple commands combined by separators.
foo; bar is a command list which runs both foo and bar; when it's complete, the exit status of the compound command is that of bar.
[ $? = 4 ] tests whether the immediately preceding program's exit status is exactly 4.
getopt -T >/dev/null 2>&1; [ $? = 4 ] thus tests whether getopt -T exits with a status of precisely 4.
Thus, your code runs the #do a thing block if getopt -T fails with exit status 4, and #do the other thing otherwise.

How do I update a "global" variable inside a while loop?

So I have this If statement that checks what the exit code of the script is:
if [ $? -eq 0 ]; then
wasSuccessful=true;
fi
In other languages, I would do something like:
while wasSuccessful == false
And then the loop would keep running until the exit code was true. (I plan to implement a loop counter as well for too many failed attempts, but that's a different problem to solve)
I think I need a here-string, but I'm not exactly sure how that would look. Here's a rough outline of my code right now, in Bash:
wasSuccessful=false
while [ "$wasSuccessful" = "false" ]
do
#Bunch of code here, then the check for exit code
if [ $? -eq 0 ]; then
wasSuccessful=true;
fi
done
Any suggestions on how to do something like this would be much appreciated :)
The question is how to exit while loop when the latest command execute successfully inside while loop?
You don't need a global variable to check. Just check the status of previous command and break it if succeed.
while true
do
# a bunch of code here
[ $? -eq 0 ] && break
done

Run a command until return code greater than x

In a bash script I need to run a command until the return is bigger than 127.
For now, i'm running the command in an subshell, with the outputs to /dev/null and printing the return code to be able to compare it with a test:
while [ `command >/dev/null 2>&1; echo $?` -lt 128 ]; do sleep 1; done
Is there an eleganter solution for it? It looks like the only use case natively supported is to test if RC == 0 or not.
I think it would be more elegant to start an infinite loop and break when the exit code is greater than 127.
while true; do
command # redirection is now optional
[ $? -gt 127 ] && break
sleep 1
done

bash one-line conditional fails when using set -e

I started using set -e in my bash scripts,
and discovered that short form of conditional expression breaks the script execution.
For example the following line should check that $var is not empty:
[ -z "$var" ] && die "result is empty"
But causes silent exit from script when $var has non-zero length.
I used this form of conditional expression in many places...
What should I do to make it run correctly? Rewrite everything with "if" construction (which would be ugly)? Or abandon "set -e"?
Edit: Everybody is asking for the code. Here is full [non]working example:
#!/bin/bash
set -e
function check_me()
{
ws="smth"
[ -z "$ws" ] && echo " fail" && exit 1
}
echo "checking wrong thing"
check_me
echo "check finished"
I'd expect it to print both echoes before and after function call.
But it silently fails in the check_me function. Output is:
checking wrong thing
Use
[ -n "$var" ] || die "result is empty"
This way, the return value of the entire statement is true if $var is non-empty, so the ERR trap is not triggered.
I'm afraid you will have to rewrite everything so no false statements occur.
The definition of set -e is clear:
-e Exit immediately if a simple command (see SHELL GRAMMAR above) exits with a non-zero status. The shell does not exit if the command that fails is part of the command list immediately following a while or until keyword, part of the test in an if statement, part of a && or || list, or if the command's return value is being inverted via !. A trap on ERR, if set, is executed before the shell exits.
You are using the "optimization" system of Bash: because a false statement will cause an AND (&&) statement never to be true, bash knows it doesn't have to execute the second part of the line. However, this is a clever "abuse" of the system, not intended behaviour and therefore incompatible with set -e. You will have to rewrite everything so it is using proper ifs.
You should write your script such that no command ever exits with non-zero status.
In your command [ -z "$var" ] can be true, in which case you call die, or false in which case -e does it's thing.
Either write it with if, as you say, or use something like this:
[ -z "$var" ] && die "result is empty" || true
I'd recommend if though.
What the bash help isn't very clear on is that only the last statement in an && or || chain is subject to causing an exit under set -e. foo && bar will exit if bar returns false, but not if foo returns false.
So your script should work... but it doesn't. Why?
It's not because of the failed -z test. It's because that failure makes the function return a non-zero status:
#!/bin/bash
set -e
function check_me()
{
ws="smth"
[ -z "$ws" ] && echo " fail" && exit 1
# The line above fails, setting $? to 1
# The function now returns, returning 1!
}
echo "checking wrong thing"
check_me # function returns 1, causing exit here
echo "check finished"
So there are multiple ways to fix this. You could add ||true to the conditional inside the function, or to the line that calls check_me. But as others have pointed out, using ||true has its own problems.
In this specific scenario, where the desired postcondition of check_me is "either this thing is valid or the script has exited", the straightforward thing to do is to write it like that, i.e. [[ -n "$ws" ]] || die "whatever".
But using && conditions will actually work fine with set -e in general, as long as you don't use such a conditional as the last thing in a function. You need to add an explicit true or return 0 or even : as a statement following such a conditional, unless you intend the function to return false when the condition fails.

Return an exit code without closing shell

I'd like to return an exit code from a BASH script that is called within another script, but could also be called directly. It roughly looks like this:
#!/bin/bash
dq2-get $1
if [ $? -ne 0 ]; then
echo "ERROR: ..."
# EXIT HERE
fi
# extract, do some stuff
# ...
Now in the line EXIT HERE the script should exit and return exit code 1. The problem is that
I cannot use return, because when I forget to source the script instead of calling it, return will not exit, and the rest of the script will be executed and mess things up.
I cannot use exit, because this closes the shell.
I cannot use the nice trick kill -SIGINT $$, because this doesn't allow to return an exit code.
Is there any viable alternative that I have overlooked?
The answer to the question title (not in the body as other answers have addressed) is:
Return an exit code without closing shell
(exit 33)
If you need to have -e active and still avoid exiting the shell with a non-zero exit code, then do:
(exit 33) && true
The true command is never executed but is used to build a compound command that is not exited by the -e shell flag.
That sets the exit code without exiting the shell (nor a sourced script).
For the more complex question of exiting (with an specific exit code) either if executed or sourced:
#!/bin/bash
[ "$BASH_SOURCE" == "$0" ] &&
echo "This file is meant to be sourced, not executed" &&
exit 30
return 88
Will set an exit code of 30 (with an error message) if executed.
And an exit code of 88 if sourced.
Will exit both the execution or the sourcing without affecting the calling shell.
Use this instead of exit or return:
[ $PS1 ] && return || exit;
Works whether sourced or not.
You can use x"${BASH_SOURCE[0]}" == x"$0" to test if the script was sourced or called (false if sourced, true if called) and return or exit accordingly.
Another option is to use a function and put the return values in that and then simply either source the script (source processStatus.sh) or call the script (./processStatus.sh) . For example consider the processStatus.sh script that needs to return a value to the stopProcess.sh script but also needs to be called separately from say the command line without using source (only relevant parts included)
Eg:
check_process ()
{
if [ $1 -eq "50" ]
then
return 1
else
return 0
fi
}
and
source processStatus.sh $1
RET_VALUE=$?
if [ $RET_VALUE -ne "0" ]
then
exit 0
fi
You can use return if you use set -e in the beginning of the script.
If you just want to check if the function returned no errors, I'd rather suggest rewriting your code like this:
#!/bin/bash
set -e # exit program if encountered errors
dq2-get ()
{
# define the function here
# ...
if [ $1 -eq 0 ]
then
return 0
else
return 255
# Note that nothing will execute from this point on,
# because `return` terminates the function.
}
# ...
# lots of code ...
# ...
# Now, the test:
# This won't exit the program.
if $(dq2-get $1); then
echo "No errors, everything's fine"
else
echo "ERROR: ..."
fi
# These commands execute anyway, no matter what
# `dq2-get $1` returns (i.e. {0..255}).
# extract, do some stuff
# ...
Now, the code above won't leave the program if the function dq2-get $1 returns errors. But, implementing the function all by itself will exit the program because of the set -e. The code below describes this situation:
# The function below will stop the program and exit
# if it returns anything other than `0`
# since `set -e` means stop if encountered any errors.
$(dq2-get $1)
# These commands execute ONLY if `dq2-get $1` returns `0`
# extract, do some stuff
# ...
Thanks for the question, my case was to source a file for some setup, but end the script and skip the setup actions if certain conditions were not met.
I had hit the issue of an attempt to use exit() actually causing the closing of my terminal, and found myself here :D
After reviewing the options for the specific solution i just went with something like the below, I also think Deepaks answer is worth reviewing if this approach works in your case.
if [ -z "$REQUIRED_VAR" ]; then
echo "please check/set \$REQUIRED_VAR ..."
echo "skipping logic"
else
echo "starting logic"
doStuff()
echo "completed logic"
fi

Resources