[root#jiaoyou ~]# test 1 = 1 -a 0 = 1
[root#jiaoyou ~]# if [1 = 1 -a 0 = 1]then echo 1;else echo 2;fi
-bash: syntax error near unexpected token `else'
Why test doesn't give any output and the if statement fails?
Can someone point out what's wrong here?
UPDATE
Can someone illustrate how to simulate complex expressions like 1=1 and (0=1 or 1=1) with [[?
Return codes are not printed; you must echo the value of $? in order to see it.
[ is a command. Just as you don't write echoHelloworld, you can't write [1 ....
There are several equivalent ways of doing the test you're looking for. First, essentially the way you're doing it, but with the syntax fixed (added required spaces around the parameters to the [ (aka test) command, and a ; between ] and then):
if [ 1 = 1 -a 0 = 1 ];then echo 1;else echo 2;fi
Here's a version with explicit grouping in the expression; note that the parentheses have to be escaped because they're special characters in the shell, but in this case we want them passed to the [ command as arguments:
if [ \( 1 = 1 \) -a \( 0 = 1 \) ];then echo 1;else echo 2;fi
Another version with explicit grouping, this time using two separate [ commands connected with bash's && operator (note that this can also connect other commands):
if [ 1 = 1 ] && [ 0 = 1 ];then echo 1;else echo 2;fi
And finally, a couple of examples using bash's [[ operator instead of the [ command; note that [[ isn't a command, so its expression isn't parsed as command arguments, allowing it a more intuitive syntax (e.g. allowing && and || as operators, and parentheses and !<> comparisons without escaping):
if [[ 1 = 1 && 0 = 1 ]];then echo 1;else echo 2;fi
if [[ ( 1 = 1 ) && ( 0 = 1 ) ]];then echo 1;else echo 2;fi
Some more notes/warnings: the = operator being used in all of these examples does string comparisons rather than numeric comparison (i.e. [ 1 = 01 ] is false); if you want numeric comparison use -eq instead (similarly, < and > are string comparisons, while -lt and -gt are numeric). Also, if you're comparing strings that include spaces, '[' can mistake the string for part of the expression under some circumstances (esp. if you don't have the strings wrapped in double-quotes); as far as I know [[ never has this problem. Overall, if you're using shells that support [[, it's much easier to deal with than [.
As #IgnacioVazquez-Abrams correctly states, test just sets the error code.
That said, here's a nice bash-ism for quickly checking the error value:
test [ expr ] && echo SUCCESS || echo FAIL
Due to how && and || short circuit, that will output SUCCESS or FAIL depending on whether test returns 0 or non-0.
Related
I've been Bash scripting for a while and I'm wondering if there's any difference between these two forms of negation with the test command:
if [ ! condition ]; then
fi
if ! [ condition ]; then
fi
The first tells the shell to pass the arguments ! condition to test, letting the program take care of the negation itself. On the other hand, the second passes condition to test and lets the shell itself negate the error code.
Are there any pitfalls I should be aware of when choosing between these two forms? What values of $condition could make the results differ between them?
(I seem to remember reading an article a while ago discussing this, but I don't remember how to find it/exactly what was discussed.)
To build on chepner's insightful comment on the question:
In [ ! condition ], the ! is just an argument to the [ builtin (an effective alias of the test builtin); it so happens that [ / test interprets argument ! as negation.
In ! [ condition ], the ! is a shell keyword that negates whatever command it is followed by (which happens to be [ in this case).
One thing that the ! [ condition ] syntax implicitly gives you is that negation applies to whatever [ condition ] evaluates to as a whole, so if that is the intent, you needn't worry about operator precedence.
Performance-wise, which syntax you choose probably doesn't make much of a difference; quick tests suggest:
If condition is literally the same in both cases, passing the ! as an argument to [ is negligibly faster.
If ! is used as a keyword, and you are therefore able to simplify the condition by not worrying about precedence, it may be slightly faster (e.g, ! [ 0 -o 1 ] vs. [ ! \( 0 -o 1 \) ]; note that the POSIX spec. discourages use of -a and -o due to ambiguity).
That said, if we're talking about Bash, then you should consider using [[ instead of [, because [[ is a shell keyword that parses the enclosed expression in a special context that:
offers more features
allows you to safely combine expressions with && and ||
comes with fewer surprises
is also slightly faster (though that will rarely matter in pratice)
See this answer of mine.
! negates the exit code of following command :
$ ! test 1 = 1 || echo $? # negate command with true expression
1
As said in man page, test (and similar [ builtin) exit with the status determined by following expression :
$ test ! 1 = 1 || echo $? # return false expression
1
$ [ ! 1 = 1 ] || echo $?
1
For a given expression :
on one side you negate the test command that exit with true expression status.
on the other side, you negate an expression and the test command (or [) exit with its false status
Both will have the same effect.
So I would say that these syntax are equivalent. With the advantage for external ! to allow negate compound tests :
$ ! [ 1 = 1 -a 2 = 2 ] || echo $?
1
There is a difference if test/[ faces an error. Consider:
x=abc
if [ ! "$x" -gt 0 ]; then echo "first true"; fi
if ! [ "$x" -gt 0 ]; then echo "second true"; fi
The output is:
bash: [: abc: integer expression expected
second true
Unlike [[ .. ]], test/[ works like regular utility and signals errors by returning 2. That's a falsy value, and with ! outside the brackets, the shell inverts it just the same as a regular negative result from the test would be inverted.
With [[ .. ]] the behaviour is different, in that a syntax error in the condition is a syntax error for the shell, and the shell itself exits with an error:
if [[ a b ]]; then echo true; else echo false; fi
prints only
bash: conditional binary operator expected
bash: syntax error near `b'
with no output from the echos.
On the other hand, arithmetic tests work differently within [[ .. ]] so [[ "$x" -gt 0 ]] would never give an error.
For some reason I just cannot get an if statement to test if a string is literally equal to an asterisk. I have tried every combination I can think of and I don't want to mess with file globbing. Please help.
if [ $VAR = "\*" ]; then
* UPDATE *
Both of those suggestions work. The issue is apparently not with the * comparison, but with the other part of the if statement. This is supposed to compare whether or not $VAR is between 0 and 20 or is a wildcard.
if [ "$VAR" -gt 0 ] && [ "$VAR" -lt 20 ] || [ "$VAR" = "*" ]; then
This other part of the IF statement if apparently goofing up the last comparison.
* UPDATE *
Just tested it again and checked my syntax. When $VAR is between 0 and 20 it works great (true), when $VAR is over 20 it also works (reports false), however as soon as I try to set $VAR to an * the if statement freaks and pops out:
line 340: [: *: integer expression expected
Another version using bash's double brackets:
if [[ $VAR = "*" || ($VAR -gt 0 && $VAR -lt 20) ]]; then
The double brackets allow you to use && and ||. Also, bash doesn't perform word splitting or glob expansion on arguments to [[, so $VAR doesn't need to be quoted and ( doesn't need to be escaped.
[[ also works in zsh and ksh, if you need (some) portability.
$ VAR="*"
$ if [ "$VAR" = "*" ] ; then echo Star ; fi
Star
Quote variables when they could contain glob patterns, whitespace or other interpretable sequences you don't want interpreted. This also avoids syntax errors if $VAR is empty.
For your second problem, [ "$VAR" -gt 0 ] doesn't make sense if $VAR is anything but a number. So you must avoid having that test evaluated in that case. Simply exchange your tests - || and && are short-circuiting (in bash at least, not sure if that's POSIX):
if [ "$VAR" = "*" ] || [ "$VAR" -gt 0 -a "$VAR" -lt 20 ] ; then
For the below script I am expecting the output to be msg_y and msg_z. But it is printing msg_x and msg_z. Can somebody explain to me what is going on?
#!/bin/bash
set -x
vr=2
echo $vr
if [ $vr > 5 ]
then
echo "entered 1st if"
echo "msg_x"
echo "out of 1st if"
if [ $vr < 8 ]; then
echo "in of 2nd if"
echo "msg_y"
else
echo "msg_z"
fi
else
if [ $vr > 1 ]; then echo "msg_y"
else echo "msg_z"
fi
fi
This expression
[ $vr > 5 ]
is being parsed as an output redirection; check to see if you have a file named "5" now. The output redirection is vacuously true. Note that the usual admonition to quote parameters inside a test expression would not help here (but it's still a good idea).
You can escape the > so that it is seen as an operator in the test command:
if [ "$vr" \> 5 ]; then
or you can use the integer comparison operator -gt
if [ "$vr" -gt 5 ]; then.
Since you are using bash, you can use the more robust
conditional expression
if [[ $vr > 5 ]]; then
or
if [[ $vr -gt 5 ]]; then
or use an arithmetic expression
if (( vr > 5 )); then
to do your comparisions (likewise for the others).
Note: although I showed how to make > work as a comparison operator even when surrounded by integers, don't do this. Most of the time, you won't get the results you want, since the arguments are compared lexicographically, not numerically. Try [ 2 \> 10 ] && echo What? Either use the correct integer comparison operators (-gt et al.) or use an arithmetic expression.
So I have a bash script that needs to take an arbitrary number of command line arguments and put them into a single string
Example of what the user would type in:
give <environment> <email> <any number of integers separated by spaces>
give testing stuff#things.com 1 2 3 4 5
I want to get all of the arguments from $3 to $# and concat them into a string.
My (probably awful) solution right now is
if [ $# -gt 3 ]
then
env="env="$1
email="email="$2
entList=""
for i in {3..$#}
do
if [ $i -eq 3 ]
then
entList=$3
shift
fi;
if [ $i -gt 3 ]
then
entList=$entList","$3
shift
fi;
done
fi;
I handle the case of having only three arguments a bit differently, and that one works fine.
Final value of $entList given the example give testing stuff#things.com 1 2 3 4 5 should be: 1,2,3,4,5
Right now when i run this i get the following Errors:
/usr/local/bin/ngive.sh: line 29: [: {3..5}: integer expression expected
/usr/local/bin/ngive.sh: line 34: [: {3..5}: integer expression expected
Lines 29 and 34 are:
line 29: if [ $i -eq 3 ]
line 34: if [ $i -gt 3 ]
Any help would be appreciated.
You're on the right track. Here's my suggestion:
if [ $# -ge 3 ]; then
env="$1"
email="$2"
entlist="$3"
while shift && [ -n "$3" ]; do
entlist="${entlist},$3"
done
echo "entlist=$entlist"
else
echo "Arguments: $*"
fi
Note that variables should always be put inside quotes. I'm not sure why you were setting env=env=$1, but I suspect that if you want to recycle that value later, you should do it programatically rather than by evaluating the variable as if it were a statement, in case that was your plan.
Skip first three arguments using a subarray:
all=( ${#} )
IFS=','
threeplus="${all[*]:3}"
The reason you're getting those error messages is that in:
for i in {3..$#}
The brace expansion is performed before the parameter expansion and so the following if statement is evaluated as:
if [ {3..$#} -eq 3 ]
which isn't valid.
Change your for statement to use the C style:
for ((i = 3; i <= $#; i++))
Use this style for integer comparison:
if (( $# > 3 ))
and
if (( i == 3 ))
and
if (( i > 3 ))
Put your parameters inside the quotes:
env="env=$1"
email="email=$2"
and
entList="$entList,$3"
although the quotes aren't necessary since word splitting isn't performed on the right side of an assignment and you're not assigning special characters such as whitespace, semicolons, pipes, etc.
How can a Bourne Shell script know that the first parameter it received was '' (Two single quotation marks?
I've tried
if [ -z "$1" ] ; then
echo "Wrong number of parameters"
fi
But it seems that the $1 expands to an empty string and so is "$1".
When you type '' in command line shell translate it to argument - zero length string.
Check variable that holds the number or arguments (before checking -z "$1").
# check for any arguments
if [ "$#" -eq 0 ]; ...
# or -- has arguments and first one is ''
if [ "$#" -gt 0 -a -z "$1" ]; ...
See 'man test' for INTEGER comparison tests (-eq, -gt, etc).
EDIT (based on comments to question):
On windows (what shell do you use?) you have to check for '' (two characters) (cmd.exe passes it that way I think). On linux your script get an argument of string length zero.
if [ \( "$#" -gt 0 -a -z "$1" \) -o "$1" = "''" ]; ...
I assume what you mean is that a parameter was passed, but its value is empty. This is how to check it:
if [ $# -gt 0 -a "$1" = '' ]
then
echo '$1 was passed, but empty'
fi
If you want to check how many parameters were passed (empty or not), then use $# (argument count):
if [ $# -eq 0 ]
then
echo 'no parameters were passed'
fi
If you want to check the difference between two double quotation marks ("") and single quotation marks (''), there's no way to do that in Bourne shell alone. By the time your code is executed, these strings have been evaluated to the empty string.
'' is obviously not an empty string; it contains two characters. Do
[ "$1" = "''" ]
But then, on the (Linux) command line, you'll have to pass the parameter as
./script.sh "''"
if [ "$1" == "--" ] ; then
echo "Wrong number of parameters"
fi