Is test or [ or [[ more portable both between bash shells and between other shells?

11,514

Solution 1

[ 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"

Solution 2

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.

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

Solution 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:

  • test is a standardised command for evaluating properties of strings and files; in your example, you are running the command test -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 ... fi construct (standardised here) uses the same method of judging "truth", but allows for a compound list of statements in the then clause, rather than the single command afforded by an && short-circuit, and provides elif and else clauses, which are hard to write using only && and ||. Note that there are no brackets around the condition in an if statement.

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"; fi
  • if [ -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

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

Share:
11,514

Related videos on Youtube

Michael Durrant
Author by

Michael Durrant

rails ruby rspec rock

Updated on September 18, 2022

Comments

  • Michael Durrant
    Michael Durrant almost 2 years

    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?

    • Gilles 'SO- stop being evil'
      Gilles 'SO- stop being evil' over 9 years
      For [ … ] vs [[ … ]] vs test …, there are more complete answers in this mostly duplicate question.
    • G-Man Says 'Reinstate Monica'
      G-Man Says 'Reinstate Monica' over 9 years
      For the specific issue of testing for writability of a file, see also How to non-invasively test for write access to a file?
    • Iswarya Swaminadhan
      Iswarya Swaminadhan over 9 years
      There is a saying: "there is no portable code, only code that has been ported". My advice with regards to this: Use the most readable form (probably [[ ... ]]) and try it on all platforms you want to support. There is not much use in obscuring your scripts so they run on ancient platforms that neither you nor your target audience uses. It will just make your code hard to read, introduce unnecessary bugs and maybe even security problems (like it did for openssl).
  • chepner
    chepner over 9 years
    Just to note, bash and zsh have supported [[ for a very long time (bash added it in the late 90s, zsh no later than 2000, and I'd be surprised if it ever lacked support), so you are unlikely to encounter a version of either without [[. Encountering a different POSIX-compliant shell (such as dash) is far more likely.
  • evilsoup
    evilsoup over 9 years
    I'm probably just being dense, but I'm not seeing what the difference would be... could you please give an example where those two constructions would give different results?
  • rush
    rush over 9 years
    @evilsoup, updated. May be I'm not the best explainer, though I hope it will be clear now.
  • PM 2Ring
    PM 2Ring over 9 years
    Very good points, rush. I suppose a safe form of your 1st example would be: [ -z "$VAR" ] && { ls file; true; } || echo wiiii. It's a bit more verbose, but it's still shorter than the if...fi construction.
  • Stéphane Chazelas
    Stéphane Chazelas over 9 years
    No a failing foo will not abort the script in either case. That's a special case for set -e (when the command is evaluated as a condition (to the left of some &&/|| or in if/while/until/elsif... conditions). A failing bar would exit the shell in both cases.
  • Stéphane Chazelas
    Stéphane Chazelas over 9 years
    You want cd /some/directory && rm -rf -- * or cd /some/directory || exit; rm -rf -- * (still doesn't remove hidden files). I personally don't like the idea of using set -e as an excuse not to make an effort to write correct code.
  • helpermethod
    helpermethod over 9 years
    One interesting feature of [[ is that parameter expansions don't have to be quoted: [[-f $file]] event works if $file contains whitespace characters.
  • Michael Durrant
    Michael Durrant over 9 years
    Yes I removed the if....fi part to make this be 1 question.
  • Michael Durrant
    Michael Durrant over 9 years
    Made the question be about [ vs [[ vs test and not also about if...fi vs && to make it be ONE question. Unfortunately doing that makes this answer seem off-point. Apologies for not getting the question m ore focused initially that led to this. Live and (try to) learn. :)
  • GnP
    GnP over 9 years
    @helpermethod also, builtin regular expressions [[ $a =~ ^reg.*exp.*$' ]]
  • GnP
    GnP over 9 years
    I downvoted because a) this is mainly a good answer, but it is an answer to different question. b) you present [ and if as subtitutes, but they aren't. It's actually the && that's replacing the if. [ executes some code and returns a status, much like ls and grep would. if branches execution depending on the return status of the command (statement) given after the if, it could be any command (statement). && executes the next statement only if the previous one returned 0, much like a simple if..then..fi.
  • rush
    rush over 9 years
    @gnp, well. a) please check comment from Michael. There is a short explanation that initially there was a different question. I just left the answer for history. b) The answer was mostly about difference between && .. || and if .. else .. fi.
  • GnP
    GnP over 9 years
    @rush you're right, I missed the edit history there. My apologies. As I said, this is mainly a good answer, so I rectified my vote.
  • Sandburg
    Sandburg over 5 years
    [is POSIX builtin or Bash builtin ?
  • Bachsau
    Bachsau about 5 years
    @Sandburg POSIX is a standard, so it can't have built-ins. It doesn't define if something should be built into the interpreter or not, just what it has to do. These rules apply to both forms, test and [ alike. If a shell can do the evaluation on its own, that saves you a process and makes it somewhat faster, but the result has to be the same.
  • JamesTheAwesomeDude
    JamesTheAwesomeDude almost 5 years
    @Bachsau that still doesn't answer Sandburg's question as to whether or not the behavior of [ is defined by POSIX and therefore maximally portable (which seems to be the entire point of this question if I'm not mistaken)
  • JamesTheAwesomeDude
    JamesTheAwesomeDude almost 5 years
    @Sandburg (and to anyone else stumbling across this): Yes, it looks like both [ and test are POSIX. There seems to be neither "recommended", though the only difference(?) I can spot between them is that test "shall not recognize the "--" argument [as a delimiter indicating the end of options]" (?- implying that "--" is recognized as end-of-arguments for [, which I can't actually come up with a good test case for anyway. But I digress. Use which you will. IMO, [ looks elegant,but test isclearly a command)