Bash while loop, how to read input until a condition is false - bash

I keep getting a run time error. I'm running this in a terminal on OSX. The error is,
test.sh: line 15: while[!false]: command not found
test.sh: line 16: syntax error near unexpected token `do'
test.sh: line 16: `do'
I just can't figure where I've got wrong syntactically as I'm new to writing bash scripts.
ipa build &
TASK_PID=$!
sleep 5
kill $TASK_PID
finished=false
declare -a schemes
echo "*****************************************************************************************"
echo "| View the list of available build configs above."
echo "| Enter the name of the build you want,one at a time."
echo "| Type \"done\" to finish entering scheme names"
echo "*****************************************************************************************"
while[!${finished}]
do
read input
if[$input == "done"]
then
finished=true
else
schemes=("${schemes[#]}" $input)
echo ${schemes[0]}
fi
done
echo "Do you want a verbose build? (y/n)"
read verbose
echo "Building your selected schemes....."
ipa build -s ${schemes[0]}

true and false are not boolean keywords in bash; they are simply strings (and the names of commands; more on that in a moment). Even if you fix your syntax by supplying whitespace where necessary:
while ! [ "${finished}" ]; do
...
done
this loop will never run. Why? Whether finished has the value true or false, it is simply a non-empty string. This code will run the [ command (yes, it's a command, not syntax) and succeed because its argument is a non-empty string. The ! negates it, so that the condition for the while loop then always fails.
The most direct fix is to explicitly compare $finished to the string "true".
while [ "$finished" != "true" ]; do
...
done
I mentioned that true and false are also commands: true always succeeds, and false always fails. Usually, you do not want to do what I am about to suggest, but here it's OK because true and false are about as simple a pair of commands as you can imagine.
finished=false
while ! $finished; do
...
# At some point
finished=true
done
Here, we are letting $finished expand to the name of a command, which then executes and has its exit status negated by the !. As long as finished=false, the negated exit status is always 0 and the while loop will continue to run. Once you change the value of finished, the negated exit status will be 1 and the loop will exit.

Give space around brackets in test conditions
while [ ! ${finished} ]
&
if [ $input = "done" ]

Why not try something like this:
#!/bin/bash
list='Foo Bar Baz Quux Xyzzy QUIT'
select item in $list; do
case $item in
QUIT)
break
;;
*)
echo "You picked '$item'!"
;;
esac
done

Related

bash shell script - getting a string from the console and store it into a variable

I am performing some actions which produce a string from the console for example I am performing the following action:
./eqagent add-target target-type=kafka events-servers=localhost:9999
and getting this message :
Equalum agent has returned the following error: The agent target
already exists
I want to catch it and store it into a variable and then use it as a condition using an if statement.
your question looks to me like a typical X-Y problem: your issue is that you want to act when your command failed, and when that happens, it will set the return value to a non zero value. So what you actually want is to use the $? variable. Here's a generalised example of your use case:
$ ./command
Argh... Failure!
$ echo $?
1
so you could do:
if [ $? -eq 1 ]; then
echo "It failed :("
fi
it's likely you have several return values to descriminate errors, and thus you can check for a value equal to 2, 3 or 255 instead.
but your shell allows you to do that in a more concise way:
# prints 'it failed :(' only when command's result ($?) is non-null
./command || echo "it failed :("
so for your example you should be able to accomplish it using:
./eqagent add-target target-type=kafka events-servers=localhost:9999 || echo "it failed :("
if your command really only returns the error state using a string (not nice), you can check the exact string using:
output=$(./eqagent add-target target-type=kafka events-servers=localhost:9999)
if [ "$output" == "Equalum agent has returned the following error: The agent target already exists" ]; then
echo "it failed :("
fi
finally you can check for substring inclusion (in case this is not the only output of your program):
error_msg="The agent target already exists"
if [ -z "${output##*$error_msg*}" ] ;then
echo "it failed :("
fi
the above syntax works with most shells (bash, ksh, dash…).
In case you don't get the output you expect in the $output variable, which is likely if the program prints error on stderr instead of stdout, you can join both outputs using the following trick:
output=$(./eqagent add-target target-type=kafka events-servers=localhost:9999 2>&1)
finally in real use case, you might want a mix of both:
error_msg="The agent target already exists"
output=$(./eqagent add-target target-type=kafka events-servers=localhost:9999)
if [ $? -ne 0 ]; then
echo -n "it failed "
if [ -z "${output##*$error_msg*}" ] ;then
echo "; because target already exists. :-s"
else
echo ":-("
fi
fi
Try this :
message=$(./eqagent add-target target-type=kafka events-servers=localhost:9999 2>&1)
The varname=$(some command) syntax is called command substitution, and expands to the string produced by the command contained inside the parentheses.
The 2>&1 part is a redirection that takes the output of file descriptor 2 (generally referred to "standard error") and causes it to go to the same output as file descriptor 1 (generally referred to as "standard out"). This makes sure your process substitution will collect the output of both, as a command substitution only capture standard out, not standard error.
If you simply want to test for success of failure, you can use the return code of the command, like this :
if
./eqagent add-target target-type=kafka events-servers=localhost:9999 2>&1
then
# Success!
else
# Failure...
fi
The return code is also available in the special shell variable $? if called right after the command has executed, and will vy convention be zero for success, and a non-zero value in case of failure.
couldn't you do something like this:
variable=`your-command`
Then do whatever you want using $variable
var="$((./eqagent add-target target-type=kafka events-servers=localhost:9999)2>&1)" # We must also make sure that stderr goes into stdin so we can capture the error message. That is what 2>&1 does
if [ $? -ne 0 ];then # Eg, it failed
echo "the output is stored in '$var'."
echo "$var" > error_log.txt
fi
That should do the trick
Edit: Forgot to add stderr redirection...

