Can I use simply exitcode within test expression (without $?)? - shell

I have to make a conditional in ash, that depends on result of two commands. The problem is one of them returns the result to stdout, the other as exitcode.
Do I have to write
command2
RET=$?
if [ `command1` -eq 1 -a $RET -eq 2 ] ; then ...
or is there some construct that would let me simply access return code of command2 within logic of [ ] ?
if [ `command1` -eq 1 -a ${{{ command2 }}} -eq 2 ] ; then ...
( with ${{{ }}}} being the magical expression extracting the returncode ? )

It would be better to write:
if [ "`command1`" -eq 1 ] && command2
then
....
fi
Or when you want to check if the exit code is 2 then:
if [ "`command1`" -eq 1 ] && { command2 ; [ "$?" = 2 ] ; }
then
....
fi
Example:
$ cat 1.sh
ARG="$1"
command1()
{
echo 1
}
command2()
{
return "$ARG"
}
if [ "`command1`" -eq 1 ] && { command2 ; [ "$?" = 2 ] ; }
then
echo OK
else
echo FAILED
fi
$ sh 1.sh 2
OK
$ sh 1.sh 3
FAILED

I guess there is no way to avoid $?, but I can use the command inside test statement, by adding ;echo $? at the end.
if [ `command1` -eq 1 -a `command2 ; echo $?` -eq 2 ] ; then ...

Related

Keep retrying yarn script until it passes

