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

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

Related

Chosing "input" in an AND (&&) and OR (||) list of commands

I have to find a way to have my script read from one of these three options:
a file argument
standard input
a previously established environment variable
Here's what I currently have:
#!/bin/bash
key=$1
[ $# -ge 1 -a -f "$2" ] && input="$2" || [ -f "$INPUT" ] && input="$INPUT" || input="-"
echo $input
Only the environment variable refuses to work, the rest works fine.
I've tried using the export INPUT="pathnametofile" before but it doesn't make any difference, I end up with the shell asking me to enter info as if I called on cat.
The problem in your script
Your attemp is not working due to the way the shell processes a Lists of Commands:
‘&&’ and ‘||’ have equal precedence.
AND and OR lists are executed with left associativity.
Your sentence:
[ $# -ge 1 -a -f "$2" ] && input="$2" || [ -f "$INPUT" ] && input="$INPUT" || input="-"
does the same as follows:
[ $# -ge 1 -a -f "$2" ] && input="$2"
[ $? -eq 0 ] || [ -f "$INPUT" ]
[ $? -eq 0 ] && input="$INPUT"
[ $? -eq 0 ] || input="-"
Now yo may see why your unexpected behaviour.
A better attempt grouping commands
{ [ $# -ge 1 -a -f "$2" ] && input="$2"; } || { [ -f "$INPUT" ] && input="$INPUT"; } || input="-"
Now, due to precedence, the first group is not needed at all:
[ $# -ge 1 -a -f "$2" ] && input="$2" || { [ -f "$INPUT" ] && input="$INPUT"; } || input="-"
Furthermore, unless you have set the positional parameters by hand, you can remove the first check (after all, if $2 is emtpy, -f "" fails the same).
[ -f "$2" ] && input="$2" || { [ -f "$INPUT" ] && input="$INPUT"; } || input="-"
An alternative with the if conditional construct
if [ -f "$2" ]; then
input=$2
elif [ -f "$INPUT" ]; then
input=$INPUT
fi
echo "${input:=-}"
untested, but you'll probably have better luck with if commands, and test that the variable is not empty:
if [ $# -ge 1 -a -f "$2" ]; then
input="$2"
elif [ -n "$INPUT" -a -f "$INPUT" ]; then
input="$INPUT"
else
input="-"
fi

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.

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

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 ...

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