This question already has answers here:
Why should there be spaces around '[' and ']' in Bash?
(5 answers)
Closed 7 months ago.
I need to add shell if-else statement to my Makefile, but the if expression always evaluates to false.
For example the next code:
if [1 -eq 1]; then echo "yes"; else echo "no"
prints "no"
The only code that evaluated to true was:
if true; then echo "yes"; else echo "no"
Why all expressions in the code (except for "true") evaluates to false? :(
I would really appreciate any help
** Please note - the statements work correctly when run from Shell
The code snippet from the original Makefile:
SIMULATION_RUN_CMD = rm -rf $(TEST_DIR)/* && mkdir -p $(TEST_DIR) && cd $(TEST_DIR) && (cp -rf $(VIVADO_PROJ)/$(PROJECT)/export_for_sim/$(SIMULATOR)/{*.mem,.mif,design.dat,nocattrs.dat,cpm_data_sim.cdo} $(TEST_DIR) || true) && \
ln -sf $(TEST_DIR)/simulation.log $(RUN_DIR)/simulation.log && \
(timeout $(SIM_TIMEOUT) ${SIM_DIR}/simv +UVM_TESTNAME=$(UVM_TESTNAME) $(SIM_FLAGS) -l $(TEST_DIR)/simulation.log -do $(DO_FILE) ; \
if [1 -eq 1]; then echo "if statement yes " >> $(TEST_DIR)/simulation.log; else echo "if statement no " >> $(TEST_DIR)/simulation.log; fi \
|| true) && \
$(MODEL_POST_SIM_ACIONS)
$(SIMULATION_RUN_TAR):
#echo -e "Make Command: $(SIMULATION_RUN_CMD)" $(PRINT_OUTPUT)
($(SIMULATION_RUN_CMD)) $(PRINT_OUTPUT)
First, you have a syntax error in your command. If you type that exactly into bash you'll get an error:
[[1: command not found
You need spaces after the [[ and before the ]] tokens:
if [[ 1 -eq 1 ]]; then echo "yes"; else echo "no"
Second, the reason it doesn't work when run from make is that make doesn't invoke bash. Make invokes the POSIX standard shell /bin/sh. If you do this you'll see the same behavior you get with make:
$ /bin/sh -c 'if [[ 1 -eq 1 ]]; then echo yes; else echo no; fi'
/bin/sh: 1: [[: not found
no
The [[ operator is a bash-specific feature. If you want to write this using POSIX features you should use:
$ /bin/sh -c 'if [ 1 -eq 1 ]; then echo yes; else echo no; fi'
yes
If you really want make to invoke bash as its shell instead of sh, add this to your makefile:
SHELL := /bin/bash
Of course then your makefile will not work on any system that doesn't have /bin/bash available.
ETA
After seeing the very much more complicated, but still not complete, code you added, I will say the following:
As I said above, you have an error in your script. if [1 -eq 1] is completely illegal. You must have spaces after [ and before ]. Again, if you run this yourself at the shell prompt you will get the same failure. It has nothing to do with make.
Because of this error, the if-statement will ALWAYS fail and so this will ALWAYS run the "else" command and print "no".
You say you don't see any error message. I can't explain that, except that you run this recipe this way:
($(SIMULATION_RUN_CMD)) $(PRINT_OUTPUT)
You don't tell use what the value of the PRINT_OUTPUT variable is, so I can only assume that it throws away stderr into the bit-bucket (or possibly, both stdout and stderr). If you didn't do that, so you could see the output, you'd see the error message being printed. Or maybe that redirects to a log file in which case, you can look there for the message.
Related
I'm trying to run a shell command (currently either sh or bash) which connects to a database. Because the database is still 'warming up', the command fails.
So I was trying to do a loop (let's say ... 100 tries) and each time the command fails, wait 1 second and retry.
If there's an error, this is the start of the string that is dumped to stdout: Sqlcmd: Error: <snipped>
Here's what I've been trying:
for i in $(seq 1 100)
do
X='/opt/mssql-tools/bin/sqlcmd -E -S localhost -Q "<some sql statement> "'
if [[ $X == Sqlcmd: Error:* ]]
echo "."
then
break
fi
done
It's not working as I figure out the string comparison stuff with shell/bash ... but was more making sure if I was on the right track etc.
You could try something like:
while true ; do
if Sqlcmd xxx xxx xxx ; then break ; fi
# or:
Sqlcmd xx xxx xxx && break
sleep 1
done
You can also add a counter:
for ((n=100;n>0;n--)) ; do
Sqlcmd xxx xxx xxx
if [[ $? == 0 ]] ; then
break
fi
sleep 1
done
[[ $n == 0 ]] && echo Timeout && exit 1
I'm showing two different ways of testing the return value here, but the first one is preferred (if cmd ; then ... ; fi).
$? is the return value from the last command, which is 0 when it completed successfully. If it returns 0 even in case of error (which can happen for malformed programs), you can test the output with grep:
Sqlcmd xxx xxx 2>&1 | grep <error pattern> > /dev/null
if [[ $? != 0 ]] ; then break ; fi
Here we test $? != 0 because grep will return 0 when the error pattern has been found.
If you want to get the output result into a variable, run the command with X=$(Sqlcmd xxx xxx). Then you can use bash string comparison:
X=$(Sqlcmd xxx xxx)
if [[ "$X" =~ .*error.* ]] ; then
<handle error here>
fi
Note bash can match regexp, which makes it really handy at checking error types.
You can also use a switch/case construct:
case "$X" in
*Error:*) echo " Error detected " ;;
*) break ;;
esac
(Note the double ;;)
I ended up learning all the clues from #matthieu's post. This is what I ended up doing:
for i in $(seq 1 30)
do
/opt/mssql-tools/bin/sqlcmd -U sa -P <snip> -S localhost -Q "USE Master" 2>&1
if [[ $? != 0 ]]
then
# Failed
echo "."
sleep 1s
else
# worked!
break
fi
done
breakdown for those learning (like me)
execute a sql query using the sqlcmd command. Any errors via stderr (that's the 2 in 2>&1) will be redirected to the console stdout (that's the $1). REF: 2>&1 shell idiom.
the result status code is sent to $? (REF: what is bash dollar questionmark ?)
if it failed(any value that is NOT a zero), then sleep 1 sec and we'll try. Only re-try 30 times, though.
if we worked (value is zero), then stop trying and go on....
So there we have it! shell/bash shell 101 stuff. good luck!
Why does bash do what I'd expect here with a compound command in a subshell:
$ bash -x -c 'set -e; (false && true; echo hi); echo here'
+ set -e
+ false
+ echo hi
hi
+ echo here
here
But NOT do what I'd expect here:
$ bash -x -c 'set -e; (eval "false && true"; echo hi); echo here'
+ set -e
+ eval 'false && true'
++ false
Basically, the difference is between 'eval'-uating a compound command and just executing a compound command. When the shell executes a compound command, non-terminal commands in the compound command that fail do not cause the entire compound command to fail, they simply terminate the command. But when eval runs the compound command and any non-terminal sub-command terminates the command with an error, eval terminates the command with an error.
I guess I need to format my eval statement like this:
eval "false && true" || :
so that the eval command doesn't exit my subshell with an error, because this works as I'd expect it to:
$ bash -x -c 'set -e; (eval "false && true" || :; echo hi); echo here'
+ set -e
+ false
+ echo hi
hi
+ echo here
here
The problem I have with this is that I've written a function:
function execute() {
local command="$1"
local remote="$2"
if [ ! -z "$remote" ]; then
$SSH $remote "$command" || :
else
eval "$command" || :
fi
}
I'm using set -e in my script. The same problem occurs with ssh in this function - if the last command in the ssh script is a compound command that terminates early, the entire command terminates with an error. I want commands like this to behave as if they were executing locally - early terminating compound commands should not cause ssh or eval to return 1, failing the entire command. If I tack || : on the end of my eval statement or my ssh statement, then all such commands will succeed, even if they shouldn't because the last command in the eval'd or ssh'd command failed.
Any ideas would be much appreciated.
I should also mention that set -e is terribly error-prone; see http://mywiki.wooledge.org/BashFAQ/105 for a bunch of examples. So the best solution might be to dispense with it, and write your own logic to detect errors and abort.
That out of the way . . .
The problem here is that eval "false && true" is a single command, and evaluates to false (nonzero), so set -e aborts after that command runs.
If you were instead to run eval "false && true; true", you would not see this behavior, because then eval evaluates to true (zero). (Note that, although eval does implement the set -e behavior, it obeys the rule that false && true is non-aborting.)
This is not actually specific to eval, by the way. A subshell would give the same result, for the same reason:
$ bash -x -c 'set -e; (false && true); echo here'
+ set -e
+ false
The simplest fix for your problem is probably just to run an extra true if the end is reached:
$SSH $remote "set -e; $command; true"
eval "$command; true"
eval counts as its own command with its own exit code.
Since eval "false && true" returns an exit code of 1, it triggers set -e.
Trying to debug my bash script. What's wrong with my syntax here? I'm trying to evaluate a parameter entered by the user and based on that run one of my IF-THEN statements. However, I'm getting a command not found.
Here's my script thus far:
if [[ $# != 4 ]]; then
echo "Usage: ./test.sh <ABC|XYZ> <owner> <db> <TARGETHOST>" 2>&1
exit 1
fi
case $1 in
ABC|XYZ)
filename="get-$1.sql"
;;
*)echo "Must enter ABC or XYZ"
exit 1
;;
esac
export OWNER=$2
export DB=$3
export HOST_NM=$4
export PORT=5432
export LOG="test-$1.log"
PSQL=`which psql`
if [[$1=="ABC"]]; then
RUNCLI=$("$PSQL" -h $HOST_NM -p $PORT -U $OWNER $DB -F $'\t' --no-align -f get-$1.sql | tee >> $LOG)
exit 1
else
echo "Error running report ..."
fi
if [[$1=="XYZ"]]; then
RUNCLI2=$("$PSQL" -h $HOST_NM -p $PORT -U $OWNER $DB -a -f get-$1.sql | tee >> $LOG)
exit 1
else
echo "Error running report ..."
fi
Error:
./test.sh: line 41: [[XYZ==ABC]]: command not found
Error running report ...
./test.sh: line 51: [[XYZ==XYZ]]: command not found
Error running report ...
Although the question is already answered in the comment section I want to give an answer and share some knowledge which is not obvious (at least it was not for me).
The if in bash just checks the return code of the following command, which means that instead of if [ condition ]... or if [[ condition ]]... you could also write if ./configure && make....
[ and [[ are commands or shell-built-ins, respectively, as well and not part of the if syntax. An which [ for instance returns /bin/[.
At this point it is obvious that you need spaces between the brackets and the condition since it is just just a set of parameters passed to a command.
If you have this in mind, you will never forget the spaces again.
This question already has answers here:
How to check exit if used tee?
(3 answers)
Closed 8 years ago.
The following script tries to create a directory and it fails because temp1 doesn't exist. I want to redirect the output with "tee." The problem is that "$?" catches the return value of "tee" which succeeds and not the value of "mkdir" which fails. How can I use "tee" but check the return value of "mkdir"
file name: ./test.sh
#!/bin/bash
mkdir temp1/temp2 | tee test_output.txt
if [ "$?" != 0 ]; then
echo "Command failed"
else
echo "Command successed"
fi
./test.sh
mkdir: cannot create directory `temp1/temp2': No such file or directory
Command successed
Since you stated bash, you can use PIPESTATUS. From the man page:
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).
For example:
#!/bin/bash
mkdir temp1/temp2 | tee test_output.txt
if [ "${PIPESTATUS[0]}" != 0 ]; then
echo "Command failed"
else
echo "Command succeeded"
fi
There are terminal dependent solutions but you can come around this issue by wrapping this command in a function.
create_dir() {
{ mkdir temp1/temp2 || return 1; } | tee test_output.txt
return $?
}
create_dir
if [ "$?" != 0 ]; then
echo "Command failed"
else
echo "Command successed"
fi
Could you not just use mkdir -p, to force creation of the directory?
Perhaps there is no directory temp1
Use parameter -p with mkdir:
mkdir -p temp1/temp2 | tee test_output.txt
Ok I'm kind of new to bash scripting [the advanced stuff] and I need a little help. I don't even know exactly how to phrase this so I'll just explain what I am doing and what I need to know about it.
in my script I run a ./configure and I need to be able to catch if there was an error in the configure and react accordingly within the bash script.
the code is:
function dobuild {
echo -e "\e[1;35;40mExecuting Bootstrap and Configure\e[0m"
cd /devel/xbmc
if [ $Debug = "1" ];
then
#either outputs to screen or nulls output
./bootstrap >/dev/null
/usr/bin/auto-apt run ./configure --prefix=/usr --enable-gl --enable-vdpau --enable-crystalhd --enable-rtmp --enable-libbluray >/dev/null
else
./bootstrap
/usr/bin/auto-apt run ./configure --prefix=/usr --enable-gl --enable-vdpau --enable-crystalhd --enable-rtmp --enable-libbluray
fi
}
and say the configure returns an error 1 or 2 how do I trap that and act on it?
TIA
After the execution of every shell command it's return value, a number between 0 and 255, is available in the shell variable ?. You can get the value of this variable by prefixing it with the $ operator.
You have to be a little careful with ?, because it is reset by every command, even a test. For example:
some_command
if (( $? != 0 ))
then
echo "Error detected! $?" >&2
fi
Gives: Error detected! 0 because ? was reset by the test condition. It is probably best to store ? in another variable if you are going to use it later, which includes doing more than one test on it.
To do a numeric test in bash use the (( ... )) numeric test construct:
some_command
result=$?
if (( $result == 0 ))
then
echo "it worked!"
elif (( $result == 1 ))
then
echo "Error 1 detected!" >&2
elif (( $result == 2 ))
then
echo "Error 2 detected!" >&2
else
echo "Some other error was detected: $result" >&2
fi
Alternatively use a case statement.
After the execution of a command, the returned value is stored in the shell variable $?. So you would have to match that with the return values of success and failure
if [ $? == 1 ]
then
#do something
else
#do something else
fi
The other answers about $? are great (though be careful about assuming values other than 0 and not-0 - different commands. or different versions of the same command may fail with different values), but if you just need to act on success or failure immediately, you can simplify things:
if command ; then
# success code here
else
# failure code here
fi
Or if you only want to act on failure, here's a hack for older shells (the colon is a null command but it satisfies the then clause):
if command ; then :
else
# failure code here
fi
But in modern shells like bash this is better:
if ! command ; then # use the ! (not) operator
# failure code here
fi
And, if you only need to do simple things, you can use the "short circuit" operators:
command1 && command2_if_command1_succeeds
command1 || command2_if_command1_fails
Those only work for single commands, stringing more && and || on them doesn't do what you might think in most cases so most people avoid that. However, you can do multiple commands if you group them:
command1 && { command2; command3; command4; }
That can get hard to read so it's best to keep it simple if you use it all:
command1 || { echo "Error, command1 failed!" >&2; exit 1; }