behavior of arithmetic evaluation operator inside extended test operator

2020-05-19 Thread Inian Vasanth
Machine: x86_64
OS: linux-gnu
Compiler: gcc
Compilation CFLAGS:  -DPROGRAM='bash' -DCONF_HOSTTYPE='x86_64'
-DCONF_OSTYPE='linux-gnu' -DCONF_MACHTYPE='x86_64-unknown-linux-gnu'
-DCONF_VENDOR='unknown' -DLOCALEDIR='/usr/local/share/locale'
-DPACKAGE='bash' -DSHELL -DHAVE_CONFIG_H   -I.  -I. -I./include -I./lib
-g -O2 -Wno-parentheses -Wno-format-security
uname output: Linux DevVM 3.10.0-123.20.1.el7.x86_64 #1 SMP Thu Jan 29
18:05:33 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
Machine Type: x86_64-unknown-linux-gnu

Bash Version: 4.4
Patch Level: 0
Release Status: release

Description:

The behavior of arithmetic context operator $((..)) inside [[..]] is not so
well defined. The GNU bash documentation says that $((..)) is not one of
the valid primaries supported inside [[..]]. The downside is the operator
without $ when used as ((..)) just behaves as double grouping, but $((..))
behaves as a valid arithmetic evaluation followed by non empty string
comparison `-n`

Steps

The first snippet produces incorrect results because of lexicographic
comparison of the two operands, while the second does an actual arithmetic
evaluation which is NOT DOCUMENTED but in the end does a string not empty
check with (-n 0) of the arithmetic evaluation result (not the exit code).

This behavior needs to be explained clearly or documented well.

bash -cx '[[ (( 100 < 3 )) ]] && echo ok'
+ bash -cx '[[ (( 100 < 3 )) ]] && echo ok'
+ [[ 100 < 3 ]]
+ echo ok

bash -cx '[[ $(( 100 < 3 )) ]] && echo ok'
+ bash -cx '[[ $(( 100 < 3 )) ]] && echo ok'
+ [[ -n 0 ]]
+ echo ok
ok

bash -cx '[[ $(( 100 < 300 )) ]] && echo ok'
+ bash -cx '[[ $(( 100 < 300 )) ]] && echo ok'
+ [[ -n 1 ]]
+ echo ok
ok




-- 
Regards,
INIAN VASANTH P


Re: behavior of arithmetic evaluation operator inside extended test operator

2020-05-19 Thread Greg Wooledge
On Tue, May 19, 2020 at 06:10:30PM +0530, Inian Vasanth wrote:
> The behavior of arithmetic context operator $((..)) inside [[..]] is not so
> well defined.

It's simply a substitution.  The $(( )) is evaluated, and the result
is placed into the [[ ]] command as a word.

> The downside is the operator
> without $ when used as ((..)) just behaves as double grouping,

Correct, as you demonstrated below.

> but $((..))
> behaves as a valid arithmetic evaluation followed by non empty string
> comparison `-n`

Well, yes.  What did you *expect*?  What are you trying to do?

> bash -cx '[[ (( 100 < 3 )) ]] && echo ok'
> + bash -cx '[[ (( 100 < 3 )) ]] && echo ok'
> + [[ 100 < 3 ]]
> + echo ok

The parentheses here are doubly redundant.  You're performing a grouping,
but there is only one operator, so there's nothing to group *for*.  And
you're doing the grouping twice, for no discernable reason.

You're also using the < operator in a [[ ]] command, which is string
comparison, not integer comparison.

If your goal was simply "check whether the integer 100 is less than the
integer 3", you don't need to use the [[ ]] command at all.

if ((100 < 3)); then
  echo ok
else
  echo not ok
fi

If you insist on using [[ ]] for some reason, integer comparisons can be
forced with the -lt -gt (et al.) operators.

if [[ 100 -lt 3 ]]; then
...

> bash -cx '[[ $(( 100 < 3 )) ]] && echo ok'
> + bash -cx '[[ $(( 100 < 3 )) ]] && echo ok'
> + [[ -n 0 ]]
> + echo ok
> ok