I am new to bash and wondering if there is a way to run a script x amount of times until it succeeds? I have the following script, but it naturally bails out and doesn't retry until it succeeds.
yarn graphql
if [ $? -eq 0 ]
then
echo "SUCCESS"
else
echo "FAIL"
fi
I can see there is a way to continuously loop, however is there a way to throttle this to say, loop every second, for 30 seconds?
while :
do
command
done
I guess you could devise a dedicated bash function for this, relying on the sleep command.
E.g., this code is freely inspired from that code by Travis, distributed under the MIT license:
#!/usr/bin/env bash
ANSI_GREEN="\033[32;1m"
ANSI_RED="\033[31;1m"
ANSI_RESET="\033[0m"
usage() {
cat >&2 <<EOF
Usage: retry_until WAIT MAX_TIMES COMMAND...
Examples:
retry_until 1s 3 echo ok
retry_until 1s 3 false
retry_until 1s 0 false
retry_until 30s 0 false
EOF
}
retry_until() {
[ $# -lt 3 ] && { usage; return 2; }
local wait_for="$1" # e.g., "30s"
local max_times="$2" # e.g., "3" (or "0" to have no limit)
shift 2
local result=0
local count=1
local str_of=''
[ "$max_times" -gt 0 ] && str_of=" of $max_times"
while [ "$count" -le "$max_times" ] || [ "$max_times" -le 0 ]; do
[ "$result" -ne 0 ] && {
echo -e "\n${ANSI_RED}The command '$*' failed. Retrying, #$count$str_of.${ANSI_RESET}\n" >&2
}
"$#" && {
echo -e "\n${ANSI_GREEN}The command '$*' succeeded on attempt #$count.${ANSI_RESET}\n" >&2
result=0
break
} || result=$?
count=$((count + 1))
sleep "$wait_for"
done
[ "$max_times" -gt 0 ] && [ "$count" -gt "$max_times" ] && {
echo -e "\n${ANSI_RED}The command '$*' failed $max_times times.${ANSI_RESET}\n" >&2
}
return "$result"
}
Then to fully answer your question, you could run:
retry_until 1s 30 command

If statement with two or more conditions

I was modifying an script didn't know how to write more than one condition in an if statement. I want to connect the two condition with an AND.
if [ envoi1 -eq 2 ];then
if [ envoi2 -eq 0 ];then
echo 'Ahora mismo.'
envoi = 1
fi
else
if [ envoi2 -eq 1 ];then
if [ envoi1 -eq 1 ];then
echo 'Situacion Normal.'
envoi = 1
fi
else
echo 'Raruno'
envoi=`expr $envoi1 + envoi2`
fi
fi
Now i use nested if to do the same but the code it's not so clear for me.
try this:
if [ $envoi1 -eq 2 ] && [ $envoi2 -eq 0 ] ; then
envoi = 1
fi
In bash, you can use [[ as follows:
if [[ $envoi2 -eq 1 && $envoi1 -eq 1 ]]; then
echo "Situacion Normal."
envoi=1
fi
However, [[ is not POSIX and will not work if you are using the /bin/sh shell. So if portability is desired use:
if [ $envoi2 -eq 1 -a $envoi1 -eq 1 ]; then
echo "Situacion Normal."
envoi=1
fi
Also note that when assigning variables you should not have any spaces on either side of the =.

Shell Script Too Many Arguments for if condition

My current script does the following;
It takes integer as a command line argument and starts from 1 to N , it checks whether the numbers are divisible by 3, 5 or both of them. It simply prints out Uc for 3, Bes for 5 and UcBes for 3,5. If the command line argument is empty, it does the same operation but the loop goes to 1 to 20.
I am having this error "Too many arguments at line 11,15 and 19".
Here is the code:
#!/bin/bash
if [ ! -z $1 ]; then
for i in `seq 1 $1`
do
if [ [$i % 3] -eq 0 ]; then
echo "Uc"
elif [ i % 5 -eq 0 ]; then
echo "Bes"
elif [ i % 3 -eq 0 ] && [ i % 5 -eq 0 ]
then
echo "UcBes"
else
echo "$i"
fi
done
elif [ -z $1 ]
then
for i in {1..20}
do
if [ i % 3 -eq 0 ]
then
echo "Uc"
elif [ i % 5 -eq 0 ]
then
echo "Bes"
elif [ i % 3 -eq 0 ] && [ i % 5 -eq 0 ]
then
echo "UcBes"
else
echo "$i"
fi
done
else
echo "heheheh"
fi
Note that [ is actually synonym for the test builtin in shell (try which [ in your terminal), and not a conditional syntax like other languages, so you cannot do:
if [ [$i % 3] -eq 0 ]; then
Moreover, always make sure that there is at least one space between [, ], and the variables that comprise the logical condition check in between them.
The syntax for evaluating an expression such as modulo is enclosure by $((...)), and the variable names inside need not be prefixed by $:
remainder=$((i % 3))
if [ $remainder -eq 0 ]; then
You should probably use something like :
if [ $(($i % 3)) -eq 0 ]
instead of
if [ $i % 3 -eq 0 ]
if [ [$i % 3] -eq 0 ]
Your script could be greatly simplified. For example:
#!/bin/sh
n=0
while test $(( ++n )) -le ${1:-20}; do
t=$n
expr $n % 3 > /dev/null || { printf Uc; t=; }
expr $n % 5 > /dev/null || { printf Bes; t=; }
echo $t
done
gives slightly different error messages if the argument is not an integer, but otherwise behaves the same.

Bash - Troubling result with IF [..] || [..]

I can seem to see why this doesn't work:
#!/bin/bash
if [ $# -ne 1 ] || [ $# -ne 2 ]; then
# Should run if there are either 1 or 2 options specified
echo "usage: ${0##*/} <username>"
exit
fi
When testing to see if it works:
root#ubuntu:~# testing.sh optionone optiontwo
...Correct output...
root#ubuntu:~# testing.sh optionone
usage: testing.sh <username>
Change the boolean logic:
if [ $# -ne 1 ] && [ $# -ne 2 ]; then
Or
if ! ( [ $# -eq 1 ] || [ $# -eq 2 ] ); then
BTW, you can use Shell-Arithmetic ((...)):
if (( $#!=1 && $#!=2 )); then
Note that you are executing 2 commands in:
[ $# -ne 1 ] || [ $# -ne 2 ]
[ $# -ne 1 ] is a 1st command, and the [ $# -ne 2 ] command is executed only if the previous has a non-zero error code as of the || shell operator.
In your case, it is not important, but in the case bellow, it is:
[ $? -eq 0 ] || [ $? -eq 1 ]
The 2nd command will always be true, as the 2nd $? is the return code of [ $? -eq 0 ]. You can test it with the lines bellow that will print true twice:
function f() { return $1; }
f 1
{ [ $? -eq 0 ] || [ $? -eq 1 ]; } && echo "true"
f 2
{ [ $? -eq 0 ] || [ $? -eq 1 ]; } && echo "true"
The correct way to execute a or in a single command is:
[ $? -eq 0 -o $? -eq 1 ]
This way, those bellow only print true once:
function f() { return $1; }
f 1
{ [ $? -eq 0 -o $? -eq 1 ]; } && echo "true"
f 2
{ [ $? -eq 0 -o $? -eq 1 ]; } && echo "true"
And concerning your original question, kev has already point out that there was a logic error in your test. The negative of [ $# -eq 1 ] || [ $# -eq 2 ] is NOT [ $# -eq 1 ] && NOT [ $# -eq 2 ] and this becomes [ $# -ne 1 ] && [ $# -ne 2 ] or in a single command:
[ $# -ne 1 -a $# -ne 2 ]
One way to make this work is to switch out the -ne comparison operator for -lt and -gt (less than and greater than) for the conditional statement. Like this:
#!/bin/bash
#Should run if there are either 1 or 2 options specified
if [ $# -lt 1 ] || [ $# -gt 2 ]; then
echo "usage: ${0##*/} <username>"
exit
fi

Consolidate multiple if statements in Ksh

How can I consolidate the following if statements into a single line?
if [ $# -eq 4 ]
then
if [ "$4" = "PREV" ]
then
print "yes"
fi
fi
if [ $# -eq 3 ]
then
if [ "$3" = "PREV" ]
then
print "yes"
fi
fi
I am using ksh.
Why does this give an error?
if [ [ $# -eq 4 ] && [ "$4" = "PREV" ] ]
then
print "yes"
fi
Error:
0403-012 A test command parameter is not valid.
Try this:
if [[ $# -eq 4 && "$4" == "PREV" ]]
then
print "yes"
fi
You can also try putting them all together like this:
if [[ $# -eq 4 && "$4" == "PREV" || $# -eq 3 && "$3" == "PREV" ]]
then
print "yes"
fi
Do you just want to check if the last argument is "PREV"? If so, you can also do something like this:
for last; do true; done
if [ "$last" == "PREV" ]
then
print "yes"
fi
'[' is not a grouping token in sh. You can do:
if [ expr ] && [ expr ]; then ...
or
if cmd && cmd; then ...
or
if { cmd && cmd; }; then ...
You can also use parentheses, but the semantics is slightly different as the tests will run in a subshell.
if ( cmd && cmd; ); then ...
Also, note that "if cmd1; then cmd2; fi" is exactly the same as "cmd1 && cmd2", so you could write:
test $# = 4 && test $4 = PREV && echo yes
but if your intention is to check that the last argument is the string PREV, you might consider:
eval test \$$# = PREV && echo yes
Try this :
if [ $# -eq 4 ] && [ "$4" = "PREV" ]
then
print "yes"
fi

Resources