Error in shell bracket test when string is a left-parenthesis











up vote
27
down vote

favorite
3












I used to be confident about the fact that quoting strings is always a good practice in order to avoid having the shell parsing it.



Then I came across this:



$ x='('
$ [ "$x" = '1' -a "$y" = '1' ]
bash: [: `)' expected, found 1


Trying to isolate the problem, getting the same error:



$ [ '(' = '1' -a '1' = '1' ]
bash: [: `)' expected, found 1


I solved the problem like this:



[ "$x" = '1' ] && [ "$y" = '1' ]


Still I need to know what's going on here.










share|improve this question




















  • 2




    As a workaround, in bash, you can use [[ "$x" = '1' && "$y" = '1' ]]
    – terdon
    Dec 7 '16 at 10:15






  • 3




    The POSIX specification for test explicitly describes -a and -o as obsolescent for this reason (which is what the [OB] superscript next to their specification means). If you wrote [ "$x" = 1 ] && [ "$y" = 1 ] instead, you'd be fine, and would be well within the realm of well-defined/standardized behavior.
    – Charles Duffy
    Dec 7 '16 at 13:58








  • 6




    This is why people used to use [ "x$x" = "x1" ] to prevent arguments being misinterpreted as operators.
    – Jonathan Leffler
    Dec 7 '16 at 14:45










  • @JonathanLeffler: Hey, you condensed my answer to a single sentence, not fair! :) If one uses a POSIX shell like dash rather than Bash, it's still a useful practice.
    – Nominal Animal
    Dec 8 '16 at 0:59










  • I want to thanks everyone taking the time to answer my question, really appreciated guys :) ! I also want to thanks for the vital editing done to my question. Entering this forum sometimes gives me the same thrill of escaping from Alcatraz, wrong move means your life. Anyway I really wish my Thanks will reach you before this comment gets deleted
    – Claudio
    Dec 8 '16 at 12:32















up vote
27
down vote

favorite
3












I used to be confident about the fact that quoting strings is always a good practice in order to avoid having the shell parsing it.



Then I came across this:



$ x='('
$ [ "$x" = '1' -a "$y" = '1' ]
bash: [: `)' expected, found 1


Trying to isolate the problem, getting the same error:



$ [ '(' = '1' -a '1' = '1' ]
bash: [: `)' expected, found 1


I solved the problem like this:



[ "$x" = '1' ] && [ "$y" = '1' ]


Still I need to know what's going on here.










share|improve this question




















  • 2




    As a workaround, in bash, you can use [[ "$x" = '1' && "$y" = '1' ]]
    – terdon
    Dec 7 '16 at 10:15






  • 3




    The POSIX specification for test explicitly describes -a and -o as obsolescent for this reason (which is what the [OB] superscript next to their specification means). If you wrote [ "$x" = 1 ] && [ "$y" = 1 ] instead, you'd be fine, and would be well within the realm of well-defined/standardized behavior.
    – Charles Duffy
    Dec 7 '16 at 13:58








  • 6




    This is why people used to use [ "x$x" = "x1" ] to prevent arguments being misinterpreted as operators.
    – Jonathan Leffler
    Dec 7 '16 at 14:45










  • @JonathanLeffler: Hey, you condensed my answer to a single sentence, not fair! :) If one uses a POSIX shell like dash rather than Bash, it's still a useful practice.
    – Nominal Animal
    Dec 8 '16 at 0:59










  • I want to thanks everyone taking the time to answer my question, really appreciated guys :) ! I also want to thanks for the vital editing done to my question. Entering this forum sometimes gives me the same thrill of escaping from Alcatraz, wrong move means your life. Anyway I really wish my Thanks will reach you before this comment gets deleted
    – Claudio
    Dec 8 '16 at 12:32













up vote
27
down vote

favorite
3









up vote
27
down vote

favorite
3






3





I used to be confident about the fact that quoting strings is always a good practice in order to avoid having the shell parsing it.



Then I came across this:



$ x='('
$ [ "$x" = '1' -a "$y" = '1' ]
bash: [: `)' expected, found 1


Trying to isolate the problem, getting the same error:



$ [ '(' = '1' -a '1' = '1' ]
bash: [: `)' expected, found 1


I solved the problem like this:



[ "$x" = '1' ] && [ "$y" = '1' ]


Still I need to know what's going on here.










share|improve this question















I used to be confident about the fact that quoting strings is always a good practice in order to avoid having the shell parsing it.



Then I came across this:



$ x='('
$ [ "$x" = '1' -a "$y" = '1' ]
bash: [: `)' expected, found 1


Trying to isolate the problem, getting the same error:



$ [ '(' = '1' -a '1' = '1' ]
bash: [: `)' expected, found 1


I solved the problem like this:



[ "$x" = '1' ] && [ "$y" = '1' ]


Still I need to know what's going on here.







shell test






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 20 at 22:47









Rui F Ribeiro

38.2k1475125




38.2k1475125










asked Dec 7 '16 at 9:44









Claudio

19615




