3

Case Scenerio:

Variable or Array
var=( 002.20 20.020 20.002000 00200 20.02 .020)
for f in ${var[@]}; do echo ${f}; done
Output: 2.2 20.02 20.002 200 20.02 .02

Trying to achive this using pure bash that with help of $BASH_REMATCH capture groups

Tried: 
eg. 
f=20.0000210
[[ $f =~ ([0-9]?\.?0?[1-9]?)(0*$) ]]
echo ${BASH_REMATCH[@]}
210 21 0


Expected:
echo ${BASH_REMATCH[@]
20.000021 0

Can anyone help with this. Please. After debugging it seems bash capture groups are behaving unusually. I dont know how BASH_REMATCH works but from online tutorials i have seen that using () we can split string into BASH_REMATCH array. But something odd with splitting

oK after some debugging it appears unusual behaviour is due to cygwin bash. eg var=0002; echo ${var##0} or echo ${var##+(0)} doesnt work as expected

Don Omar
  • 91
  • 5
  • Check this link if it helps https://stackoverflow.com/questions/18714645/how-can-i-remove-leading-and-trailing-zeroes-from-numbers-with-sed-awk-perl. Still sed and awk works in bash – Tim Aug 12 '22 at 08:45
  • 1
    https://stackoverflow.com/questions/18714645/how-can-i-remove-leading-and-trailing-zeroes-from-numbers-with-sed-awk-perl - May work but not everytime. My script produces floating points with arbitraty zeroes in trailing and leading position. So $BASE_REMATCH capture groups may only help – Don Omar Aug 12 '22 at 08:52
  • `${var##0}` does not work because the longest prefix equal to `0` is... `0`. And for `${var##+(0)}` to work you first need to enable `extglob` (`shopt -s extglob`). Did you? – Renaud Pacalet Aug 12 '22 at 09:53
  • The output you get looks right to me. Seeing your regex from the right: The `0*$` matches of course the final zero. To the left of this, you are expecting a singles non-zero-digit (`[1-9]?`), to the left of this the string `.0`, and of the left of that a single digit. All of these pieces are optional, but we see that to the left of the final 0, you indeed have a non-zero digit (`1`). You don't have a `.0` to the left of it, but it's optional. However you have another digit (`2`) which does match the `[0-9]`. Hence the first rematch group is `21`, and the whole pattern matches `210`. – user1934428 Aug 12 '22 at 09:58

5 Answers5

4

Another way: (Another Use Case: Below Method will help splitting a string based on delimeter)

var=( 002.20 20.020 20.002000 00200 20.02 .020)
for f in ${var[@]}; do 
[[ $f =~ (^[^\.?]*.)(.*) ]]; 
INT=$(echo ${BASH_REMATCH[1]}); 
FRAC=$(echo ${BASH_REMATCH[2]}); 
echo "$f --> ${INT##+(0)}${FRAC%%+(0)}"; done
Output
002.20 --> 2.2
20.020 --> 20.02
20.002000 --> 20.002
00200 --> 200
20.02 --> 20.02
.020 --> .02

Splits and captures the Integer part and Fractional Part

Capture 1 - (^[^.?].) --> Matches beginning to decimal point which is optional in case of natural numbers and append "." to result
Capture 2 - (.
) --> Fractional part

Using BASH_REMATCH[1] & [2], stores integer in INT and Fraction in FRAC

Using string expansion remove leading "0" from INT and trailing "0" from FRAC

Tathastu Pandya
  • 326
  • 1
  • 8
2

If you want to use [[ var =~ REGEX ]] and then BASH_REMATCH, you can do:

#!/bin/bash

var=( 002.20 20.020 20.002000 00200 20.02 .020)

for i in "${var[@]}"; do             # loop over each element in array
  if [[ $i =~ "." ]]; then           # does it contain '.'?
    [[ $i =~ ^0*(.*[^0]+)0*$ ]]      # use regex to trim both ends
  else
    [[ $i =~ ^0*([^0]+.*$) ]]        # otherwise trim from front.
  fi
  echo ${BASH_REMATCH[1]}            # output result
done

Example Output

2.2
20.02
20.002
200
20.02
.02

You can change echo as needed to output with printf for line control as desired.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
1
$ shopt -s extglob
$ var=( 002.20 20.020 20.002000 00200 20.02 .020 )
$ for f in "${var[@]##+(0)}"; do
    [[ "$f" =~ \. ]] && f="${f%%+(0)}"
    printf '%s\n' "$f"
  done
2.2
20.02
20.002
200
20.02
.02
Renaud Pacalet
  • 25,260
  • 3
  • 34
  • 51
  • One glitch 200 needs to be 200 and not 2 since it does not have any floating point – Don Omar Aug 12 '22 at 09:07
  • Oh!, good point. Fixed. – Renaud Pacalet Aug 12 '22 at 09:15
  • can you explain regex f%%+(0) and why f%%0* or f%%[0]* isn't working – Don Omar Aug 12 '22 at 09:37
  • `+(pattern)` is one of the extended pattern matching operators that the `extglob` option enable. It matches one or more occurrences of `pattern`. `f%%0*` and `f%%[0]*` do not work because in pattern matching `*` does not mean _0 or more times_, like in regular expressions, it means _any string_, like in pathname expansion. – Renaud Pacalet Aug 12 '22 at 09:41
1

Using a regular expression approach in bash:

var=(002.20 20.020 20.002000 00200 20.02 .020 20.0000210)

# regex may match an empty string also
re='^0*([1-9][0-9]*)?(\.[0-9]*[1-9])?0*$'
for n in "${var[@]}"; do
   [[ $n =~ $re ]] && echo "$n => ${BASH_REMATCH[1]}${BASH_REMATCH[2]}"
done

Output:

002.20 => 2.2
20.020 => 20.02
20.002000 => 20.002
00200 => 200
20.02 => 20.02
.020 => .02
20.0000210 => 20.000021

RegEx Demo and Details

RegEx Details:

  • ^: Start
  • 0*: Match 0 or more zeroes
  • ([1-9][0-9]*)?: Capture group #1 (optional) to match digit 1-9 followed by 0 or more of any digits
  • (\.[0-9]*[1-9])?: Capture group #2 (optional) to match dot then 0 or more of any digits then digit 1-9
  • 0*: Match 0 or more zeroes
  • $: End
  • In substitution we only keep values captured in capture group #1 and #2 ignoring leading and trailing zeroes.
anubhava
  • 761,203
  • 64
  • 569
  • 643
0

You can do it via sed.

$ var=( 002.20 20.020 20.002000 00200 20.02 .020)
$ for f in ${var[@]}; do echo $(echo $f|sed -r 's/^0+|0+$//g'); done
2.2
20.02
20.002
2
20.02
.02
WeDBA
  • 343
  • 4
  • 7