-1

I want to separate a numeric decimal number, storing the integer part in bash variable ts and the fractional part in variable fr.

Had a go with parameter expansion, but keep getting the original number.

t=13.491675 
ts=${t#![0-9]*}
fr=${t%*![0-9]}
Han
  • 15
  • 4
  • why not split on the `.`? do you have other numbers that you need to split that are not delimited by `.`? do you have to address numbers with the a 1000's separator? do you need to support european numbers (ie, comma instead of period)? I'm surprised you're not getting an error (eg, `-bash: ![0: event not found`) – markp-fuso Aug 20 '21 at 13:52

3 Answers3

1

This should work with any decimal separator:

shopt -s extglob
t=13.491675
ts="${t%%[^0-9]+([0-9])}"
fr="${t##+([0-9])[^0-9]}"
Andrej Podzimek
  • 2,409
  • 9
  • 12
  • 2
    Why does the use of `extglob` need a point? What is the point of using anything at all? If this was a technical (rather than rhetorical) question, the answer is that `extglob` enables (for example) the `+(...)` construct. Bash uses globs in many ways, not only for matching file sytem paths. Globs are used also in string comparisons and in expansions such as these. – Andrej Podzimek Aug 20 '21 at 14:36
  • 2
    Very nice, upvoted. Even if, according your Wikipedia link, we are not 100% sure it works in Antarctic. More seriously, as we don't know if the decimal separator shall be retained in the fractional part, it could be interesting to add a solution that preserves it (`"${t##+([0-9])}"`). – Renaud Pacalet Aug 20 '21 at 14:43
  • Because it is not needed here. This can be achieved using ordinary globs as well, hence the downvote. – oguz ismail Aug 20 '21 at 15:30
  • Please post a 100% equivalent example with ordinary globs then. – Andrej Podzimek Aug 20 '21 at 15:44
  • 1
    @AndrejPodzimek Oguz might have been thinking of these two: `${t%%[,.]*}` & `${t##*[,.]}`. In any case, don't take any of these personally, it's just us lawyers talking the intricacies of the Bash law :) – Ionuț G. Stan Aug 20 '21 at 16:04
  • @IonuțG.Stan I was thinking of this: `ts=${t%[!0-9]*} fr=${t#"$ts"?}`. If it is certain that `t` is a proper decimal non-integer, this is enough. Otherwise none of the solutions proposed on this page is gonna work. – oguz ismail Aug 20 '21 at 16:48
  • @oguzismail that's a nice solution, especially the use of a word expansion in the pattern position of the `#` expansion. – Ionuț G. Stan Aug 20 '21 at 19:57
1

Use plain POSIX expansion:

#!/usr/bin/env sh

t=13.491675

# Trim-out shortest trailing dot followed by any number of any character
ts=${t%.*}

# Cut shortest leading any number of any character followed by a dot
fr=${t#*.}

# Debug printout
printf 't=%s\nts=%d\nfr=%d\n' "$t" "$ts" "$fr"

Or use Bash's here-string to split with the Internal Field Separator set to the dot:

#!/usr/bin/env bash

t=13.491675

IFS=. read -r ts fr <<<"$t"

printf 't=%s\nts=%d\nfr=%d\n' "$t" "$ts" "$fr"

Or same with IFS split an unquoted variable as arguments

#!/usr/bin/env sh

t=13.491675

# Save IFS
__OIFS="$IFS"

IFS=.
set -- $t
ts=$1
fr=$2

IFS=$__OIFS

printf 't=%s\nts=%d\nfr=%d\n' "$t" "$ts" "$fr"
Léa Gris
  • 17,497
  • 4
  • 32
  • 41
-1

Let's first treat numbers as numbers and use bc if it's available:

$ t=13.491675
$ read ts fr < <(printf 't=%s; print t/1," ",t-t/1\n' "$t" | bc)
$ echo "$ts + $fr = $t"
13 + .491675 = 13.491675

The same can be acomplished with the computing capabilities of awk:

$ read ts fr < <(echo "$t" | awk '{printf("%d %f\n", int($1), $1-int($1))}')
$ echo "$ts + $fr = $t"
13 + 0.491675 = 13.491675

Note the extra leading 0 in the fractional part.

Or a mixture of printf to extract the integer part and bc to extract the fractional part:

$ t=13.491675
$ printf -v ts '%.0f\n' "$t"
$ fr=$(printf '%s-%d\n' "$t" "$i" | bc)
$ echo "$ts + $fr = $t"
13 + .491675 = 13.491675

Or, if bc is not an option, printf and bash parameter expansion to finish the job:

$ t=13.491675
$ printf -v ts '%.0f\n' "$t"
$ fr="${t#$i}"
$ echo "$ts + $fr = $t"
13 + .491675 = 13.491675

To get rid of the decimal separator, if needed, we can use one more parameter expansion:

$ fr="${fr:1}"

or:

$ fr="${fr:2}"

for the awk-based solution.

Renaud Pacalet
  • 25,260
  • 3
  • 34
  • 51