Here, you are forcing an arithmetic substitution to be explicitly performed,
before the [[ ]] command begins.  The result of the arithmetic substitution
is a word, and that word will be checked for non-zero-length by the [[
command.  It is exactly as if you had written:

tmp=$((100 < 3))
[[ $tmp ]] && ...

The form [[ $x ]] is just the same as [[ -n $x ]] and that's what you
have written here.


> bash -cx '[[ $(( 100 < 300 )) ]] && echo ok'
> + bash -cx '[[ $(( 100 < 300 )) ]] && echo ok'
> + [[ -n 1 ]]
> + echo ok
> ok

Same.  It doesn't matter whether the result of the arithmetic expression
is 1 (true) or 0 (false), because both of these words are strings of
non-zero length.

To repeat: if your goal is to compare integers, you should use one of
these forms:

if ((x < y)); then ...

if [[ $x -lt $y ]]; then ...

if test "$x" -lt "$y"; then ...

if [ "$x" -lt "$y" ]; then ...


Remember, the [ and [[ commands are just that: *commands*.  They are not
a part of the "if" syntax.  You don't *need* them every time you use
an "if".  You don't need to bend over backwards trying to work out how
to merge the command you actually want to use, together with the [[
command.

Just omit the [[ if it's not the command you want.



Re: behavior of arithmetic evaluation operator inside extended test operator

2020-05-19 Thread Inian Vasanth
Thanks Greg for the explanation. Yes your explanation  aligns with my
understanding too.

My recommendation was to check if this behavior needs to be documented as a
side-note in this section of
http://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Bash-Conditional-Expressions
to
explain that, any other primaries other than the ones mentioned above will
be evaluated as a literal string result. I also tried finding an
explanation in your wiki at
https://mywiki.wooledge.org/BashGuide/TestsAndConditionals, but there
wasn't an explicit point made.

On Tue, May 19, 2020 at 7:40 PM Greg Wooledge  wrote:

> On Tue, May 19, 2020 at 06:10:30PM +0530, Inian Vasanth wrote:
> > The behavior of arithmetic context operator $((..)) inside [[..]] is not
> so
> > well defined.
>
> It's simply a substitution.  The $(( )) is evaluated, and the result
> is placed into the [[ ]] command as a word.
>
> > The downside is the operator
> > without $ when used as ((..)) just behaves as double grouping,
>
> Correct, as you demonstrated below.
>
> > but $((..))
> > behaves as a valid arithmetic evaluation followed by non empty string
> > comparison `-n`
>
> Well, yes.  What did you *expect*?  What are you trying to do?
>
> > bash -cx '[[ (( 100 < 3 )) ]] && echo ok'
> > + bash -cx '[[ (( 100 < 3 )) ]] && echo ok'
> > + [[ 100 < 3 ]]
> > + echo ok
>
> The parentheses here are doubly redundant.  You're performing a grouping,
> but there is only one operator, so there's nothing to group *for*.  And
> you're doing the grouping twice, for no discernable reason.
>
> You're also using the < operator in a [[ ]] command, which is string
> comparison, not integer comparison.
>
> If your goal was simply "check whether the integer 100 is less than the
> integer 3", you don't need to use the [[ ]] command at all.
>
> if ((100 < 3)); then
>   echo ok
> else
>   echo not ok
> fi
>
> If you insist on using [[ ]] for some reason, integer comparisons can be
> forced with the -lt -gt (et al.) operators.
>
> if [[ 100 -lt 3 ]]; then
> ...
>
> > bash -cx '[[ $(( 100 < 3 )) ]] && echo ok'
> > + bash -cx '[[ $(( 100 < 3 )) ]] && echo ok'
> > + [[ -n 0 ]]
> > + echo ok
> > ok
>
> Here, you are forcing an arithmetic substitution to be explicitly
> performed,
> before the [[ ]] command begins.  The result of the arithmetic substitution
> is a word, and that word will be checked for non-zero-length by the [[
> command.  It is exactly as if you had written:
>
> tmp=$((100 < 3))
> [[ $tmp ]] && ...
>
> The form [[ $x ]] is just the same as [[ -n $x ]] and that's what you
> have written here.
>
>
> > bash -cx '[[ $(( 100 < 300 )) ]] && echo ok'
> > + bash -cx '[[ $(( 100 < 300 )) ]] && echo ok'
> > + [[ -n 1 ]]
> > + echo ok
> > ok
>
> Same.  It doesn't matter whether the result of the arithmetic expression
> is 1 (true) or 0 (false), because both of these words are strings of
> non-zero length.
>
> To repeat: if your goal is to compare integers, you should use one of
> these forms:
>
> if ((x < y)); then ...
>
> if [[ $x -lt $y ]]; then ...
>
> if test "$x" -lt "$y"; then ...
>
> if [ "$x" -lt "$y" ]; then ...
>
>
> Remember, the [ and [[ commands are just that: *commands*.  They are not
> a part of the "if" syntax.  You don't *need* them every time you use
> an "if".  You don't need to bend over backwards trying to work out how
> to merge the command you actually want to use, together with the [[
> command.
>
> Just omit the [[ if it's not the command you want.
>


-- 
Regards,
INIAN VASANTH P


Re: behavior of arithmetic evaluation operator inside extended test operator

2020-05-19 Thread Oğuz
On Tue, May 19, 2020 at 8:36 PM Inian Vasanth  wrote:
>
> Thanks Greg for the explanation. Yes your explanation  aligns with my
> understanding too.
>
> My recommendation was to check if this behavior needs to be documented as a
> side-note in this section of
> http://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Bash-Conditional-Expressions
> to
> explain that, any other primaries other than the ones mentioned above will
> be evaluated as a literal string result.

I don't think that's necessary, under `[[…]]`, it says

Expressions may be combined using the following operators, listed
in decreasing order of precedence:

( expression )
Returns the value of expression. This may be used to override
the normal precedence of operators.

With that it shouldn't be that hard to figure out `[[ (( x < y )) ]]`
equals to `[[ x < y ]]` in function.

> I also tried finding an
> explanation in your wiki at
> https://mywiki.wooledge.org/BashGuide/TestsAndConditionals, but there
> wasn't an explicit point made.
>
> On Tue, May 19, 2020 at 7:40 PM Greg Wooledge  wrote:
>
> > On Tue, May 19, 2020 at 06:10:30PM +0530, Inian Vasanth wrote:
> > > The behavior of arithmetic context operator $((..)) inside [[..]] is not
> > so
> > > well defined.
> >
> > It's simply a substitution.  The $(( )) is evaluated, and the result
> > is placed into the [[ ]] command as a word.
> >
> > > The downside is the operator
> > > without $ when used as ((..)) just behaves as double grouping,
> >
> > Correct, as you demonstrated below.
> >
> > > but $((..))
> > > behaves as a valid arithmetic evaluation followed by non empty string
> > > comparison `-n`
> >
> > Well, yes.  What did you *expect*?  What are you trying to do?
> >
> > > bash -cx '[[ (( 100 < 3 )) ]] && echo ok'
> > > + bash -cx '[[ (( 100 < 3 )) ]] && echo ok'
> > > + [[ 100 < 3 ]]
> > > + echo ok
> >
> > The parentheses here are doubly redundant.  You're performing a grouping,
> > but there is only one operator, so there's nothing to group *for*.  And
> > you're doing the grouping twice, for no discernable reason.
> >
> > You're also using the < operator in a [[ ]] command, which is string
> > comparison, not integer comparison.
> >
> > If your goal was simply "check whether the integer 100 is less than the
> > integer 3", you don't need to use the [[ ]] command at all.
> >
> > if ((100 < 3)); then
> >   echo ok
> > else
> >   echo not ok
> > fi
> >
> > If you insist on using [[ ]] for some reason, integer comparisons can be
> > forced with the -lt -gt (et al.) operators.
> >
> > if [[ 100 -lt 3 ]]; then
> > ...
> >
> > > bash -cx '[[ $(( 100 < 3 )) ]] && echo ok'
> > > + bash -cx '[[ $(( 100 < 3 )) ]] && echo ok'
> > > + [[ -n 0 ]]
> > > + echo ok
> > > ok
> >
> > Here, you are forcing an arithmetic substitution to be explicitly
> > performed,
> > before the [[ ]] command begins.  The result of the arithmetic substitution
> > is a word, and that word will be checked for non-zero-length by the [[
> > command.  It is exactly as if you had written:
> >
> > tmp=$((100 < 3))
> > [[ $tmp ]] && ...
> >
> > The form [[ $x ]] is just the same as [[ -n $x ]] and that's what you
> > have written here.
> >
> >
> > > bash -cx '[[ $(( 100 < 300 )) ]] && echo ok'
> > > + bash -cx '[[ $(( 100 < 300 )) ]] && echo ok'
> > > + [[ -n 1 ]]
> > > + echo ok
> > > ok
> >
> > Same.  It doesn't matter whether the result of the arithmetic expression
> > is 1 (true) or 0 (false), because both of these words are strings of
> > non-zero length.
> >
> > To repeat: if your goal is to compare integers, you should use one of
> > these forms:
> >
> > if ((x < y)); then ...
> >
> > if [[ $x -lt $y ]]; then ...
> >
> > if test "$x" -lt "$y"; then ...
> >
> > if [ "$x" -lt "$y" ]; then ...
> >
> >
> > Remember, the [ and [[ commands are just that: *commands*.  They are not
> > a part of the "if" syntax.  You don't *need* them every time you use
> > an "if".  You don't need to bend over backwards trying to work out how
> > to merge the command you actually want to use, together with the [[
> > command.
> >
> > Just omit the [[ if it's not the command you want.
> >
>
>
> --
> Regards,
> INIAN VASANTH P



-- 
Oğuz



Re: behavior of arithmetic evaluation operator inside extended test operator

2020-05-19 Thread Eli Schwartz
On 5/19/20 1:34 PM, Inian Vasanth wrote:
> Thanks Greg for the explanation. Yes your explanation  aligns with my
> understanding too.
> 
> My recommendation was to check if this behavior needs to be documented as a
> side-note in this section of
> http://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Bash-Conditional-Expressions
> to
> explain that, any other primaries other than the ones mentioned above will
> be evaluated as a literal string result. I also tried finding an
> explanation in your wiki at
> https://mywiki.wooledge.org/BashGuide/TestsAndConditionals, but there
> wasn't an explicit point made.

The constructs:

(list)
{ list; }
[[ expression ]]
(( expression ))
for name [ [ in [ word ... ] ] ; ] do list ; done
for (( expr1 ; expr2 ; expr3 )) ; do list ; done
select name [ in word ] ; do list ; done
case word in [ [(] pattern [ | pattern ] ... ) list ;; ] ... esac
if list; then list; [ elif list; then list; ] ... [ else list; ] fi
while list-1; do list-2; done
until list-1; do list-2; done

are listed as *competing* definitions of "what is a compound command".

I don't see anywhere in the definition of "CONDITIONAL EXPRESSIONS" that
states you may use completely arbitrary compound commands as the
expression within a [[ compound command. It seems to me that
"CONDITIONAL EXPRESSIONS" is pretty unambiguously explicit on what
constitutes one.

Any possible reading which even implies that

(( expression ))

is valid when nested inside of

[[ expression ]]

as

[[ (( expression )) ]]

must also imply that one can do

[[ [[ expression ]] ]]

or [[ for i in one two three; do cmd; done ]]


So I'm completely baffled why this might need further clarification.

As for your claim that

[[ $(( 100 < 3 )) ]]

is doing "undocumented arithmetic evaluation",

> Word splitting and pathname expansion are not performed on the words
> between the [[ and ]]; tilde expansion, parameter and variable
> expansion, arithmetic expansion, command substitution, process
> substitution, and quote removal are performed.

And as duly noted in the documentation, $(( 100 < 3 )) is Arithmetic
Expansion and is being performed on the words inside the [[ and ]].

> explain that, any other primaries other than the ones mentioned above
> will be evaluated as a literal string result

The documentation *already* states that. Nothing is valid in a
conditional expression other than the listed primaries, *but* one of
those documented primaries is `[[ string ]]` which is defined to be a
primary identical to `[[ -n string ]]`. Only strings are valid for this
primary, no exceptions. Other primaries don't even accept strings at
all, they accept filenames; filenames just so happen to be a subcategory
of strings. The same can be said of the -t primary, which accepts a file
descriptor number, which is a subcategory of integers, not strings (you
may then argue that an integer is a subcategory of a string), or various
primaries which operate on variable names, which are subcategories of
strings, etc. etc.


The documentation doesn't need changing. People need to realize when
they read the documentation that a conditional expression happens after
other stages of the shell execution process, and that their
understanding of conditional expressions is incomplete if they don't
understand where they are permitted to use them and how to use tilde
expansion, parameter and variable expansion, arithmetic expansion,
command substitution, process substitution, and quote removal in order
to preprocess words to then use in a conditional expression.

To improve their understanding, they must therefore read the definition
of the [[ ]] syntax. Thus, enlightenment shall be obtained.

-- 
Eli Schwartz
Arch Linux Bug Wrangler and Trusted User



signature.asc
Description: OpenPGP digital signature