19615








  • 2




    As a workaround, in bash, you can use [[ "$x" = '1' && "$y" = '1' ]]
    – terdon
    Dec 7 '16 at 10:15






  • 3




    The POSIX specification for test explicitly describes -a and -o as obsolescent for this reason (which is what the [OB] superscript next to their specification means). If you wrote [ "$x" = 1 ] && [ "$y" = 1 ] instead, you'd be fine, and would be well within the realm of well-defined/standardized behavior.
    – Charles Duffy
    Dec 7 '16 at 13:58








  • 6




    This is why people used to use [ "x$x" = "x1" ] to prevent arguments being misinterpreted as operators.
    – Jonathan Leffler
    Dec 7 '16 at 14:45










  • @JonathanLeffler: Hey, you condensed my answer to a single sentence, not fair! :) If one uses a POSIX shell like dash rather than Bash, it's still a useful practice.
    – Nominal Animal
    Dec 8 '16 at 0:59










  • I want to thanks everyone taking the time to answer my question, really appreciated guys :) ! I also want to thanks for the vital editing done to my question. Entering this forum sometimes gives me the same thrill of escaping from Alcatraz, wrong move means your life. Anyway I really wish my Thanks will reach you before this comment gets deleted
    – Claudio
    Dec 8 '16 at 12:32














  • 2




    As a workaround, in bash, you can use [[ "$x" = '1' && "$y" = '1' ]]
    – terdon
    Dec 7 '16 at 10:15






  • 3




    The POSIX specification for test explicitly describes -a and -o as obsolescent for this reason (which is what the [OB] superscript next to their specification means). If you wrote [ "$x" = 1 ] && [ "$y" = 1 ] instead, you'd be fine, and would be well within the realm of well-defined/standardized behavior.
    – Charles Duffy
    Dec 7 '16 at 13:58








  • 6




    This is why people used to use [ "x$x" = "x1" ] to prevent arguments being misinterpreted as operators.
    – Jonathan Leffler
    Dec 7 '16 at 14:45










  • @JonathanLeffler: Hey, you condensed my answer to a single sentence, not fair! :) If one uses a POSIX shell like dash rather than Bash, it's still a useful practice.
    – Nominal Animal
    Dec 8 '16 at 0:59










  • I want to thanks everyone taking the time to answer my question, really appreciated guys :) ! I also want to thanks for the vital editing done to my question. Entering this forum sometimes gives me the same thrill of escaping from Alcatraz, wrong move means your life. Anyway I really wish my Thanks will reach you before this comment gets deleted
    – Claudio
    Dec 8 '16 at 12:32








2




2




As a workaround, in bash, you can use [[ "$x" = '1' && "$y" = '1' ]]
– terdon
Dec 7 '16 at 10:15




As a workaround, in bash, you can use [[ "$x" = '1' && "$y" = '1' ]]
– terdon
Dec 7 '16 at 10:15




3




3




The POSIX specification for test explicitly describes -a and -o as obsolescent for this reason (which is what the [OB] superscript next to their specification means). If you wrote [ "$x" = 1 ] && [ "$y" = 1 ] instead, you'd be fine, and would be well within the realm of well-defined/standardized behavior.
– Charles Duffy
Dec 7 '16 at 13:58






The POSIX specification for test explicitly describes -a and -o as obsolescent for this reason (which is what the [OB] superscript next to their specification means). If you wrote [ "$x" = 1 ] && [ "$y" = 1 ] instead, you'd be fine, and would be well within the realm of well-defined/standardized behavior.
– Charles Duffy
Dec 7 '16 at 13:58






6




6




This is why people used to use [ "x$x" = "x1" ] to prevent arguments being misinterpreted as operators.
– Jonathan Leffler
Dec 7 '16 at 14:45




This is why people used to use [ "x$x" = "x1" ] to prevent arguments being misinterpreted as operators.
– Jonathan Leffler
Dec 7 '16 at 14:45












@JonathanLeffler: Hey, you condensed my answer to a single sentence, not fair! :) If one uses a POSIX shell like dash rather than Bash, it's still a useful practice.
– Nominal Animal
Dec 8 '16 at 0:59




@JonathanLeffler: Hey, you condensed my answer to a single sentence, not fair! :) If one uses a POSIX shell like dash rather than Bash, it's still a useful practice.
– Nominal Animal
Dec 8 '16 at 0:59












I want to thanks everyone taking the time to answer my question, really appreciated guys :) ! I also want to thanks for the vital editing done to my question. Entering this forum sometimes gives me the same thrill of escaping from Alcatraz, wrong move means your life. Anyway I really wish my Thanks will reach you before this comment gets deleted
– Claudio
Dec 8 '16 at 12:32




I want to thanks everyone taking the time to answer my question, really appreciated guys :) ! I also want to thanks for the vital editing done to my question. Entering this forum sometimes gives me the same thrill of escaping from Alcatraz, wrong move means your life. Anyway I really wish my Thanks will reach you before this comment gets deleted
– Claudio
Dec 8 '16 at 12:32










2 Answers
2






active

oldest

votes

















up vote
25
down vote













