On 17.6. 18:47, Greg Wooledge wrote:
On Mon, Jun 17, 2019 at 02:30:27PM +0100, Jeremy Townshend wrote:
In the meantime it would seem cautionary to advise against the pitfall of
using base# prefixed to variables (contrary to
mywiki.wooledge.org/ArithmeticExpression) unless you can be confident that
they will never be decremented below zero.

Fair point.  I've updated <https://mywiki.wooledge.org/ArithmeticExpression>
and <https://mywiki.wooledge.org/BashPitfalls>.

Good!

I still wish this could be fixed to do the useful thing without any workarounds, given it's what ksh and zsh do, and since this is the second time it comes up on the list, it appears to be surprising to users, too.

The <base># prefix is already an extension of the C numeric constant syntax, so extending it further to include an optional sign wouldn't seem in inappropriate.


I took a look last night and made some sort of a patch. It seems to work, though I'm not sure if I've missed any corner cases. Apart from the digitless '10#', the behaviour matches ksh and zsh, I made it an error, they apparently allow it.

  $ cat test.sh
  echo $(( 10 * 10#-123 ))  # -1230
  echo $(( 10 * 10#-008 ))  #   -80
  echo $(( 10 * 10#1+23 ))  #    10*1 + 23 = 33
  echo $(( 10# ))           #  error

  $ ./bash test.sh
  -1230
  -80
  33
  test.sh: line 5: 10#: no digits in number (error token is "10#")

  $ ksh test.sh
  -1230
  -80
  33
  0


--
Ilkka Virta / itvi...@iki.fi
--- expr.c.orig 2018-12-17 16:32:21.000000000 +0200
+++ expr.c      2019-06-18 08:30:31.110851666 +0300
@@ -1386,8 +1386,12 @@ readtok ()
     }
   else if (DIGIT(c))
     {
-      while (ISALNUM (c) || c == '#' || c == '@' || c == '_')
-       c = *cp++;
+      unsigned char prevc = c;
+      while (ISALNUM (c) || c == '#' || c == '@' || c == '_' || ((c == '+' || 
c == '-') && prevc == '#')) 
+        {
+          prevc = c;
+          c = *cp++;
+        }
 
       c = *--cp;
       *cp = '\0';
@@ -1531,6 +1535,8 @@ strlong (num)
   register char *s;
   register unsigned char c;
   int base, foundbase;
+  int digits = 0;
+  char sign = 0;
   intmax_t val;
 
   s = num;
@@ -1569,8 +1575,16 @@ strlong (num)
 
          base = val;
          val = 0;
+         digits = 0;
          foundbase++;
        }
+      else if (c == '-' || c == '+')
+        {
+          if (digits > 0 || sign != 0)
+            evalerror (_("invalid number"));
+          
+          sign = c;
+        }
       else if (ISALNUM(c) || (c == '_') || (c == '@'))
        {
          if (DIGIT(c))
@@ -1588,11 +1602,18 @@ strlong (num)
            evalerror (_("value too great for base"));
 
          val = (val * base) + c;
+         digits++;
        }
       else
        break;
     }
 
+  if (sign == '-')
+    val *= -1;
+  
+  if (digits == 0)
+    evalerror (_("no digits in number"));
+    
   return (val);
 }
 

Reply via email to