I see I can do
$ [ -w /home/durrantm ] && echo "writable" writable
or
$ test -w /home/durrantm && echo "writable" writable
or
$ [[ -w /home/durrantm ]] && echo "writable" writable
I like using the third syntax. Are they equivalent in all ways and for all negative and edge cases? Are there any differences in portability, e.g. between bash on Ubuntu and on OS X or older/newer bash versions, e.g. before/after 4.0 and do they both expand expressions the same way?
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
Yes, there are differences. The most portable are test or [ ]. These are both part of the POSIX test specification.
The if ... fi construct is also defined by POSIX and should be completely portable.
The [[ ]] is a ksh feature that is also present in some versions of bash (all modern ones), in zsh and perhaps in others but is not present in sh or dash or the various other simpler shells.
So, to make your scripts portable, use [ ], test or if ... fi.
Method 2
[ is synonym of the test command and it is simultaneously a bash builtin and separate command. But [[ is a bash keyword and works in some versions only. So for reasons of portability you are better off using single [] or test
[ -w "/home/durrantm" ] && echo "writable"
Method 3
Please note, that [] && cmd is not the same as if .. fi construction.
Sometimes its behaviour its pretty similar and you can use [] && cmd instead of if .. fi. But only sometimes. If you have more then one command to execute if condition or you need if .. else .. fi be careful and whatch the logic.
A couple of examples:
[ -z "$VAR" ] && ls file || echo wiiii
Is not the same as
if [ -z $VAR ] ; then ls file else echo wiii fi
because if ls will fail, echo will be executed which will not happen with if.
Another example:
[ -z "$VAR" ] && ls file && echo wiii
is not the same as
if [ -z "$VAR" ] ; then ls file echo $wiii fi
though this construction will act the same
[ -z "$VAR" ] && { ls file ; echo wiii ; }
please note ; after echo is important and must be there.
So resuming statement above we can say
[] && cmd == if first command is successful then execute the next one
if .. fi == if condition (which may be the test command as well) then execute command(s)
So for portability between [ and [[ use [ only.
if is POSIX compatible. So if you have to choose between [ and if choose looking at your task and expected behaviour.
Method 4
It’s actually the && that is replacing the if, not the test: an if statement in shell scripting tests whether a command returned a “successful” (zero) exit status; in your example, the command is [.
So, there are actually two things you are varying here: the command used to run the test, and the syntax used to execute code based on the result of that test.
Test commands:
testis a standardised command for evaluating properties of strings and files; in your example, you are running the commandtest -w /home/durrantm[is an alias of that command, equally standardised, which has a mandatory last argument of]in order to look like a bracketed expression; don’t be fooled, it’s still just a command (you may even find that your system has a file called/bin/[)[[is an extended version of the test command built into some shells, but not part of the same POSIX standard; it includes extra options which you are not using here
Conditional expressions:
- The
&&operator (standardised here) performs a logical AND operation, by evaluating two commands and returning 0 (which represents true) if they both return 0; it will only evaluate the second command if the first one returned zero, so it can be used as a simple conditional - The
if ... then ... ficonstruct (standardised here) uses the same method of judging “truth”, but allows for a compound list of statements in thethenclause, rather than the single command afforded by an&&short-circuit, and provideselifandelseclauses, which are hard to write using only&&and||. Note that there are no brackets around the condition in anifstatement.
So, the following are all equally portable, and entirely equivalent, renderings of your example:
test -w /home/durrantm && echo "writable"[ -w /home/durrantm ] && echo "writable"if test -w /home/durrantm; then echo "writable"; fiif [ -w /home/durrantm ]; then echo "writable"; fi
While the following are also equivalent, but less portable due to the non-standard nature of [[:
[[ -w /home/durrantm ]] && echo "writable"if [[ -w /home/durrantm ]]; then echo "writable"; fi
Method 5
For portability, use test / [. But if you don’t need the portability, for the sake of the sanity of yourself and others reading your script use [[. 🙂
Also see What is the difference between test, [ and [[ ? in the BashFAQ.
Method 6
If you want portability outside the Bourne-like world, then:
test -w /home/durrantm && echo writable
is the most portable. It works in shells of the Bourne, csh and rc families.
test -w /home/durrantm && echo "writable"
would output "writable" instead of writable in shells of the rc family (rc, es, akanga, where " is not special).
[ -w /home/durrantm ] && echo writable
would not work in shells of the csh or rc families on systems that don’t have a [ command in $PATH (some have been known to have test but not its [ alias).
if [ -w /home/durrantm ]; then echo writabe; fi
only works in shells of the Bourne family.
[[ -w /home/durrantm ]] && echo writable
only works in ksh (where it originated), zsh and bash (all 3 in the Bourne family).
None would work in the fish shell where you need:
[ -w /home/durrantm ]; and echo writable
or:
if [ -w /home/durrantm ]; echo writable; end
Method 7
My most important reason for choosing either if foo; then bar; fi or foo && bar is whether the exit status of the whole command is important.
compare:
#!/bin/sh set -e foo && bar do_baz
with:
#!/bin/sh set -e if foo; then bar; fi do_baz
You might think they do the same; however if foo fails (or is false, depending on your point of view) then in the first example do_baz will not be executed, as the script will have exited… The set -e instructs the shell to exit immediately if any command return a false status. Very useful if you’re doing things like:
cd /some/directory rm -rf *
You don’t want the script to continue running if the cd fails for whatever reason.
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0