What's the purpose of adding a prefix on both sides of a shell variable comparison to a string literal?

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP












4














I have encountered comparisions of variables to string literals multiple times over the years which had one character prefixing the variable and the literal, e.g.



if [ "x$A" = "xtrue" ]; then


in order to check whether $A is "true".



I assume this is done to achieve shell compatibility or to work around a longterm bug, an unintuitive behavior, etc. Nothing obvious comes to mind.



Today I figured I want to know the reason, but my research didn't turn up anything. Or maybe it's just me making something out of a rather frequent exposure to rare occurances.



Is this practice still useful, maybe even best?










share|improve this question



















  • 2




    Similar: bash script [ x$1 = x ]
    – Stéphane Chazelas
    Dec 21 '18 at 20:52










  • See also Does the syntax of not equal matter?
    – Stéphane Chazelas
    Dec 21 '18 at 20:58










  • see also stackoverflow.com/questions/18242822/…
    – lesmana
    Dec 24 '18 at 19:19















4














I have encountered comparisions of variables to string literals multiple times over the years which had one character prefixing the variable and the literal, e.g.



if [ "x$A" = "xtrue" ]; then


in order to check whether $A is "true".



I assume this is done to achieve shell compatibility or to work around a longterm bug, an unintuitive behavior, etc. Nothing obvious comes to mind.



Today I figured I want to know the reason, but my research didn't turn up anything. Or maybe it's just me making something out of a rather frequent exposure to rare occurances.



Is this practice still useful, maybe even best?










share|improve this question



















  • 2




    Similar: bash script [ x$1 = x ]
    – Stéphane Chazelas
    Dec 21 '18 at 20:52










  • See also Does the syntax of not equal matter?
    – Stéphane Chazelas
    Dec 21 '18 at 20:58










  • see also stackoverflow.com/questions/18242822/…
    – lesmana
    Dec 24 '18 at 19:19













4












4








4


3





I have encountered comparisions of variables to string literals multiple times over the years which had one character prefixing the variable and the literal, e.g.



if [ "x$A" = "xtrue" ]; then


in order to check whether $A is "true".



I assume this is done to achieve shell compatibility or to work around a longterm bug, an unintuitive behavior, etc. Nothing obvious comes to mind.



Today I figured I want to know the reason, but my research didn't turn up anything. Or maybe it's just me making something out of a rather frequent exposure to rare occurances.



Is this practice still useful, maybe even best?










share|improve this question















I have encountered comparisions of variables to string literals multiple times over the years which had one character prefixing the variable and the literal, e.g.



if [ "x$A" = "xtrue" ]; then


in order to check whether $A is "true".



I assume this is done to achieve shell compatibility or to work around a longterm bug, an unintuitive behavior, etc. Nothing obvious comes to mind.



Today I figured I want to know the reason, but my research didn't turn up anything. Or maybe it's just me making something out of a rather frequent exposure to rare occurances.



Is this practice still useful, maybe even best?







shell variable string test






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Dec 21 '18 at 20:54









Jeff Schaller

38.9k1053125




38.9k1053125










asked Dec 21 '18 at 20:28









Karl Richter

7781823




7781823







  • 2




    Similar: bash script [ x$1 = x ]
    – Stéphane Chazelas
    Dec 21 '18 at 20:52










  • See also Does the syntax of not equal matter?
    – Stéphane Chazelas
    Dec 21 '18 at 20:58










  • see also stackoverflow.com/questions/18242822/…
    – lesmana
    Dec 24 '18 at 19:19












  • 2




    Similar: bash script [ x$1 = x ]
    – Stéphane Chazelas
    Dec 21 '18 at 20:52










  • See also Does the syntax of not equal matter?
    – Stéphane Chazelas
    Dec 21 '18 at 20:58










  • see also stackoverflow.com/questions/18242822/…
    – lesmana
    Dec 24 '18 at 19:19







2




2




Similar: bash script [ x$1 = x ]
– Stéphane Chazelas
Dec 21 '18 at 20:52




Similar: bash script [ x$1 = x ]
– Stéphane Chazelas
Dec 21 '18 at 20:52












See also Does the syntax of not equal matter?
– Stéphane Chazelas
Dec 21 '18 at 20:58




See also Does the syntax of not equal matter?
– Stéphane Chazelas
Dec 21 '18 at 20:58












see also stackoverflow.com/questions/18242822/…
– lesmana
Dec 24 '18 at 19:19




see also stackoverflow.com/questions/18242822/…
– lesmana
Dec 24 '18 at 19:19










2 Answers
2






active

oldest

votes


















10