This is a very obscure corner case that one might consider a bug in how the test [ built-in is defined; however, it does match the behaviour of the actual [ binary available on many systems. As far as I can tell, it only affects certain cases and a variable having a value that matches a [ operator like (, !, =, -e, and so on.



Let me explain why, and how to work around it in Bash and POSIX shells.





Explanation:



Consider the following:



x="("
[ "$x" = "(" ] && echo yes || echo no


No problem; the above yields no error, and outputs yes. This is how we expect stuff to work. You can change the comparison string to '1' if you like, and the value of x, and it'll work as expected.



Note that the actual /usr/bin/[ binary behaves the same way. If you run e.g. '/usr/bin/[' '(' = '(' ']' there is no error, because the program can detect that the arguments consist of a single string comparison operation.



The bug occurs when we and with a second expression. It does not matter what the second expression is, as long as it is valid. For example,



[ '1' = '1' ] && echo yes || echo no


outputs yes, and is obviously a valid expression; but, if we combine the two,



[ "$x" = "(" -a '1' = '1' ] && echo yes || echo no


Bash rejects the expression if and only if x is ( or !.



If we were to run the above using the actual [ program, i.e.



'/usr/bin/[' "$x" = "(" -a '1' = '1' ] && echo yes || echo no


the error would be understandable: since the shell does the variable substitutions, the /usr/bin/[ binary only receives parameters ( = ( -a 1 = 1 and the terminating ], it understandably fails to parse whether the open parentheses start a sub-expression or not, there being an and operation involved. Sure, parsing it as two string comparisons is possible, but doing it greedily like that might cause issues when applied to proper expressions with parenthesized sub-expressions.



The problem, really, is that the shell [ built-in behaves the same way, as if it expanded the value of x before examining the expression.



(These ambiguities, and others related to variable expansion, were a large reason why Bash implemented and now recommends using the [[ ... ]] test expressions instead.)





The workaround is trivial, and often seen in scripts using older sh shells. You add a "safe" character, often x, in front of the strings (both values being compared), to ensure the expression is recognized as a string comparison:



[ "x$x" = "x(" -a "x$y" = "x1" ]





share|improve this answer

















  • 1




    I wouldn't call the behaviour of the [ built-in a bug. If anything, it's an inherent design flaw. [[ is a shell keyword, not just a built-in command, so it gets to look at things before quote-removal, and actually override the usual word-splitting. e.g. [[ $x == 1 ]] doesn't need to use "$x", because a [[ context is different from normal. Anyway, this is how [[ is able to avoid the pitfalls of [. POSIX requires [ to behave the way it does, and bash is mostly POSIX compliant even without --posix, so changing [ into a keyword is unattractive.
    – Peter Cordes
    Dec 7 '16 at 17:42










  • Your workaround is not recommended by POSIX. Just use two calls to [.
    – Wildcard
    Dec 7 '16 at 21:08










  • @PeterCordes: Quite right; good point. (I perhaps should have used designed instead of defined in my first paragraph, but ['s behaviour being burdened by history predating POSIX, I chose the latter word instead.) I do personally avoid using [[ in my example scripts, but only because it is an exception to the quoting recommendation I always harp about (since omitting quotes is the most common reason for script bugs I see), and I haven't thought of a simple paragraph to explain why [[ is an exception to the rules, without making my quoting recommendation suspect.
    – Nominal Animal
    Dec 8 '16 at 0:38










  • @Wildcard: No. POSIX does not recommend against this practice, and that's what matters. Just because an authority does not happen to recommend this practice, does not make this bad. Indeed, as I point out, this is historical practice used in sh scripts, well predating POSIX standardization. Using parenthesized subexpressions and -a and -o logical operators in tests/[ is more efficient that relying on expression chaining (via && and ||); it's just that on current machines, the difference is irrelevant.
    – Nominal Animal
    Dec 8 '16 at 0:44










  • @Wildcard: However, I do personally prefer to chain the test expressions using && and ||, but the reason is, it makes it easier for us humans to maintain (read, understand, and modify if/when necessary) them without introducing bugs. So, I'm not criticising your suggestion, but only the reasoning behind the suggestion. (For very similar reasons, the .LT., .GE., etc. comparison operators in FORTRAN 77 got much more human-friendly versions <, >=, etc. in later versions.)
    – Nominal Animal
    Dec 8 '16 at 0:51


















up vote
11
down vote













[ aka test sees:



 argc: 1 2 3 4  5 6 7 8
argv: ( = 1 -a 1 = 1 ]


test accepts subexpressions in parentheses; so it thinks that the left parenthesis opens a subexpression and is trying to parse it; the parser sees = as the first thing in the subexpression and thinks that it is an implicit string-length test, so it is happy; the subexpression should then be followed by a right parenthesis, and instead the parser finds 1 instead of ). And it complains.



When test has exactly three arguments, and the middle argument is one of the recognized operators, it applies that operator to the 1st and 3rd arguments without looking for subexpressions in parentheses.



For the full details look at man bash, search for test expr.



Conclusion: The parsing algorithm used by test is complicated. Use only simple expressions and use the shell operators !, && and || to combine them.






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',
    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%2f328625%2ferror-in-shell-bracket-test-when-string-is-a-left-parenthesis%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








    up vote
    25
    down vote













    This is a very obscure corner case that one might consider a bug in how the test [ built-in is defined; however, it does match the behaviour of the actual [ binary available on many systems. As far as I can tell, it only affects certain cases and a variable having a value that matches a [ operator like (, !, =, -e, and so on.



    Let me explain why, and how to work around it in Bash and POSIX shells.





    Explanation:



    Consider the following:



    x="("
    [ "$x" = "(" ] && echo yes || echo no


    No problem; the above yields no error, and outputs yes. This is how we expect stuff to work. You can change the comparison string to '1' if you like, and the value of x, and it'll work as expected.



    Note that the actual /usr/bin/[ binary behaves the same way. If you run e.g. '/usr/bin/[' '(' = '(' ']' there is no error, because the program can detect that the arguments consist of a single string comparison operation.



    The bug occurs when we and with a second expression. It does not matter what the second expression is, as long as it is valid. For example,



    [ '1' = '1' ] && echo yes || echo no


    outputs yes, and is obviously a valid expression; but, if we combine the two,



    [ "$x" = "(" -a '1' = '1' ] && echo yes || echo no


    Bash rejects the expression if and only if x is ( or !.



    If we were to run the above using the actual [ program, i.e.



    '/usr/bin/[' "$x" = "(" -a '1' = '1' ] && echo yes || echo no


    the error would be understandable: since the shell does the variable substitutions, the /usr/bin/[ binary only receives parameters ( = ( -a 1 = 1 and the terminating ], it understandably fails to parse whether the open parentheses start a sub-expression or not, there being an and operation involved. Sure, parsing it as two string comparisons is possible, but doing it greedily like that might cause issues when applied to proper expressions with parenthesized sub-expressions.



    The problem, really, is that the shell [ built-in behaves the same way, as if it expanded the value of x before examining the expression.



    (These ambiguities, and others related to variable expansion, were a large reason why Bash implemented and now recommends using the [[ ... ]] test expressions instead.)





    The workaround is trivial, and often seen in scripts using older sh shells. You add a "safe" character, often x, in front of the strings (both values being compared), to ensure the expression is recognized as a string comparison:



    [ "x$x" = "x(" -a "x$y" = "x1" ]





    share|improve this answer

















    • 1




      I wouldn't call the behaviour of the [ built-in a bug. If anything, it's an inherent design flaw. [[ is a shell keyword, not just a built-in command, so it gets to look at things before quote-removal, and actually override the usual word-splitting. e.g. [[ $x == 1 ]] doesn't need to use "$x", because a [[ context is different from normal. Anyway, this is how [[ is able to avoid the pitfalls of [. POSIX requires [ to behave the way it does, and bash is mostly POSIX compliant even without --posix, so changing [ into a keyword is unattractive.
      – Peter Cordes
      Dec 7 '16 at 17:42










    • Your workaround is not recommended by POSIX. Just use two calls to [.
      – Wildcard
      Dec 7 '16 at 21:08










    • @PeterCordes: Quite right; good point. (I perhaps should have used designed instead of defined in my first paragraph, but ['s behaviour being burdened by history predating POSIX, I chose the latter word instead.) I do personally avoid using [[ in my example scripts, but only because it is an exception to the quoting recommendation I always harp about (since omitting quotes is the most common reason for script bugs I see), and I haven't thought of a simple paragraph to explain why [[ is an exception to the rules, without making my quoting recommendation suspect.
      – Nominal Animal
      Dec 8 '16 at 0:38










    • @Wildcard: No. POSIX does not recommend against this practice, and that's what matters. Just because an authority does not happen to recommend this practice, does not make this bad. Indeed, as I point out, this is historical practice used in sh scripts, well predating POSIX standardization. Using parenthesized subexpressions and -a and -o logical operators in tests/[ is more efficient that relying on expression chaining (via && and ||); it's just that on current machines, the difference is irrelevant.
      – Nominal Animal
      Dec 8 '16 at 0:44










    • @Wildcard: However, I do personally prefer to chain the test expressions using && and ||, but the reason is, it makes it easier for us humans to maintain (read, understand, and modify if/when necessary) them without introducing bugs. So, I'm not criticising your suggestion, but only the reasoning behind the suggestion. (For very similar reasons, the .LT., .GE., etc. comparison operators in FORTRAN 77 got much more human-friendly versions <, >=, etc. in later versions.)
      – Nominal Animal
      Dec 8 '16 at 0:51















    up vote
    25
    down vote













    This is a very obscure corner case that one might consider a bug in how the test [ built-in is defined; however, it does match the behaviour of the actual [ binary available on many systems. As far as I can tell, it only affects certain cases and a variable having a value that matches a [ operator like (, !, =, -e, and so on.



    Let me explain why, and how to work around it in Bash and POSIX shells.





    Explanation:



    Consider the following:



    x="("
    [ "$x" = "(" ] && echo yes || echo no


    No problem; the above yields no error, and outputs yes. This is how we expect stuff to work. You can change the comparison string to '1' if you like, and the value of x, and it'll work as expected.



    Note that the actual /usr/bin/[ binary behaves the same way. If you run e.g. '/usr/bin/[' '(' = '(' ']' there is no error, because the program can detect that the arguments consist of a single string comparison operation.



    The bug occurs when we and with a second expression. It does not matter what the second expression is, as long as it is valid. For example,



    [ '1' = '1' ] && echo yes || echo no


    outputs yes, and is obviously a valid expression; but, if we combine the two,



    [ "$x" = "(" -a '1' = '1' ] && echo yes || echo no


    Bash rejects the expression if and only if x is ( or !.



    If we were to run the above using the actual [ program, i.e.



    '/usr/bin/[' "$x" = "(" -a '1' = '1' ] && echo yes || echo no


    the error would be understandable: since the shell does the variable substitutions, the /usr/bin/[ binary only receives parameters ( = ( -a 1 = 1 and the terminating ], it understandably fails to parse whether the open parentheses start a sub-expression or not, there being an and operation involved. Sure, parsing it as two string comparisons is possible, but doing it greedily like that might cause issues when applied to proper expressions with parenthesized sub-expressions.



    The problem, really, is that the shell [ built-in behaves the same way, as if it expanded the value of x before examining the expression.



    (These ambiguities, and others related to variable expansion, were a large reason why Bash implemented and now recommends using the [[ ... ]] test expressions instead.)





    The workaround is trivial, and often seen in scripts using older sh shells. You add a "safe" character, often x, in front of the strings (both values being compared), to ensure the expression is recognized as a string comparison:



    [ "x$x" = "x(" -a "x$y" = "x1" ]





    share|improve this answer

















    • 1




      I wouldn't call the behaviour of the [ built-in a bug. If anything, it's an inherent design flaw. [[ is a shell keyword, not just a built-in command, so it gets to look at things before quote-removal, and actually override the usual word-splitting. e.g. [[ $x == 1 ]] doesn't need to use "$x", because a [[ context is different from normal. Anyway, this is how [[ is able to avoid the pitfalls of [. POSIX requires [ to behave the way it does, and bash is mostly POSIX compliant even without --posix, so changing [ into a keyword is unattractive.
      – Peter Cordes
      Dec 7 '16 at 17:42










    • Your workaround is not recommended by POSIX. Just use two calls to [.
      – Wildcard
      Dec 7 '16 at 21:08










    • @PeterCordes: Quite right; good point. (I perhaps should have used designed instead of defined in my first paragraph, but ['s behaviour being burdened by history predating POSIX, I chose the latter word instead.) I do personally avoid using [[ in my example scripts, but only because it is an exception to the quoting recommendation I always harp about (since omitting quotes is the most common reason for script bugs I see), and I haven't thought of a simple paragraph to explain why [[ is an exception to the rules, without making my quoting recommendation suspect.
      – Nominal Animal
      Dec 8 '16 at 0:38










    • @Wildcard: No. POSIX does not recommend against this practice, and that's what matters. Just because an authority does not happen to recommend this practice, does not make this bad. Indeed, as I point out, this is historical practice used in sh scripts, well predating POSIX standardization. Using parenthesized subexpressions and -a and -o logical operators in tests/[ is more efficient that relying on expression chaining (via && and ||); it's just that on current machines, the difference is irrelevant.
      – Nominal Animal
      Dec 8 '16 at 0:44










    • @Wildcard: However, I do personally prefer to chain the test expressions using && and ||, but the reason is, it makes it easier for us humans to maintain (read, understand, and modify if/when necessary) them without introducing bugs. So, I'm not criticising your suggestion, but only the reasoning behind the suggestion. (For very similar reasons, the .LT., .GE., etc. comparison operators in FORTRAN 77 got much more human-friendly versions <, >=, etc. in later versions.)
      – Nominal Animal
      Dec 8 '16 at 0:51













    up vote
    25
    down vote










    up vote
    25
    down vote









    This is a very obscure corner case that one might consider a bug in how the test [ built-in is defined; however, it does match the behaviour of the actual [ binary available on many systems. As far as I can tell, it only affects certain cases and a variable having a value that matches a [ operator like (, !, =, -e, and so on.



    Let me explain why, and how to work around it in Bash and POSIX shells.





    Explanation:



    Consider the following:



    x="("
    [ "$x" = "(" ] && echo yes || echo no


    No problem; the above yields no error, and outputs yes. This is how we expect stuff to work. You can change the comparison string to '1' if you like, and the value of x, and it'll work as expected.



    Note that the actual /usr/bin/[ binary behaves the same way. If you run e.g. '/usr/bin/[' '(' = '(' ']' there is no error, because the program can detect that the arguments consist of a single string comparison operation.



    The bug occurs when we and with a second expression. It does not matter what the second expression is, as long as it is valid. For example,



    [ '1' = '1' ] && echo yes || echo no


    outputs yes, and is obviously a valid expression; but, if we combine the two,



    [ "$x" = "(" -a '1' = '1' ] && echo yes || echo no


    Bash rejects the expression if and only if x is ( or !.



    If we were to run the above using the actual [ program, i.e.



    '/usr/bin/[' "$x" = "(" -a '1' = '1' ] && echo yes || echo no


    the error would be understandable: since the shell does the variable substitutions, the /usr/bin/[ binary only receives parameters ( = ( -a 1 = 1 and the terminating ], it understandably fails to parse whether the open parentheses start a sub-expression or not, there being an and operation involved. Sure, parsing it as two string comparisons is possible, but doing it greedily like that might cause issues when applied to proper expressions with parenthesized sub-expressions.



    The problem, really, is that the shell [ built-in behaves the same way, as if it expanded the value of x before examining the expression.



    (These ambiguities, and others related to variable expansion, were a large reason why Bash implemented and now recommends using the [[ ... ]] test expressions instead.)





    The workaround is trivial, and often seen in scripts using older sh shells. You add a "safe" character, often x, in front of the strings (both values being compared), to ensure the expression is recognized as a string comparison:



    [ "x$x" = "x(" -a "x$y" = "x1" ]





    share|improve this answer












    This is a very obscure corner case that one might consider a bug in how the test [ built-in is defined; however, it does match the behaviour of the actual [ binary available on many systems. As far as I can tell, it only affects certain cases and a variable having a value that matches a [ operator like (, !, =, -e, and so on.



    Let me explain why, and how to work around it in Bash and POSIX shells.





    Explanation:



    Consider the following:



    x="("
    [ "$x" = "(" ] && echo yes || echo no


    No problem; the above yields no error, and outputs yes. This is how we expect stuff to work. You can change the comparison string to '1' if you like, and the value of x, and it'll work as expected.



    Note that the actual /usr/bin/[ binary behaves the same way. If you run e.g. '/usr/bin/[' '(' = '(' ']' there is no error, because the program can detect that the arguments consist of a single string comparison operation.



    The bug occurs when we and with a second expression. It does not matter what the second expression is, as long as it is valid. For example,



    [ '1' = '1' ] && echo yes || echo no


    outputs yes, and is obviously a valid expression; but, if we combine the two,



    [ "$x" = "(" -a '1' = '1' ] && echo yes || echo no


    Bash rejects the expression if and only if x is ( or !.



    If we were to run the above using the actual [ program, i.e.



    '/usr/bin/[' "$x" = "(" -a '1' = '1' ] && echo yes || echo no


    the error would be understandable: since the shell does the variable substitutions, the /usr/bin/[ binary only receives parameters ( = ( -a 1 = 1 and the terminating ], it understandably fails to parse whether the open parentheses start a sub-expression or not, there being an and operation involved. Sure, parsing it as two string comparisons is possible, but doing it greedily like that might cause issues when applied to proper expressions with parenthesized sub-expressions.



    The problem, really, is that the shell [ built-in behaves the same way, as if it expanded the value of x before examining the expression.



    (These ambiguities, and others related to variable expansion, were a large reason why Bash implemented and now recommends using the [[ ... ]] test expressions instead.)





    The workaround is trivial, and often seen in scripts using older sh shells. You add a "safe" character, often x, in front of the strings (both values being compared), to ensure the expression is recognized as a string comparison:



    [ "x$x" = "x(" -a "x$y" = "x1" ]






    share|improve this answer












    share|improve this answer



    share|improve this answer










    answered Dec 7 '16 at 10:30









    Nominal Animal

    2,820812




    2,820812








    • 1




      I wouldn't call the behaviour of the [ built-in a bug. If anything, it's an inherent design flaw. [[ is a shell keyword, not just a built-in command, so it gets to look at things before quote-removal, and actually override the usual word-splitting. e.g. [[ $x == 1 ]] doesn't need to use "$x", because a [[ context is different from normal. Anyway, this is how [[ is able to avoid the pitfalls of [. POSIX requires [ to behave the way it does, and bash is mostly POSIX compliant even without --posix, so changing [ into a keyword is unattractive.
      – Peter Cordes
      Dec 7 '16 at 17:42










    • Your workaround is not recommended by POSIX. Just use two calls to [.
      – Wildcard
      Dec 7 '16 at 21:08










    • @PeterCordes: Quite right; good point. (I perhaps should have used designed instead of defined in my first paragraph, but ['s behaviour being burdened by history predating POSIX, I chose the latter word instead.) I do personally avoid using [[ in my example scripts, but only because it is an exception to the quoting recommendation I always harp about (since omitting quotes is the most common reason for script bugs I see), and I haven't thought of a simple paragraph to explain why [[ is an exception to the rules, without making my quoting recommendation suspect.
      – Nominal Animal
      Dec 8 '16 at 0:38










    • @Wildcard: No. POSIX does not recommend against this practice, and that's what matters. Just because an authority does not happen to recommend this practice, does not make this bad. Indeed, as I point out, this is historical practice used in sh scripts, well predating POSIX standardization. Using parenthesized subexpressions and -a and -o logical operators in tests/[ is more efficient that relying on expression chaining (via && and ||); it's just that on current machines, the difference is irrelevant.
      – Nominal Animal
      Dec 8 '16 at 0:44










    • @Wildcard: However, I do personally prefer to chain the test expressions using && and ||, but the reason is, it makes it easier for us humans to maintain (read, understand, and modify if/when necessary) them without introducing bugs. So, I'm not criticising your suggestion, but only the reasoning behind the suggestion. (For very similar reasons, the .LT., .GE., etc. comparison operators in FORTRAN 77 got much more human-friendly versions <, >=, etc. in later versions.)
      – Nominal Animal
      Dec 8 '16 at 0:51














    • 1




      I wouldn't call the behaviour of the [ built-in a bug. If anything, it's an inherent design flaw. [[ is a shell keyword, not just a built-in command, so it gets to look at things before quote-removal, and actually override the usual word-splitting. e.g. [[ $x == 1 ]] doesn't need to use "$x", because a [[ context is different from normal. Anyway, this is how [[ is able to avoid the pitfalls of [. POSIX requires [ to behave the way it does, and bash is mostly POSIX compliant even without --posix, so changing [ into a keyword is unattractive.
      – Peter Cordes
      Dec 7 '16 at 17:42










    • Your workaround is not recommended by POSIX. Just use two calls to [.
      – Wildcard
      Dec 7 '16 at 21:08










    • @PeterCordes: Quite right; good point. (I perhaps should have used designed instead of defined in my first paragraph, but ['s behaviour being burdened by history predating POSIX, I chose the latter word instead.) I do personally avoid using [[ in my example scripts, but only because it is an exception to the quoting recommendation I always harp about (since omitting quotes is the most common reason for script bugs I see), and I haven't thought of a simple paragraph to explain why [[ is an exception to the rules, without making my quoting recommendation suspect.
      – Nominal Animal
      Dec 8 '16 at 0:38










    • @Wildcard: No. POSIX does not recommend against this practice, and that's what matters. Just because an authority does not happen to recommend this practice, does not make this bad. Indeed, as I point out, this is historical practice used in sh scripts, well predating POSIX standardization. Using parenthesized subexpressions and -a and -o logical operators in tests/[ is more efficient that relying on expression chaining (via && and ||); it's just that on current machines, the difference is irrelevant.
      – Nominal Animal
      Dec 8 '16 at 0:44










    • @Wildcard: However, I do personally prefer to chain the test expressions using && and ||, but the reason is, it makes it easier for us humans to maintain (read, understand, and modify if/when necessary) them without introducing bugs. So, I'm not criticising your suggestion, but only the reasoning behind the suggestion. (For very similar reasons, the .LT., .GE., etc. comparison operators in FORTRAN 77 got much more human-friendly versions <, >=, etc. in later versions.)
      – Nominal Animal
      Dec 8 '16 at 0:51








    1




    1




    I wouldn't call the behaviour of the [ built-in a bug. If anything, it's an inherent design flaw. [[ is a shell keyword, not just a built-in command, so it gets to look at things before quote-removal, and actually override the usual word-splitting. e.g. [[ $x == 1 ]] doesn't need to use "$x", because a [[ context is different from normal. Anyway, this is how [[ is able to avoid the pitfalls of [. POSIX requires [ to behave the way it does, and bash is mostly POSIX compliant even without --posix, so changing [ into a keyword is unattractive.
    – Peter Cordes
    Dec 7 '16 at 17:42




    I wouldn't call the behaviour of the [ built-in a bug. If anything, it's an inherent design flaw. [[ is a shell keyword, not just a built-in command, so it gets to look at things before quote-removal, and actually override the usual word-splitting. e.g. [[ $x == 1 ]] doesn't need to use "$x", because a [[ context is different from normal. Anyway, this is how [[ is able to avoid the pitfalls of [. POSIX requires [ to behave the way it does, and bash is mostly POSIX compliant even without --posix, so changing [ into a keyword is unattractive.
    – Peter Cordes
    Dec 7 '16 at 17:42












    Your workaround is not recommended by POSIX. Just use two calls to [.
    – Wildcard
    Dec 7 '16 at 21:08




    Your workaround is not recommended by POSIX. Just use two calls to [.
    – Wildcard
    Dec 7 '16 at 21:08












    @PeterCordes: Quite right; good point. (I perhaps should have used designed instead of defined in my first paragraph, but ['s behaviour being burdened by history predating POSIX, I chose the latter word instead.) I do personally avoid using [[ in my example scripts, but only because it is an exception to the quoting recommendation I always harp about (since omitting quotes is the most common reason for script bugs I see), and I haven't thought of a simple paragraph to explain why [[ is an exception to the rules, without making my quoting recommendation suspect.
    – Nominal Animal
    Dec 8 '16 at 0:38




    @PeterCordes: Quite right; good point. (I perhaps should have used designed instead of defined in my first paragraph, but ['s behaviour being burdened by history predating POSIX, I chose the latter word instead.) I do personally avoid using [[ in my example scripts, but only because it is an exception to the quoting recommendation I always harp about (since omitting quotes is the most common reason for script bugs I see), and I haven't thought of a simple paragraph to explain why [[ is an exception to the rules, without making my quoting recommendation suspect.
    – Nominal Animal
    Dec 8 '16 at 0:38












    @Wildcard: No. POSIX does not recommend against this practice, and that's what matters. Just because an authority does not happen to recommend this practice, does not make this bad. Indeed, as I point out, this is historical practice used in sh scripts, well predating POSIX standardization. Using parenthesized subexpressions and -a and -o logical operators in tests/[ is more efficient that relying on expression chaining (via && and ||); it's just that on current machines, the difference is irrelevant.
    – Nominal Animal
    Dec 8 '16 at 0:44




    @Wildcard: No. POSIX does not recommend against this practice, and that's what matters. Just because an authority does not happen to recommend this practice, does not make this bad. Indeed, as I point out, this is historical practice used in sh scripts, well predating POSIX standardization. Using parenthesized subexpressions and -a and -o logical operators in tests/[ is more efficient that relying on expression chaining (via && and ||); it's just that on current machines, the difference is irrelevant.
    – Nominal Animal
    Dec 8 '16 at 0:44












    @Wildcard: However, I do personally prefer to chain the test expressions using && and ||, but the reason is, it makes it easier for us humans to maintain (read, understand, and modify if/when necessary) them without introducing bugs. So, I'm not criticising your suggestion, but only the reasoning behind the suggestion. (For very similar reasons, the .LT., .GE., etc. comparison operators in FORTRAN 77 got much more human-friendly versions <, >=, etc. in later versions.)
    – Nominal Animal
    Dec 8 '16 at 0:51




    @Wildcard: However, I do personally prefer to chain the test expressions using && and ||, but the reason is, it makes it easier for us humans to maintain (read, understand, and modify if/when necessary) them without introducing bugs. So, I'm not criticising your suggestion, but only the reasoning behind the suggestion. (For very similar reasons, the .LT., .GE., etc. comparison operators in FORTRAN 77 got much more human-friendly versions <, >=, etc. in later versions.)
    – Nominal Animal
    Dec 8 '16 at 0:51












    up vote
    11
    down vote













    [ aka test sees:



     argc: 1 2 3 4  5 6 7 8
    argv: ( = 1 -a 1 = 1 ]


    test accepts subexpressions in parentheses; so it thinks that the left parenthesis opens a subexpression and is trying to parse it; the parser sees = as the first thing in the subexpression and thinks that it is an implicit string-length test, so it is happy; the subexpression should then be followed by a right parenthesis, and instead the parser finds 1 instead of ). And it complains.



    When test has exactly three arguments, and the middle argument is one of the recognized operators, it applies that operator to the 1st and 3rd arguments without looking for subexpressions in parentheses.



    For the full details look at man bash, search for test expr.



    Conclusion: The parsing algorithm used by test is complicated. Use only simple expressions and use the shell operators !, && and || to combine them.






    share|improve this answer



























      up vote
      11
      down vote













      [ aka test sees:



       argc: 1 2 3 4  5 6 7 8
      argv: ( = 1 -a 1 = 1 ]


      test accepts subexpressions in parentheses; so it thinks that the left parenthesis opens a subexpression and is trying to parse it; the parser sees = as the first thing in the subexpression and thinks that it is an implicit string-length test, so it is happy; the subexpression should then be followed by a right parenthesis, and instead the parser finds 1 instead of ). And it complains.



      When test has exactly three arguments, and the middle argument is one of the recognized operators, it applies that operator to the 1st and 3rd arguments without looking for subexpressions in parentheses.



      For the full details look at man bash, search for test expr.



      Conclusion: The parsing algorithm used by test is complicated. Use only simple expressions and use the shell operators !, && and || to combine them.






      share|improve this answer

























        up vote
        11
        down vote










        up vote
        11
        down vote









        [ aka test sees:



         argc: 1 2 3 4  5 6 7 8
        argv: ( = 1 -a 1 = 1 ]


        test accepts subexpressions in parentheses; so it thinks that the left parenthesis opens a subexpression and is trying to parse it; the parser sees = as the first thing in the subexpression and thinks that it is an implicit string-length test, so it is happy; the subexpression should then be followed by a right parenthesis, and instead the parser finds 1 instead of ). And it complains.



        When test has exactly three arguments, and the middle argument is one of the recognized operators, it applies that operator to the 1st and 3rd arguments without looking for subexpressions in parentheses.



        For the full details look at man bash, search for test expr.



        Conclusion: The parsing algorithm used by test is complicated. Use only simple expressions and use the shell operators !, && and || to combine them.






        share|improve this answer














        [ aka test sees:



         argc: 1 2 3 4  5 6 7 8
        argv: ( = 1 -a 1 = 1 ]


        test accepts subexpressions in parentheses; so it thinks that the left parenthesis opens a subexpression and is trying to parse it; the parser sees = as the first thing in the subexpression and thinks that it is an implicit string-length test, so it is happy; the subexpression should then be followed by a right parenthesis, and instead the parser finds 1 instead of ). And it complains.



        When test has exactly three arguments, and the middle argument is one of the recognized operators, it applies that operator to the 1st and 3rd arguments without looking for subexpressions in parentheses.



        For the full details look at man bash, search for test expr.



        Conclusion: The parsing algorithm used by test is complicated. Use only simple expressions and use the shell operators !, && and || to combine them.







        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Dec 8 '16 at 1:28









        G-Man

        12.3k92961




        12.3k92961










        answered Dec 7 '16 at 10:26









        AlexP

        6,9161024




        6,9161024






























             

            draft saved


            draft discarded



















































             


            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f328625%2ferror-in-shell-bracket-test-when-string-is-a-left-parenthesis%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

            Morgemoulin

            Scott Moir

            Souastre