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

Clash Royale CLAN TAG#URR8PPP
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
add a comment |
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
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
add a comment |
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
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
shell variable string test
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
add a comment |
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
add a comment |
2 Answers
2
active
oldest
votes
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 whetherxis a non-empty string (so you can see there's a conflict with the above).- in some shells/
[s,-aand-ocan 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 xcan be eitherand(nonempty("!"), nonempty("x")ornot(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=
add a comment |
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
%
add a comment |
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
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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 whetherxis a non-empty string (so you can see there's a conflict with the above).- in some shells/
[s,-aand-ocan 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 xcan be eitherand(nonempty("!"), nonempty("x")ornot(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=
add a comment |
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 whetherxis a non-empty string (so you can see there's a conflict with the above).- in some shells/
[s,-aand-ocan 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 xcan be eitherand(nonempty("!"), nonempty("x")ornot(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=
add a comment |
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 whetherxis a non-empty string (so you can see there's a conflict with the above).- in some shells/
[s,-aand-ocan 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 xcan be eitherand(nonempty("!"), nonempty("x")ornot(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=
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 whetherxis a non-empty string (so you can see there's a conflict with the above).- in some shells/
[s,-aand-ocan 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 xcan be eitherand(nonempty("!"), nonempty("x")ornot(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=
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
add a comment |
add a comment |
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
%
add a comment |
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
%
add a comment |
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
%
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
%
answered Dec 21 '18 at 22:25
JdeBP
33.3k469156
33.3k469156
add a comment |
add a comment |
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.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
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