bash: error handling and functions

I am trying to call a function in a loop and gracefully handle and continue when it throws.
If I omit the || handle_error it just stops the entire script as one would expect.
If I leave || handle_error there it will print foo is fine after the error and will not execute handle_error at all. This is also an expected behavior, it's just how it works.
#!/bin/bash
set -e
things=(foo bar)
function do_something {
echo "param: $1"
# just throw on first loop run
# this statement is just a way to selectively throw
# not part of a real use case scenario where the command(s)
# may or may not throw
if [[ $1 == "foo" ]]; then
throw_error
fi
# this line should not be executed when $1 is "foo"
echo "$1 is fine."
}
function handle_error {
echo "$1 failed."
}
for thing in ${things[#]}; do
do_something $thing || handle_error $thing
done
echo "done"
yields
param: foo
./test.sh: line 12: throw_error: command not found
foo is fine.
param: bar
bar is fine.
done
what I would like to have is
param: foo
./test.sh: line 12: throw_error: command not found
foo failed.
param: bar
bar is fine.
done
Edit:
do_something doesn't really have to return anything. It's just an example of a function that throws, I could potentially remove it from the example source code because I will have no control over its content nor I want to, and testing each command in it for failure is not viable.
Edit:
You are not allowed to touch do_something logic. I stated this before, it's just a function containing a set of instructions that may throw an error. It may be a typo, it may be make failing in a CI environment, it may be a network error.
The solution I found is to save the function in a separate file and execute it in a sub-shell. The downside is that we lose all locals.
do-something.sh
#!/bin/bash
set -e
echo "param: $1"
if [[ $1 == "foo" ]]; then
throw_error
fi
echo "$1 is fine."
my-script.sh
#!/bin/bash
set -e
things=(foo bar)
function handle_error {
echo "$1 failed."
}
for thing in "${things[#]}"; do
./do-something.sh "$thing" || handle_error "$thing"
done
echo "done"
yields
param: foo
./do-something.sh: line 8: throw_error: command not found
foo failed.
param: bar
bar is fine.
done
If there is a more elegant way I will mark that as correct answer. Will check again in 48h.
Edit
Thanks to #PeterCordes comment and this other answer I found another solution that doesn't require to have separate files.
#!/bin/bash
set -e
things=(foo bar)
function do_something {
echo "param: $1"
if [[ $1 == "foo" ]]; then
throw_error
fi
echo "$1 is fine."
}
function handle_error {
echo "$1 failed with code: $2"
}
for thing in "${things[#]}"; do
set +e; (set -e; do_something "$thing"); error=$?; set -e
((error)) && handle_error "$thing" $error
done
echo "done"
correctly yields
param: foo
./test.sh: line 11: throw_error: command not found
foo failed with code: 127
param: bar
bar is fine.
done
#!/bin/bash
set -e
things=(foo bar)
function do_something() {
echo "param: $1"
ret_value=0
if [[ $1 == "foo" ]]; then
ret_value=1
elif [[ $1 == "fred" ]]; then
ret_value=2
fi
echo "$1 is fine."
return $ret_value
}
function handle_error() {
echo "$1 failed."
}
for thing in ${things[#]}; do
do_something $thing || handle_error $thing
done
echo "done"
See my comment above for the explanation. You can't test for a return value without creating a return value, which should be somewhat obvious. And || tests for a return value, basically, one greater than 0. Like && tests for 0. I think that's more or less right. I believe the bash return value limit is 254? I want to say. Must be integer between 0 and 254. Can't be a string, a float, etc.
http://tldp.org/LDP/abs/html/complexfunct.html
Functions return a value, called an exit status. This is analogous to
the exit status returned by a command. The exit status may be
explicitly specified by a return statement, otherwise it is the exit
status of the last command in the function (0 if successful, and a
non-zero error code if not). This exit status may be used in the
script by referencing it as $?. This mechanism effectively permits
script functions to have a "return value" similar to C functions.
So actually, foo there would have returned a 127 command not found error. I think. I'd have to test to see for sure.
[updated}
No, echo is the last command, as you can see from your output. And the outcome of the last echo is 0, so the function returns 0. So you want to dump this notion and go to something like trap, that's assuming you can't touch the internals of the function, which is odd.
echo fred; echo reval: $?
fred
reval: 0
What does set -e mean in a bash script?
-e Exit immediately if a command exits with a non-zero status.
But it's not very reliable and considered as a bad practice, better use :
trap 'do_something' ERR
http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_12_02.html see trap, that may do what you want. But not ||, unless you add returns.
try
if [[ "$1" == "foo" ]]; then
foo
fi
I wonder if it was trying to execute the command foo within the condition test?
from bash reference:
-e
Exit immediately if a pipeline (see Pipelines), which may consist of a single simple command (see Simple Commands), a list (see Lists),
or a compound command (see Compound Commands) returns 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 any command executed in a &&
or || list except the command following the final && or ||, any
command in a pipeline but the last, or if the command’s return status
is being inverted with !.
as you can see, if the error occurs within the test condition, then the script will continue oblivious and return 0.
--
Edit
So in response, I note that the docs continue:
If a compound command other than a subshell returns a non-zero status
because a command failed while -e was being ignored, the shell does
not exit.
Well because your for is succeeded by the echo, there's no reason for an error to be thrown!

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

bash script: how to save return value of first command in a pipeline?

Bash: I want to run a command and pipe the results through some filter, but if the command fails, I want to return the command's error value, not the boring return value of the filter:
E.g.:
if !(cool_command | output_filter); then handle_the_error; fi
Or:
set -e
cool_command | output_filter
In either case it's the return value of cool_command that I care about -- for the 'if' condition in the first case, or to exit the script in the second case.
Is there some clean idiom for doing this?
Use the PIPESTATUS builtin variable.
From man bash:
PIPESTATUS
An array variable (see Arrays
below) containing a list of exit
status values from the processes in
the most-recently-executed foreground
pipeline (which may contain only a
single command).
If you didn't need to display the error output of the command, you could do something like
if ! echo | mysql $dbcreds mysql; then
error "Could not connect to MySQL. Did you forget to add '--db-user=' or '--db-password='?"
die "Check your credentials or ensure server is running with /etc/init.d/mysqld status"
fi
In the example, error and die are defined functions. elsewhere in the script. $dbcreds is also defined, though this is built from command line options. If there is no error generated by the command, nothing is returned. If an error occurs, text will be returned by this particular command.
Correct me if I'm wrong, but I get the impression you're really looking to do something a little more convoluted than
[ `id -u` -eq '0' ] || die "Must be run as root!"
where you actually grab the user ID prior to the if statement, and then perform the test. Doing it this way, you could then display the result if you choose. This would be
UID=`id -u`
if [ $UID -eq '0' ]; then
echo "User is root"
else
echo "User is not root"
exit 1 ##set an exit code higher than 0 if you're exiting because of an error
fi
The following script uses a fifo to filter the output in a separate process. This has the following advantages over the other answers. First, it is not bash specific. In particular it does not rely on PIPESTATUS. Second, output is not stalled until the command has completed.
$ cat >test_filter.sh <<EOF
#!/bin/sh
cmd()
{
echo $1
echo $2 >&2
return $3
}
filter()
{
while read line
do
echo "... $line"
done
}
tmpdir=$(mktemp -d)
fifo="$tmpdir"/out
mkfifo "$fifo"
filter <"$fifo" &
pid=$!
cmd a b 10 >"$fifo" 2>&1
ret=$?
wait $pid
echo exit code: $ret
rm -f "$fifo"
rmdir "$tmpdir"
EOF
$ sh ./test_filter.sh
... a
... b
exit code: 10

Resources