The important thing to understand here is that in most shells¹, [ is just an ordinary command parsed by the shell like any other ordinary command.



Then the shell invokes that [ (aka test) command with a list of arguments, and then it's up to [ to interpret them as a conditional expression.



At that point, those are just a list of strings and the information about which ones resulted from some form of expansion is lost, even in those shells where [ is built-in (all Bourne-like ones these days).



The [ utility used to have a hard time telling which ones of its arguments were operators and which ones were operands (the thing operators work on). It didn't help that the syntax was intrinsically ambiguous. For instance:




  • [ -t ] used to be (and still is in some shells/[s) to test whether stdout is a terminal.


  • [ x ] is short for [ -n x ]: test whether x is a non-empty string (so you can see there's a conflict with the above).

  • in some shells/[s, -a and -o can be both unary ([ -a file ] for accessible file (now replaced by [ -e file ]), [ -o option ] for is the option enabled?) and binary operators (and and or). Again, ! -a x can be either and(nonempty("!"), nonempty("x") or not(isaccenssible("x")).


  • (, ) and ! add more problems.

In normal programming languages like C or perl, in:



if ($a eq $b) ...


There's no way the content of $a or $b will be taken as operators because the conditional expression is parsed before those $a and $b are expanded. But in shells, in:



[ "$a" = "$b" ]


The shell expands the variables first². For instance, if $a contains ( and $b contains ), all the [ command sees is [, (, =, ) and ] arguments. So does that means "(" = ")" (are ( and ) lexically equal) or ( -n = ) (is = a non-empty string).



Historical implementations (test appeared in Unix V7 in the late 70s) used to fail even in cases where it was not ambiguous just because of the order in which they were processing their arguments.



Here with version 7 Unix in a PDP11 emulator:



$ ls -l /bin/[
-rwxr-xr-x 2 bin 2876 Jun 8 1979 /bin/[
$ [ ! = x ]
test: argument expected
$ [ "(" = x ]
test: argument expected


Most shell and [ implementations have or have had problems with those or variants thereof. With bash 4.4 today:



bash-4.4$ a='(' b=-o c=x
bash-4.4$ [ "$a" = "$b" -o "$a" = "$c" ]
bash: [: `)' expected, found =


POSIX (in the early 90s) devised an algorithm that would make ['s behaviour unambiguous and deterministic when passed at most 4 arguments (beside [ and ]) in the most common usage patterns ([ -f "$a" -o "$b" ] still unspecified for instance). It deprecated (, ), -a and -o, and dropped -t without operand. bash did implement that algorithm (or at least tried to) in bash 2.0.



So, in POSIX compliant [ implementations, [ "$a" = "$b" ] is guaranteed to compare the content of $a and $b for equality, whatever they are. Without -o, we would write:



[ "$a" = "$b" ] || [ "$a" = "$c" ]


That is, call [ twice, each time with fewer than 5 arguments.



But it took quite a while for all [ implementations to become compliant. bash's was not compliant until 4.4 (though the last problem was for [ '(' ! "$var" ')' ] which nobody would really use in real life)



The /bin/sh of Solaris 10 and older, which is not a POSIX shell, but a Bourne shell still has problems with [ "$a" = "$b" ]:



$ a='!' b='!'
$ [ "$a" = "$b" ]
test: argument expected


Using [ "x$a" = "x$b" ] works around the problem as there is no [ operator that starts with x. Another option is to use case instead:



case "$a" in
"$b") echo same;;
*) echo different;;
esac


(quoting is necessary around $b, not around $a).



In any case, it is not and never has been about empty values. People have problems with empty values in [ when they forget to quote their variables, but that's not a problem with [ then.



$ a= b='-o x'
[ $a = $b ]


with the default value of $IFS becomes:



[ = -o x ]


Which is a test of whether = or x is a non-empty string, but no amount of prefixing will help³ as [ x$a = x$b ] will still be: [ x = x-o x ] which would cause an error, and it could get a lot worse including DoS and arbitrary command injection with other values like in bash:



bash-4.4$ a= b='x -o -v a[`uname>&2`]'
bash-4.4$ [ x$a = x$b ]
Linux


The correct solution is to always quote:



[ "$a" = "$b" ] # OK in POSIX shells
[ "x$a" = "x$b" ] # OK in all Bourne-like shells


Note that expr has similar (and even worse) problems.



expr also has a = operator, though it's for testing whether the two operatands are equal integers when they look like decimal integer numbers, or sort the same when not.



In many implementations, expr + = +, or expr '(' = ')' or expr index = index don't do equality comparison. expr "x$a" = "x$b" would work around it for string comparison, but prefixing with an x could affect the sorting (in locales that have collating elements starting with x for instance) and obviously can't be used for number comparison expr "0$a" = "0$b" doesn't work for comparing negative integers. expr " $a" = " $b"
works for integer comparison in some implementations, but not others (for a=01 b=1, some would return true, some false).




¹ ksh93 is an exception. In ksh93, [ can be seen as a reserved word in that [ -t ] is actually different from var=-t; [ "$var" ], or from ""[ -t ] or cmd='['; "$cmd" -t ]. That's to preserve backward compatibility and still be POSIX compliant in cases where it matters. The -t is only taken as an operator here if it's literal, and ksh93 detects that you're calling the [ command.



² ksh added a [[...]] conditional expression operator with its own syntax parsing rules (and some problems of its own) to address that (also found in some other shells, with some differences).



³ except in zsh where split+glob is not invoked upon parameter expansion, but empty removal still is, or in other shells when disabling split+glob globally with set -o noglob; IFS=






share|improve this answer






























    5














    People often ascribe the prefix to problems with empty strings, but that is not the reason for it. The problem is a very simple one: the expansion of the variable could be one of test's operators, suddenly turning a binary equality test into a different expression.



    Recent implementations of the command on most platforms avoid the pitfall with look-ahead in the expression parser, preventing the parser from recognizing the first operand to a binary operator as anything other than an operand, as long as there are enough tokens to be a binary operator of course:


    % a=-n
    % /bin/test "$a" = -n ; echo $?
    0
    % /bin/test "$a" = ; echo $?
    0
    % /bin/test x"$a" = ; echo $?
    test: =: argument expected
    2
    % a='('
    % /bin/test "$a" = "(" ; echo $?
    0
    % /bin/test "$a" = ; echo $?
    test: closing paren expected
    2
    %





    share|improve this answer




















      Your Answer








      StackExchange.ready(function()
      var channelOptions =
      tags: "".split(" "),
      id: "106"
      ;
      initTagRenderer("".split(" "), "".split(" "), channelOptions);

      StackExchange.using("externalEditor", function()
      // Have to fire editor after snippets, if snippets enabled
      if (StackExchange.settings.snippets.snippetsEnabled)
      StackExchange.using("snippets", function()
      createEditor();
      );

      else
      createEditor();

      );

      function createEditor()
      StackExchange.prepareEditor(
      heartbeatType: 'answer',
      autoActivateHeartbeat: false,
      convertImagesToLinks: false,
      noModals: true,
      showLowRepImageUploadWarning: true,
      reputationToPostImages: null,
      bindNavPrevention: true,
      postfix: "",
      imageUploader:
      brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
      contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
      allowUrls: true
      ,
      onDemand: true,
      discardSelector: ".discard-answer"
      ,immediatelyShowMarkdownHelp:true
      );



      );













      draft saved

      draft discarded


















      StackExchange.ready(
      function ()
      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f490393%2fwhats-the-purpose-of-adding-a-prefix-on-both-sides-of-a-shell-variable-comparis%23new-answer', 'question_page');

      );

      Post as a guest















      Required, but never shown

























      2 Answers
      2






      active

      oldest

      votes








      2 Answers
      2






      active

      oldest

      votes









      active

      oldest

      votes






      active

      oldest

      votes









      10














      The important thing to understand here is that in most shells¹, [ is just an ordinary command parsed by the shell like any other ordinary command.



      Then the shell invokes that [ (aka test) command with a list of arguments, and then it's up to [ to interpret them as a conditional expression.



      At that point, those are just a list of strings and the information about which ones resulted from some form of expansion is lost, even in those shells where [ is built-in (all Bourne-like ones these days).



      The [ utility used to have a hard time telling which ones of its arguments were operators and which ones were operands (the thing operators work on). It didn't help that the syntax was intrinsically ambiguous. For instance:




      • [ -t ] used to be (and still is in some shells/[s) to test whether stdout is a terminal.


      • [ x ] is short for [ -n x ]: test whether x is a non-empty string (so you can see there's a conflict with the above).

      • in some shells/[s, -a and -o can be both unary ([ -a file ] for accessible file (now replaced by [ -e file ]), [ -o option ] for is the option enabled?) and binary operators (and and or). Again, ! -a x can be either and(nonempty("!"), nonempty("x") or not(isaccenssible("x")).


      • (, ) and ! add more problems.

      In normal programming languages like C or perl, in:



      if ($a eq $b) ...


      There's no way the content of $a or $b will be taken as operators because the conditional expression is parsed before those $a and $b are expanded. But in shells, in:



      [ "$a" = "$b" ]


      The shell expands the variables first². For instance, if $a contains ( and $b contains ), all the [ command sees is [, (, =, ) and ] arguments. So does that means "(" = ")" (are ( and ) lexically equal) or ( -n = ) (is = a non-empty string).



      Historical implementations (test appeared in Unix V7 in the late 70s) used to fail even in cases where it was not ambiguous just because of the order in which they were processing their arguments.



      Here with version 7 Unix in a PDP11 emulator:



      $ ls -l /bin/[
      -rwxr-xr-x 2 bin 2876 Jun 8 1979 /bin/[
      $ [ ! = x ]
      test: argument expected
      $ [ "(" = x ]
      test: argument expected


      Most shell and [ implementations have or have had problems with those or variants thereof. With bash 4.4 today:



      bash-4.4$ a='(' b=-o c=x
      bash-4.4$ [ "$a" = "$b" -o "$a" = "$c" ]
      bash: [: `)' expected, found =


      POSIX (in the early 90s) devised an algorithm that would make ['s behaviour unambiguous and deterministic when passed at most 4 arguments (beside [ and ]) in the most common usage patterns ([ -f "$a" -o "$b" ] still unspecified for instance). It deprecated (, ), -a and -o, and dropped -t without operand. bash did implement that algorithm (or at least tried to) in bash 2.0.



      So, in POSIX compliant [ implementations, [ "$a" = "$b" ] is guaranteed to compare the content of $a and $b for equality, whatever they are. Without -o, we would write:



      [ "$a" = "$b" ] || [ "$a" = "$c" ]


      That is, call [ twice, each time with fewer than 5 arguments.



      But it took quite a while for all [ implementations to become compliant. bash's was not compliant until 4.4 (though the last problem was for [ '(' ! "$var" ')' ] which nobody would really use in real life)



      The /bin/sh of Solaris 10 and older, which is not a POSIX shell, but a Bourne shell still has problems with [ "$a" = "$b" ]:



      $ a='!' b='!'
      $ [ "$a" = "$b" ]
      test: argument expected


      Using [ "x$a" = "x$b" ] works around the problem as there is no [ operator that starts with x. Another option is to use case instead:



      case "$a" in
      "$b") echo same;;
      *) echo different;;
      esac


      (quoting is necessary around $b, not around $a).



      In any case, it is not and never has been about empty values. People have problems with empty values in [ when they forget to quote their variables, but that's not a problem with [ then.



      $ a= b='-o x'
      [ $a = $b ]


      with the default value of $IFS becomes:



      [ = -o x ]


      Which is a test of whether = or x is a non-empty string, but no amount of prefixing will help³ as [ x$a = x$b ] will still be: [ x = x-o x ] which would cause an error, and it could get a lot worse including DoS and arbitrary command injection with other values like in bash:



      bash-4.4$ a= b='x -o -v a[`uname>&2`]'
      bash-4.4$ [ x$a = x$b ]
      Linux


      The correct solution is to always quote:



      [ "$a" = "$b" ] # OK in POSIX shells
      [ "x$a" = "x$b" ] # OK in all Bourne-like shells


      Note that expr has similar (and even worse) problems.



      expr also has a = operator, though it's for testing whether the two operatands are equal integers when they look like decimal integer numbers, or sort the same when not.



      In many implementations, expr + = +, or expr '(' = ')' or expr index = index don't do equality comparison. expr "x$a" = "x$b" would work around it for string comparison, but prefixing with an x could affect the sorting (in locales that have collating elements starting with x for instance) and obviously can't be used for number comparison expr "0$a" = "0$b" doesn't work for comparing negative integers. expr " $a" = " $b"
      works for integer comparison in some implementations, but not others (for a=01 b=1, some would return true, some false).




      ¹ ksh93 is an exception. In ksh93, [ can be seen as a reserved word in that [ -t ] is actually different from var=-t; [ "$var" ], or from ""[ -t ] or cmd='['; "$cmd" -t ]. That's to preserve backward compatibility and still be POSIX compliant in cases where it matters. The -t is only taken as an operator here if it's literal, and ksh93 detects that you're calling the [ command.



      ² ksh added a [[...]] conditional expression operator with its own syntax parsing rules (and some problems of its own) to address that (also found in some other shells, with some differences).



      ³ except in zsh where split+glob is not invoked upon parameter expansion, but empty removal still is, or in other shells when disabling split+glob globally with set -o noglob; IFS=






      share|improve this answer



























        10














        The important thing to understand here is that in most shells¹, [ is just an ordinary command parsed by the shell like any other ordinary command.



        Then the shell invokes that [ (aka test) command with a list of arguments, and then it's up to [ to interpret them as a conditional expression.



        At that point, those are just a list of strings and the information about which ones resulted from some form of expansion is lost, even in those shells where [ is built-in (all Bourne-like ones these days).



        The [ utility used to have a hard time telling which ones of its arguments were operators and which ones were operands (the thing operators work on). It didn't help that the syntax was intrinsically ambiguous. For instance:




        • [ -t ] used to be (and still is in some shells/[s) to test whether stdout is a terminal.


        • [ x ] is short for [ -n x ]: test whether x is a non-empty string (so you can see there's a conflict with the above).

        • in some shells/[s, -a and -o can be both unary ([ -a file ] for accessible file (now replaced by [ -e file ]), [ -o option ] for is the option enabled?) and binary operators (and and or). Again, ! -a x can be either and(nonempty("!"), nonempty("x") or not(isaccenssible("x")).


        • (, ) and ! add more problems.

        In normal programming languages like C or perl, in:



        if ($a eq $b) ...


        There's no way the content of $a or $b will be taken as operators because the conditional expression is parsed before those $a and $b are expanded. But in shells, in:



        [ "$a" = "$b" ]


        The shell expands the variables first². For instance, if $a contains ( and $b contains ), all the [ command sees is [, (, =, ) and ] arguments. So does that means "(" = ")" (are ( and ) lexically equal) or ( -n = ) (is = a non-empty string).



        Historical implementations (test appeared in Unix V7 in the late 70s) used to fail even in cases where it was not ambiguous just because of the order in which they were processing their arguments.



        Here with version 7 Unix in a PDP11 emulator:



        $ ls -l /bin/[
        -rwxr-xr-x 2 bin 2876 Jun 8 1979 /bin/[
        $ [ ! = x ]
        test: argument expected
        $ [ "(" = x ]
        test: argument expected


        Most shell and [ implementations have or have had problems with those or variants thereof. With bash 4.4 today:



        bash-4.4$ a='(' b=-o c=x
        bash-4.4$ [ "$a" = "$b" -o "$a" = "$c" ]
        bash: [: `)' expected, found =


        POSIX (in the early 90s) devised an algorithm that would make ['s behaviour unambiguous and deterministic when passed at most 4 arguments (beside [ and ]) in the most common usage patterns ([ -f "$a" -o "$b" ] still unspecified for instance). It deprecated (, ), -a and -o, and dropped -t without operand. bash did implement that algorithm (or at least tried to) in bash 2.0.



        So, in POSIX compliant [ implementations, [ "$a" = "$b" ] is guaranteed to compare the content of $a and $b for equality, whatever they are. Without -o, we would write:



        [ "$a" = "$b" ] || [ "$a" = "$c" ]


        That is, call [ twice, each time with fewer than 5 arguments.



        But it took quite a while for all [ implementations to become compliant. bash's was not compliant until 4.4 (though the last problem was for [ '(' ! "$var" ')' ] which nobody would really use in real life)



        The /bin/sh of Solaris 10 and older, which is not a POSIX shell, but a Bourne shell still has problems with [ "$a" = "$b" ]:



        $ a='!' b='!'
        $ [ "$a" = "$b" ]
        test: argument expected


        Using [ "x$a" = "x$b" ] works around the problem as there is no [ operator that starts with x. Another option is to use case instead:



        case "$a" in
        "$b") echo same;;
        *) echo different;;
        esac


        (quoting is necessary around $b, not around $a).



        In any case, it is not and never has been about empty values. People have problems with empty values in [ when they forget to quote their variables, but that's not a problem with [ then.



        $ a= b='-o x'
        [ $a = $b ]


        with the default value of $IFS becomes:



        [ = -o x ]


        Which is a test of whether = or x is a non-empty string, but no amount of prefixing will help³ as [ x$a = x$b ] will still be: [ x = x-o x ] which would cause an error, and it could get a lot worse including DoS and arbitrary command injection with other values like in bash:



        bash-4.4$ a= b='x -o -v a[`uname>&2`]'
        bash-4.4$ [ x$a = x$b ]
        Linux


        The correct solution is to always quote:



        [ "$a" = "$b" ] # OK in POSIX shells
        [ "x$a" = "x$b" ] # OK in all Bourne-like shells


        Note that expr has similar (and even worse) problems.



        expr also has a = operator, though it's for testing whether the two operatands are equal integers when they look like decimal integer numbers, or sort the same when not.



        In many implementations, expr + = +, or expr '(' = ')' or expr index = index don't do equality comparison. expr "x$a" = "x$b" would work around it for string comparison, but prefixing with an x could affect the sorting (in locales that have collating elements starting with x for instance) and obviously can't be used for number comparison expr "0$a" = "0$b" doesn't work for comparing negative integers. expr " $a" = " $b"
        works for integer comparison in some implementations, but not others (for a=01 b=1, some would return true, some false).




        ¹ ksh93 is an exception. In ksh93, [ can be seen as a reserved word in that [ -t ] is actually different from var=-t; [ "$var" ], or from ""[ -t ] or cmd='['; "$cmd" -t ]. That's to preserve backward compatibility and still be POSIX compliant in cases where it matters. The -t is only taken as an operator here if it's literal, and ksh93 detects that you're calling the [ command.



        ² ksh added a [[...]] conditional expression operator with its own syntax parsing rules (and some problems of its own) to address that (also found in some other shells, with some differences).



        ³ except in zsh where split+glob is not invoked upon parameter expansion, but empty removal still is, or in other shells when disabling split+glob globally with set -o noglob; IFS=






        share|improve this answer

























          10












          10








          10






          The important thing to understand here is that in most shells¹, [ is just an ordinary command parsed by the shell like any other ordinary command.



          Then the shell invokes that [ (aka test) command with a list of arguments, and then it's up to [ to interpret them as a conditional expression.



          At that point, those are just a list of strings and the information about which ones resulted from some form of expansion is lost, even in those shells where [ is built-in (all Bourne-like ones these days).



          The [ utility used to have a hard time telling which ones of its arguments were operators and which ones were operands (the thing operators work on). It didn't help that the syntax was intrinsically ambiguous. For instance:




          • [ -t ] used to be (and still is in some shells/[s) to test whether stdout is a terminal.


          • [ x ] is short for [ -n x ]: test whether x is a non-empty string (so you can see there's a conflict with the above).

          • in some shells/[s, -a and -o can be both unary ([ -a file ] for accessible file (now replaced by [ -e file ]), [ -o option ] for is the option enabled?) and binary operators (and and or). Again, ! -a x can be either and(nonempty("!"), nonempty("x") or not(isaccenssible("x")).


          • (, ) and ! add more problems.

          In normal programming languages like C or perl, in:



          if ($a eq $b) ...


          There's no way the content of $a or $b will be taken as operators because the conditional expression is parsed before those $a and $b are expanded. But in shells, in:



          [ "$a" = "$b" ]


          The shell expands the variables first². For instance, if $a contains ( and $b contains ), all the [ command sees is [, (, =, ) and ] arguments. So does that means "(" = ")" (are ( and ) lexically equal) or ( -n = ) (is = a non-empty string).



          Historical implementations (test appeared in Unix V7 in the late 70s) used to fail even in cases where it was not ambiguous just because of the order in which they were processing their arguments.



          Here with version 7 Unix in a PDP11 emulator:



          $ ls -l /bin/[
          -rwxr-xr-x 2 bin 2876 Jun 8 1979 /bin/[
          $ [ ! = x ]
          test: argument expected
          $ [ "(" = x ]
          test: argument expected


          Most shell and [ implementations have or have had problems with those or variants thereof. With bash 4.4 today:



          bash-4.4$ a='(' b=-o c=x
          bash-4.4$ [ "$a" = "$b" -o "$a" = "$c" ]
          bash: [: `)' expected, found =


          POSIX (in the early 90s) devised an algorithm that would make ['s behaviour unambiguous and deterministic when passed at most 4 arguments (beside [ and ]) in the most common usage patterns ([ -f "$a" -o "$b" ] still unspecified for instance). It deprecated (, ), -a and -o, and dropped -t without operand. bash did implement that algorithm (or at least tried to) in bash 2.0.



          So, in POSIX compliant [ implementations, [ "$a" = "$b" ] is guaranteed to compare the content of $a and $b for equality, whatever they are. Without -o, we would write:



          [ "$a" = "$b" ] || [ "$a" = "$c" ]


          That is, call [ twice, each time with fewer than 5 arguments.



          But it took quite a while for all [ implementations to become compliant. bash's was not compliant until 4.4 (though the last problem was for [ '(' ! "$var" ')' ] which nobody would really use in real life)



          The /bin/sh of Solaris 10 and older, which is not a POSIX shell, but a Bourne shell still has problems with [ "$a" = "$b" ]:



          $ a='!' b='!'
          $ [ "$a" = "$b" ]
          test: argument expected


          Using [ "x$a" = "x$b" ] works around the problem as there is no [ operator that starts with x. Another option is to use case instead:



          case "$a" in
          "$b") echo same;;
          *) echo different;;
          esac


          (quoting is necessary around $b, not around $a).



          In any case, it is not and never has been about empty values. People have problems with empty values in [ when they forget to quote their variables, but that's not a problem with [ then.



          $ a= b='-o x'
          [ $a = $b ]


          with the default value of $IFS becomes:



          [ = -o x ]


          Which is a test of whether = or x is a non-empty string, but no amount of prefixing will help³ as [ x$a = x$b ] will still be: [ x = x-o x ] which would cause an error, and it could get a lot worse including DoS and arbitrary command injection with other values like in bash:



          bash-4.4$ a= b='x -o -v a[`uname>&2`]'
          bash-4.4$ [ x$a = x$b ]
          Linux


          The correct solution is to always quote:



          [ "$a" = "$b" ] # OK in POSIX shells
          [ "x$a" = "x$b" ] # OK in all Bourne-like shells


          Note that expr has similar (and even worse) problems.



          expr also has a = operator, though it's for testing whether the two operatands are equal integers when they look like decimal integer numbers, or sort the same when not.



          In many implementations, expr + = +, or expr '(' = ')' or expr index = index don't do equality comparison. expr "x$a" = "x$b" would work around it for string comparison, but prefixing with an x could affect the sorting (in locales that have collating elements starting with x for instance) and obviously can't be used for number comparison expr "0$a" = "0$b" doesn't work for comparing negative integers. expr " $a" = " $b"
          works for integer comparison in some implementations, but not others (for a=01 b=1, some would return true, some false).




          ¹ ksh93 is an exception. In ksh93, [ can be seen as a reserved word in that [ -t ] is actually different from var=-t; [ "$var" ], or from ""[ -t ] or cmd='['; "$cmd" -t ]. That's to preserve backward compatibility and still be POSIX compliant in cases where it matters. The -t is only taken as an operator here if it's literal, and ksh93 detects that you're calling the [ command.



          ² ksh added a [[...]] conditional expression operator with its own syntax parsing rules (and some problems of its own) to address that (also found in some other shells, with some differences).



          ³ except in zsh where split+glob is not invoked upon parameter expansion, but empty removal still is, or in other shells when disabling split+glob globally with set -o noglob; IFS=






          share|improve this answer














          The important thing to understand here is that in most shells¹, [ is just an ordinary command parsed by the shell like any other ordinary command.



          Then the shell invokes that [ (aka test) command with a list of arguments, and then it's up to [ to interpret them as a conditional expression.



          At that point, those are just a list of strings and the information about which ones resulted from some form of expansion is lost, even in those shells where [ is built-in (all Bourne-like ones these days).



          The [ utility used to have a hard time telling which ones of its arguments were operators and which ones were operands (the thing operators work on). It didn't help that the syntax was intrinsically ambiguous. For instance:




          • [ -t ] used to be (and still is in some shells/[s) to test whether stdout is a terminal.


          • [ x ] is short for [ -n x ]: test whether x is a non-empty string (so you can see there's a conflict with the above).

          • in some shells/[s, -a and -o can be both unary ([ -a file ] for accessible file (now replaced by [ -e file ]), [ -o option ] for is the option enabled?) and binary operators (and and or). Again, ! -a x can be either and(nonempty("!"), nonempty("x") or not(isaccenssible("x")).


          • (, ) and ! add more problems.

          In normal programming languages like C or perl, in:



          if ($a eq $b) ...


          There's no way the content of $a or $b will be taken as operators because the conditional expression is parsed before those $a and $b are expanded. But in shells, in:



          [ "$a" = "$b" ]


          The shell expands the variables first². For instance, if $a contains ( and $b contains ), all the [ command sees is [, (, =, ) and ] arguments. So does that means "(" = ")" (are ( and ) lexically equal) or ( -n = ) (is = a non-empty string).



          Historical implementations (test appeared in Unix V7 in the late 70s) used to fail even in cases where it was not ambiguous just because of the order in which they were processing their arguments.



          Here with version 7 Unix in a PDP11 emulator:



          $ ls -l /bin/[
          -rwxr-xr-x 2 bin 2876 Jun 8 1979 /bin/[
          $ [ ! = x ]
          test: argument expected
          $ [ "(" = x ]
          test: argument expected


          Most shell and [ implementations have or have had problems with those or variants thereof. With bash 4.4 today:



          bash-4.4$ a='(' b=-o c=x
          bash-4.4$ [ "$a" = "$b" -o "$a" = "$c" ]
          bash: [: `)' expected, found =


          POSIX (in the early 90s) devised an algorithm that would make ['s behaviour unambiguous and deterministic when passed at most 4 arguments (beside [ and ]) in the most common usage patterns ([ -f "$a" -o "$b" ] still unspecified for instance). It deprecated (, ), -a and -o, and dropped -t without operand. bash did implement that algorithm (or at least tried to) in bash 2.0.



          So, in POSIX compliant [ implementations, [ "$a" = "$b" ] is guaranteed to compare the content of $a and $b for equality, whatever they are. Without -o, we would write:



          [ "$a" = "$b" ] || [ "$a" = "$c" ]


          That is, call [ twice, each time with fewer than 5 arguments.



          But it took quite a while for all [ implementations to become compliant. bash's was not compliant until 4.4 (though the last problem was for [ '(' ! "$var" ')' ] which nobody would really use in real life)



          The /bin/sh of Solaris 10 and older, which is not a POSIX shell, but a Bourne shell still has problems with [ "$a" = "$b" ]:



          $ a='!' b='!'
          $ [ "$a" = "$b" ]
          test: argument expected


          Using [ "x$a" = "x$b" ] works around the problem as there is no [ operator that starts with x. Another option is to use case instead:



          case "$a" in
          "$b") echo same;;
          *) echo different;;
          esac


          (quoting is necessary around $b, not around $a).



          In any case, it is not and never has been about empty values. People have problems with empty values in [ when they forget to quote their variables, but that's not a problem with [ then.



          $ a= b='-o x'
          [ $a = $b ]


          with the default value of $IFS becomes:



          [ = -o x ]


          Which is a test of whether = or x is a non-empty string, but no amount of prefixing will help³ as [ x$a = x$b ] will still be: [ x = x-o x ] which would cause an error, and it could get a lot worse including DoS and arbitrary command injection with other values like in bash:



          bash-4.4$ a= b='x -o -v a[`uname>&2`]'
          bash-4.4$ [ x$a = x$b ]
          Linux


          The correct solution is to always quote:



          [ "$a" = "$b" ] # OK in POSIX shells
          [ "x$a" = "x$b" ] # OK in all Bourne-like shells


          Note that expr has similar (and even worse) problems.



          expr also has a = operator, though it's for testing whether the two operatands are equal integers when they look like decimal integer numbers, or sort the same when not.



          In many implementations, expr + = +, or expr '(' = ')' or expr index = index don't do equality comparison. expr "x$a" = "x$b" would work around it for string comparison, but prefixing with an x could affect the sorting (in locales that have collating elements starting with x for instance) and obviously can't be used for number comparison expr "0$a" = "0$b" doesn't work for comparing negative integers. expr " $a" = " $b"
          works for integer comparison in some implementations, but not others (for a=01 b=1, some would return true, some false).




          ¹ ksh93 is an exception. In ksh93, [ can be seen as a reserved word in that [ -t ] is actually different from var=-t; [ "$var" ], or from ""[ -t ] or cmd='['; "$cmd" -t ]. That's to preserve backward compatibility and still be POSIX compliant in cases where it matters. The -t is only taken as an operator here if it's literal, and ksh93 detects that you're calling the [ command.



          ² ksh added a [[...]] conditional expression operator with its own syntax parsing rules (and some problems of its own) to address that (also found in some other shells, with some differences).



          ³ except in zsh where split+glob is not invoked upon parameter expansion, but empty removal still is, or in other shells when disabling split+glob globally with set -o noglob; IFS=







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Dec 25 '18 at 12:59









          Jeff Schaller

          38.9k1053125




          38.9k1053125










          answered Dec 22 '18 at 12:34









          Stéphane Chazelas

          299k54564913




          299k54564913























              5














              People often ascribe the prefix to problems with empty strings, but that is not the reason for it. The problem is a very simple one: the expansion of the variable could be one of test's operators, suddenly turning a binary equality test into a different expression.



              Recent implementations of the command on most platforms avoid the pitfall with look-ahead in the expression parser, preventing the parser from recognizing the first operand to a binary operator as anything other than an operand, as long as there are enough tokens to be a binary operator of course:


              % a=-n
              % /bin/test "$a" = -n ; echo $?
              0
              % /bin/test "$a" = ; echo $?
              0
              % /bin/test x"$a" = ; echo $?
              test: =: argument expected
              2
              % a='('
              % /bin/test "$a" = "(" ; echo $?
              0
              % /bin/test "$a" = ; echo $?
              test: closing paren expected
              2
              %





              share|improve this answer

























                5














                People often ascribe the prefix to problems with empty strings, but that is not the reason for it. The problem is a very simple one: the expansion of the variable could be one of test's operators, suddenly turning a binary equality test into a different expression.



                Recent implementations of the command on most platforms avoid the pitfall with look-ahead in the expression parser, preventing the parser from recognizing the first operand to a binary operator as anything other than an operand, as long as there are enough tokens to be a binary operator of course:


                % a=-n
                % /bin/test "$a" = -n ; echo $?
                0
                % /bin/test "$a" = ; echo $?
                0
                % /bin/test x"$a" = ; echo $?
                test: =: argument expected
                2
                % a='('
                % /bin/test "$a" = "(" ; echo $?
                0
                % /bin/test "$a" = ; echo $?
                test: closing paren expected
                2
                %





                share|improve this answer























                  5












                  5








                  5






                  People often ascribe the prefix to problems with empty strings, but that is not the reason for it. The problem is a very simple one: the expansion of the variable could be one of test's operators, suddenly turning a binary equality test into a different expression.



                  Recent implementations of the command on most platforms avoid the pitfall with look-ahead in the expression parser, preventing the parser from recognizing the first operand to a binary operator as anything other than an operand, as long as there are enough tokens to be a binary operator of course:


                  % a=-n
                  % /bin/test "$a" = -n ; echo $?
                  0
                  % /bin/test "$a" = ; echo $?
                  0
                  % /bin/test x"$a" = ; echo $?
                  test: =: argument expected
                  2
                  % a='('
                  % /bin/test "$a" = "(" ; echo $?
                  0
                  % /bin/test "$a" = ; echo $?
                  test: closing paren expected
                  2
                  %





                  share|improve this answer












                  People often ascribe the prefix to problems with empty strings, but that is not the reason for it. The problem is a very simple one: the expansion of the variable could be one of test's operators, suddenly turning a binary equality test into a different expression.



                  Recent implementations of the command on most platforms avoid the pitfall with look-ahead in the expression parser, preventing the parser from recognizing the first operand to a binary operator as anything other than an operand, as long as there are enough tokens to be a binary operator of course:


                  % a=-n
                  % /bin/test "$a" = -n ; echo $?
                  0
                  % /bin/test "$a" = ; echo $?
                  0
                  % /bin/test x"$a" = ; echo $?
                  test: =: argument expected
                  2
                  % a='('
                  % /bin/test "$a" = "(" ; echo $?
                  0
                  % /bin/test "$a" = ; echo $?
                  test: closing paren expected
                  2
                  %






                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  answered Dec 21 '18 at 22:25









                  JdeBP

                  33.3k469156




                  33.3k469156



























                      draft saved

                      draft discarded
















































                      Thanks for contributing an answer to Unix & Linux Stack Exchange!


                      • Please be sure to answer the question. Provide details and share your research!

                      But avoid


                      • Asking for help, clarification, or responding to other answers.

                      • Making statements based on opinion; back them up with references or personal experience.

                      To learn more, see our tips on writing great answers.





                      Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


                      Please pay close attention to the following guidance:


                      • Please be sure to answer the question. Provide details and share your research!

                      But avoid


                      • Asking for help, clarification, or responding to other answers.

                      • Making statements based on opinion; back them up with references or personal experience.

                      To learn more, see our tips on writing great answers.




                      draft saved


                      draft discarded














                      StackExchange.ready(
                      function ()
                      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f490393%2fwhats-the-purpose-of-adding-a-prefix-on-both-sides-of-a-shell-variable-comparis%23new-answer', 'question_page');

                      );

                      Post as a guest















                      Required, but never shown





















































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown

































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown






                      Popular posts from this blog

                      Peggy Mitchell

                      Palaiologos

                      The Forum (Inglewood, California)