I have been trying to convert the following code to use 'test' instead of 'if'
if [ -e ./blah ] && [ ! -L ./blah ];
then
exit 1
fi
My intention is to use test instead, so that I dont have to exit 1 explicitly. I am trying something like this:
test -e ./blah && ! -L ./blah;
instead of &&, I have tried -a, with different bracket combinations, but I am not successful. I am sure there should be a simple way to do this. Can anyone help me ?
test does not understand logical operators && and ||. You can use test -e ./blah -a ! -L ./blah, but if you are using bash, you can also switch to its more powerful [[ .. ]] construct:
[[ -e ./blah && ! -L ./blah ]]
To make if [ -e ./blah ] && [ ! -L ./blah ]; work, use the following
if [ -e ./blah -a ! -L ./blah ]; (-a stands for and) http://tldp.org/LDP/abs/html/comparison-ops.html
Demo -- http://ideone.com/GR8UiK
But, as others have pointed out, [[ .. ]] construct is more powerful than [...].
You can combine them all (including if then fi):
[[ -e ./blah && ! -L ./blah ]] && exit 1
Use [[ keyword as it is more powerful.
if [[ -e ./blah && ! -L ./blah ]]
then
...
fi
However, to ensure portability, you can do something like this too
if [ -e ./blah ] && [ ! -L ./blah ]
then
...do something
fi
As you ask to use test, you can do like so:
test -e ./blah && test -L ./blah || ( echo 'First action' ; echo 'Second action )
The different operators (&&, ||, etc...) are first parsed by the shell, so you can't use it in command parameter(s).
if [ -e ./blah ] && [ ! -L ./blah ];
is equivalent to
if test -e ./blah && test ! -L ./blah;
Therefore you can simply write
test -e ./blah && test ! -L ./blah
To wit:
$ help [\[] | tail -n +3
[: [ arg... ]
Evaluate conditional expression.
This is a synonym for the "test" builtin, but the last argument must
be a literal `]', to match the opening `['.
Do the following:
$ ls -i /bin/test
54008404 /bin/test
$ ls -i /bin/[
54008404 /bin/test
That 54008404 is the inode number. This is the real name of a file. The /bin/test simply points to the inode and the inode contains all file file information.
The thing to note is that /bin/[ and /bin/test are the same inode. That means, they're the same command.
Thus:
if [ -f "$foo" ]
is the same as:
if test -f "$foo"
The if command executes the command given, and then will execute the if clause if the command returns true and doesn't execute the clause if the command it false.
For example:
if grep -q "foo" $foo
then
echo "File $foo contains the regular expression /foo/"
fi
Is completely valid. The grep -q command (in many variants of grep means search for the regular expression, and if that regular expression is in the file, return an exit code of 0 (which means the command succeeded and is true).
Note there is no square brackets.
The test command (or [...]) merely runs a test as specified, and returns with an exit code of 0 (thus the command is a success) if the test is true. That's all it does.
You may also see this construct:
[ "$foo" = "$bar" ] && echo "$foo is equal to $bar"
The && means execute the next command (and return the exit code) if the first command returns an exit code of zero. Otherwise, simply return the first command's exit code.
Thus:
if [ -e ./blah ] && [ ! -L ./blah ];
is saying run test -e ./blah and if that is true (that is, the file exists) execute test ! -L ./blah and if that is also true, run the if clause of the statement.
Note that [ -e ./blah] and [ ! -L ./blah ] are two separate commands. The && strings together the two commands:
[ "$foo" = "$bar" ] && some_command;
This says, run test "$foo" = "$bar" and if that is true, run the command some_command. Note that this is equivalent to:
if [ "$foo" = "$bar" ]
then
some_command
fi
The other list structure is the ||. This means that if the first command succeeds, return an exit code of 0 and don't run the second command. Thus:
[ "$foo" = "$bar" ] || some_command;
Is the same as:
if [ "$foo" = "$bar" ]
then
:
else
some_command
fi
Let's get back to your _original question:
if [ -e ./blah ] && [ ! -L ./blah ];
then
exit 1
fi
Is the same as:
if test -e ./blah && test ! -L ./blah
then
exit 1
fi
Which is the same as
test -e ./blah && test ! -L ./blah && exit 1
This means: If test -e ./blah is true (./blah is a file), then execute the command after the && list operator. This is test -! -L ./blah. If this test also is true, run the command after the && list operator again.
This could also be rewritten as:
test -e ./blah && test -L ./blah || exit 1
This says that if test -e ./blah is true, run the command after the && list operator. If test -L ./blah is false, run the command after the || operator.
Related
This question already has answers here:
Is there a list of 'if' switches anywhere?
(5 answers)
Closed 4 years ago.
What is is meaning of -a -z in
if [ -z "$ENV_VAR" -a -z "$ENV_VAR2"]; then
...
fi
bash conditional?
The first -z checks if $ENV_VAR defined according to
-z string True if the length of string is zero.
What does -a -z combination test with relation to ENV_VAR2?
according to the docs
-a file True if file exists.
however, ENV_VAR2 may contain text only, not a file name.
[ -z "$ENV_VAR" -a -z "$ENV_VAR2" ] has 2 conditions ANDed together using -a switch:
What it means is this:
-z "$ENV_VAR": $ENV_VAR is empty
-a: and
-z "$ENV_VAR2": $ENV_VAR2 is empty
btw if you're using bash you can refactor this condition to make it bit more succinct:
[[ -z $ENV_VAR && -z $ENV_VAR2 ]]
Please try this "man test".
Ideally, in that output, you'll see that -a performs an "AND" between two expressions.
It's "and".
See man test
EXPRESSION1 -a EXPRESSION2
both EXPRESSION1 and EXPRESSION2 are true
Examples:
$ [ -z "" -a -z "" ] && echo Hello
Hello
$ [[ -z "" -a -z "" ]] && echo Hello
bash: syntax error in conditional expression
bash: syntax error near `-a'
If used with single [ it is the "and" from test. If used with [[ it is the file check from bash.
The bash solution:
$ [[ -z "" && -z "" ]] && echo Hello
Hello
For POSIX compatibility, [[ ... && ... ]] is not available, but -a is considered obsolete (and optional) by POSIX, so use two separate [ commands instead.
if [ -z "$ENV_VAR" ] && [ -z "$ENV_VAR2" ]; then
...
fi
I have some problem with my code here. This is my code:
#!bin/sh
grep "$1" Rail.txt > test.txt
if [ "$#" -eq 1 -a grep -q "$1" test.txt ]; then
grep "$1" Rail.txt
else
echo not found
fi
Problem:
It says: script.sh: line 3: [: too many arguments every time I run it.
I'm not sure what's wrong with my condition whether I use the wrong operators or parenthesis or square brackets.
At a semi-educated guess, what you want to write is:
if [ "$#" -eq 1 ] && grep -q "$1" test.txt; then
On what ocassions should we use the square brackets?
Historically, the test command, /bin/test was linked to /bin/[, and was an external command, not a shell built-in. These days (and for several decades now), it has been a shell built-in. However, it follows the structure of a command, requiring spaces to separate arguments, and if it is invoked as [, then the last argument must be ].
As to when you use it, you use it when you need to test a condition.
Note that you can write:
if ls; false; true; then echo "the last command counts"; else echo "no it doesn't"; fi
The if command executes a sequence of one or more commands, and tests the exit status of the last command. If the exit status is 0, success, the then clause is executed; if the exit status is not 0, then the else clause is taken.
So, you can use the test when you need to test something. It can be part of an if or elif or while (or until). It can also be used on its own with || or &&:
[ -z "$1" ] && echo "No arguments - or the first argument is an empty string"
[ "$var1" -gt "$var2" ] || echo "Oops!" && exit 1
These could be written as if statements too, of course:
if [ -z "$1" ]
then echo "No arguments - or the first argument is an empty string"
fi
if [ "$var1" -le "$var2" ]
then
echo "Oops!"
exit 1
fi
Note that I needed to invert the test in the second rewrite. Bash has a built-in ! operator which inverts the exit status of the command that follows, so I could also have written:
if ! [ "$var1" -gt "$var2" ]
and test has a ! too, so it could also be written:
if [ ! "$var1" -gt "$var2" ]
I'm trying to write a short bash script that optionally accepts arguments from the command line, or prompts for their input
if [ [ -z "$message" ] && [ -z "$predefined" ] ] ; then
read -p "Enter message [$defaultMessage]: " message
message=${message:-$defaultMessage}
else
if [ -n "$predefined" ]; then
if [ -f $base/$environment/vle/data/$predefined.txt ]; then
echo Predefined message file $predefined.txt does not exist
exit 1
fi
fi
fi
If neither message nor predefined has been passed in as command line arguments, then the code should prompt for a value for message; otherwise if predefined has been passed in as a command line argument, then the script should test for the existence of a file of that name and only continue if the file does exist
But I'm getting the following error
[: -z: binary operator expected
at the first of those if tests
Any help in explaining what's wrong with my syntax for that first if statement? Or providing an alternative syntax to achieve the same objectives.
The first if is not well-formed. This would work:
if [ -z "$message" ] && [ -z "$predefined" ]; then
or this:
if test -z "$message" && test -z "$predefined"; then
or this bash-specific, easy but dirty way:
if [[ -z "$message" ]] && [[ -z "$predefined" ]]; then
or this bash-specific proper way:
if [[ -z $message && -z $predefined ]]; then
In this last version the double-quotes are unnecessary, not a typo.
Thanks #mklement0 for the corrections in the bash-specific style, and for this final comment:
I should note that there's one case where double-quoting is still a must inside [[ ... ]], namely if you want a variable reference on the right side of a string comparison (==) to be treated as a literal:
v='[a]'
[[ $v == $v ]] # FALSE!
[[ $v == "$v" ]] # true
Without double-quoting, the right-hand side is interpreted as a pattern. Some people advocate always double-quoting variable references so as not to have to remember such subtleties. That said (from bash 3.2 on), you must NOT double-quote the right operand when regex matching with =~
test expression1 -a expression2
is true if both expressions are true.
test expression1 -o expression2
is true if either or both expressions are true.
if [ -z "$message" -a -z "$predefined" ]; then
read -p "Enter message [$defaultMessage]: " message
message=${message:-$defaultMessage}
else
if [ -n "$predefined" -a -f $base/$environment/vle/data/$predefined.txt ]; then
echo Predefined message file $predefined.txt does not exist
exit 1
fi
fi
This was able to combine 4 test into 2 while also getting rid of one nested if expression; then ; fi
I found this answer which works fine, but I wanted to understand why the following code won't detect the presence of two files?
if [[ $(test -e ./file1 && test -e ./file2) ]]; then
echo "yep"
else
echo "nope"
fi
Running this directly from the shell works as expected:
test -e ./file1 && test -e ./file2 && echo yes
The output of test -e ./file1 && test -e ./file2 is an empty string, which causes [[ ]] to produce a non-zero exit code. You want
if [[ -e ./file1 && -e ./file2 ]]; then
echo "yep"
else
echo "nope"
fi
[[ ... ]] is a replacement for [ ... ] or test ..., not a wrapper around it.
if executes a program (or builtin, in the case of [[) and branches based on its return value. You need to omit either the [[ ]] or the tests:
if [[ -e ./file1 && -e ./file2 ]]; then
Or
if test -e ./file1 && test -e ./file2; then
why does:
#!/bin/bash
wtf=false
if [ $wtf ] || [ ! -f filethatexists.whatever ]
then
echo "WTF1"
fi
if [ ! -f filethatexists.whatever ]
then
echo "WTF2"
fi
print:
WTF1
instead of nothing? It is especially perplexing that the second form works as expected and the first not.
The basic test
[ $wtf ]
tests whether the string in the middle is empty or not.
Since $wtf contains the string 'false', the test returns true, or exit status 0 for success, because 'false' is not the same as the empty string '' — and hence you get WTF1 as the response.
Try with:
wtf=''
As pointed out by Gordon Davisson (and Dennis Williamson), it is a good idea to be careful with strings that you are testing. Indeed, I should have stated that I would always use [ -n "$wtf" ] or [ -z "$wtf" ] to test whether a variable is set, because that was necessary when I was learning shell, once upon a quarter century ago. I've had counter stories from Bash afficionados that you don't have to worry about it in Bash - however, I think the code here provides a counter-example that in fact you do still have to worry about it.
So, some best practices:
Enclose tested variables in double quotes, or
(In Bash), use [[ $wtf ]] which does know how to handle the variable expansion.
Use the -n or -z tests to test for non-empty or empty values.
There can be exceptions to the rules - but you will not go far wrong following them.
Consider the code:
wtf="1 -eq 0"
[ $wtf ] && echo "WTF0"
[[ $wtf ]] && echo "WTF1"
wtf="false"
[ $wtf ] && echo "WTF2"
[[ $wtf ]] && echo "WTF3"
wtf=""
[ $wtf ] && echo "WTF4"
[[ $wtf ]] && echo "WTF5"
wtf="false"
[ "$wtf" ] && echo "WTF6"
[[ "$wtf" ]] && echo "WTF7"
wtf=""
[ "$wtf" ] && echo "WTF8"
[[ "$wtf" ]] && echo "WTF9"
That produces:
WTF1
WTF2
WTF3
WTF6
WTF7
with both bash and ksh (as found on MacOS X 10.6.4, when run with 'bash testcode.sh' or 'ksh testcode.sh'). A real Bourne shell (if you can still find such a thing) would object to the double-bracket operations - it would not be able to find the command '[[' on $PATH.
You can extend the testing to cover more cases ad nauseam.
Here's a handy little trick:
wtf=false
if $wtf || [ ! -f filethatexists.whatever ]
In this form, the contents of the variable are executed and the return value determines whether the test passes or fails. It happens that true and false are Bash builtins that return the appropriate value.
if [ $wtf = true ] || [ ! -f . .