Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I need to convert human-readable sizes into bytes. Unfortunately numfmt function is not accessible for me. Is there any other alternative? I need a shell/bash function to call from inside the sh script.

1K should be converted to 1000, 1M to 1000 000 etc.

share|improve this question

2 Answers 2

up vote 6 down vote accepted

If Perl is available:

echo your_string | perl -ne '
    BEGIN{ %suffixes=( K => 3, M => 6, G => 9, T => 12, P => 15, E => 18 ); 
           $suffix_regex = join "|", keys %suffixes;
    }
    s/([0-9][0-9]*(?:\.[0-9]+)?)($suffix_regex)/$1e$suffixes{$2}/g;
    printf "%d\n", $_;
'

Since this is to be used as a text filter, it's more appropriate to define it as a perl script and pipe input into it. You can embed it inside a shell function by adding the following to your .bashrc (assuming you use Bash):

myconvert() {
    cat <<'EOF' >/dev/null
#!--perl-- -n
BEGIN{ %suffixes=( K => 3, M => 6, G => 9, T => 12, P => 15, E => 18 ); 
       $suffix_regex = join "|", keys %suffixes;
}
s/([0-9][0-9]*(?:\.[0-9]+)?)($suffix_regex)/$1e$suffixes{$2}/g;
printf "%d\n", $_;
__END__
EOF
   exec perl -x "/path/to/your/.bashrc"
}

A solution using sed and bc:

myconvert(){
  sed '
      s/\([0-9][0-9]*\(\.[0-9]\+\)\?\)K/\1*1000/g;
      s/\([0-9][0-9]*\(\.[0-9]\+\)\?\)M/\1*1000000/g;
      s/\([0-9][0-9]*\(\.[0-9]\+\)\?\)G/\1*1000000000/g;
      s/\([0-9][0-9]*\(\.[0-9]\+\)\?\)T/\1*1000000000000/g;
      s/\([0-9][0-9]*\(\.[0-9]\+\)\?\)P/\1*1000000000000000/g;
      s/\([0-9][0-9]*\(\.[0-9]\+\)\?\)E/\1*1000000000000000000/g
  ' </dev/stdin | bc | sed 's/\..*$//' # Final sed to remove decimal point
}

Usage (the same for both solutions):

$ echo '5.23K' | myconvert
5230
$ echo '6.27G' | myconvert
6270000000

This solution assumes your input consists only of strings of the form 5.23K (fractional part optional), otherwise, bc won't know what to do with them.

Note

The Perl one-liner could have been inserted as-is into a shell function with < /dev/stdin as with the sed solution. This didn't cross my mind when writing the first version of the answer. I'm leaving the perl -x trick up there in case it can benefit someone else, though.

share|improve this answer
    
I need a shell function. I tried for 5.1M and result is 5.1000000 where decimal point is not somewhat expected. Is it possible to wrap it into function with parameter? –  jaksky Feb 13 at 12:10
    
@jaksky See if the edit helps. –  Joseph R. Feb 13 at 12:35
    
Sorry I really need a shell/bash function. I cannot use perl. Sorry for that. –  jaksky Feb 13 at 12:47
    
@jaksky Does that exclude other tools like sed and awk as well? You need a solution that relies only on shell builtins? –  Joseph R. Feb 13 at 12:54
    
@ Joseph - no. awk, sed etc. are ok. I found numfmt function but that isn't unfortunatelly available. Ideal use case would be to call that function to convert. e.g. toBytes 5.23K would yield 5230. All is done on one line as those values are parsed from some text. –  jaksky Feb 13 at 13:15

With gawk, you could do:

convert() {
  gawk --use-lc-numeric -v RS='[KMGTPE]' '
    match($0, /[0-9]*['"$(locale decimal_point)"']?[0-9]+$/) {
      $0 = substr($0, 1, RSTART-1) sprintf("%'\''.17g", \
        substr($0,RSTART) * (10**(index(RS,RT)*3-3)))
      RT=""
    }{printf "%s", $0 RT}'
}

Then:

$ echo "You owe me £1.23M" | convert
You owe me £1,230,000
$ export LC_ALL=fr_FR.UTF-8
$ echo "Tu me dois 1,34M€" | convert
Tu me dois 1 340 000€
$ echo "Sie schulden mir 1,34M€" | LC_ALL=de_DE.UTF-8 convert
Sie schulden mir 1.340.000€

If you don't have to worry about decimal or thousand separators (your question is not clear on that as you used a French style thousand separator there), and use . as the decimal separator and no thousand separator, you could do:

convert() {
  perl -pe 's/\d*\.?\d+([KMGTPE])/$&*10**(index(KMGTPE,$1)*3+3)/ge'
}

If you'd rather 112334E be displayed as 112334000000000000000000 instead of 1.12334e+23, you can add the -Mbignum option to perl above.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.