5

I have a file on a Linux system with various numbers, all on a single line (numbers_in_one_line.txt):

1941102556 1750145810 2604905 7000793682 5160065824 3000350768 6449300295 3046118928 12693055728 257664864 13471943769

with

tr ' ' '\n' < numbers_in_one_line.txt > numbers_line_by_line.txt
printf '| %12s  |\n' $(cat numbers_line_by_line.txt) > numbers_line_by_line_right.txt

numbers_line_by_line_right.txt :

|   5150876365  |
|   4059965777  |
|    842556472  |
|   3304990536  |
|   5722391148  |
|   6186870011  |
|     70057009  |
|   2808271890  |
|   1812899872  |
|  23803433186  |
|   1010834863  |

but I want it to look like this (numbers_line_by_line_right_dots.txt) :

|   5.150.876.365  |
|   4.059.965.777  |
|     842.556.472  |
|   3.304.990.536  |
|   5.722.391.148  |
|   6.186.870.011  |
|      70.057.009  |
|   2.808.271.890  |
|   1.812.899.872  |
|  23.803.433.186  |
|   1.010.834.863  |

How can I insert a . after every three digits from the right?

6
  • Sidenote: Using . to seperate thousands are a bad idea, same with ,. Use ', ` ` or _. Commented Jun 24 at 10:50
  • 2
    How did 1941102556 in the input become 5.150.876.365 in the output? Please make sure the expected output you provide is the output you expect from the input you provide, not the output from some other input you haven't shown us. Give us input+output that clearly demonstrates your requirements and we can copy/paste your example to test with so it's as easy as possible for as many people as possible to help you.
    – Ed Morton
    Commented Jun 24 at 11:03
  • Related: unix.stackexchange.com/a/622314/116858
    – Kusalananda
    Commented Jun 24 at 14:23
  • @12431234123412341234123 some locales use comma (,) to separate the integer and fractional parts of a number. In these places it might make sense to use dot (.) as a thousands separator. (I would prefer your own suggestions, myself.) Commented Jun 24 at 16:26
  • @ChrisDavies And there are locales that use . as a decimal seperator, this is why you should not use , nor . as a thousands separator. There is no way to know how much 123.456,789 kg are. Commented Jun 24 at 16:57

5 Answers 5

12

Using printf you could use the single quote ' FLAG for the numbers to be grouped with thousands' grouping characters. In the man pages of printf(3):

For some numeric conversions a radix character ("decimal point") or thousands' grouping character is used. The actual character used depends on the LC_NUMERIC part of the locale. (See setlocale(3).) The POSIX locale uses '.' as radix character, and does not have a grouping character. Thus, printf("%'.2f", 1234567.89); results in "1234567.89" in the POSIX locale, in "1234567,89" in the nl_NL locale, and in "1.234.567,89" in the da_DK locale. ... ' For decimal conversion (i, d, u, f, F, g, G) the output is to be grouped with thousands' grouping characters if the locale information indicates any. (See setlocale(3).) Note that many versions of gcc(1) cannot parse this option and will issue a warning. (SUSv2 did not include %'F, but SUSv3 added it.) Note also that the default locale of a C program is "C" whose locale information indicates no thousands' grouping character. Therefore, without a prior call to setlocale(3), no thousands' grouping characters will be printed.

You can see the thousands separator (thousands_sep) value of each locale by using the locale command:

$ LC_NUMERIC=POSIX locale -k thousands_sep
thousands_sep=""
$ LC_NUMERIC=en_US locale -k thousands_sep
thousands_sep=","
$ LC_NUMERIC=da_DK locale -k thousands_sep
thousands_sep="."

Or, to check all locales you currently have generated on your machine, use:

locale -a | 
  while read l; do 
    printf '%s: %s\n' "$l" "$(LC_NUMERIC=$l locale -k thousands_sep)"
done

So to complete your task, you can just use the following printf command for each number (you can change da_DK to any locale you have installed that has the right thousands separator):

LC_NUMERIC=da_DK printf "%'d\n"

And to print it the way you requested, you do not need intermediate file:

# LC_NUMERIC=da_DK printf "| %'15d |\n" $(cat numbers_in_one_line.txt)

# Or:

$ LC_NUMERIC=da_DK printf "| %'15d |\n" $(<numbers_in_one_line.txt)
|   1.941.102.556 |
|   1.750.145.810 |
|       2.604.905 |
|   7.000.793.682 |
|   5.160.065.824 |
|   3.000.350.768 |
|   6.449.300.295 |
|   3.046.118.928 |
|  12.693.055.728 |
|     257.664.864 |
|  13.471.943.769 |

Or, if there are too many numbers to fit into one command line you can use xargs:

LC_NUMERIC=da_DK xargs printf "| %'15d  |\n" < numbers_in_one_line.txt

Or in GNU xargs (or other versions that support the -a file / --arg-file=file option):

LC_NUMERIC=da_DK xargs --arg-file numbers_in_one_line.txt printf "| %'15d  |\n"
2
  • 3
    Nice! Is there a way to set this manually? I mean, if none of the locales that have been generated on this machine use ., could the OP set the thousand separator in any other way? Or would they need to locale-gen a new locale?
    – terdon
    Commented Jun 23 at 11:45
  • 3
    @terdon no, there's no other way to set it. It has to be able to read a binary LC_NUMERIC file which you can create a custom one using locale-gen / localedef. I could explain here also how to compile it from scratch, but it seems a bit out of the scope of this question, since it's very rare there are no installed locales that support that.
    – aviro
    Commented Jun 23 at 14:02
9

Another solution is to use numfmt which is included in Debian variants by default

$ WIDTH=20
$ <file.txt tr ' ' '\n' | LC_ALL=el_GR.UTF-8 numfmt --grouping --padding=$WIDTH
       1.941.102.556
       1.750.145.810
           2.604.905
       7.000.793.682
       5.160.065.824
       3.000.350.768
       6.449.300.295
       3.046.118.928
      12.693.055.728
         257.664.864
      13.471.943.769

$ <file.txt tr ' ' '\n' | LC_ALL=el_GR.UTF-8 numfmt --grouping --padding=$WIDTH | sed -e 's/^/| /' -e 's/$/ |/'
|        1.941.102.556 |
|        1.750.145.810 |
|            2.604.905 |
|        7.000.793.682 |
|        5.160.065.824 |
|        3.000.350.768 |
|        6.449.300.295 |
|        3.046.118.928 |
|       12.693.055.728 |
|          257.664.864 |
|       13.471.943.769 |

$ <file.txt tr ' ' '\n' | LC_ALL=el_GR.UTF-8 numfmt --format "| %'${WIDTH}f |"
|        1.941.102.556 |
|        1.750.145.810 |
|            2.604.905 |
|        7.000.793.682 |
|        5.160.065.824 |
|        3.000.350.768 |
|        6.449.300.295 |
|        3.046.118.928 |
|       12.693.055.728 |
|          257.664.864 |
|       13.471.943.769 |

Change LC_ALL to your desired locale, and set WIDTH to the number of characters to do right align. Of course you can also do left alignment if necessary

4

Given this input:

$ cat file
1941102556 1750145810 2604905 7000793682 5160065824 3000350768 6449300295 3046118928 12693055728 257664864 13471943769

there's no need to run tr and/or generate a temp file or do anything else with it.

Using any awk that supports the ' (represented below by \047) qualifier for printf:

$ LC_ALL='da_DK' awk -v RS=' ' '{printf "| %\047*i  |\n", 15, $0}' file
|   1.941.102.556  |
|   1.750.145.810  |
|       2.604.905  |
|   7.000.793.682  |
|   5.160.065.824  |
|   3.000.350.768  |
|   6.449.300.295  |
|   3.046.118.928  |
|  12.693.055.728  |
|     257.664.864  |
|  13.471.943.769  |

or if you don't know how wide your widest number will be, you can let column do the formatting:

$ LC_ALL='da_DK' awk -v RS=' ' '{printf "| %\047i |\n", $0}' file | column -t -R0
|   1.941.102.556  |
|   1.750.145.810  |
|       2.604.905  |
|   7.000.793.682  |
|   5.160.065.824  |
|   3.000.350.768  |
|   6.449.300.295  |
|   3.046.118.928  |
|  12.693.055.728  |
|     257.664.864  |
|  13.471.943.769  |

or if your version of column doesn't support -R0 to right-align all the fields then you could just do it all in awk:

$ LC_ALL='da_DK' awk -v RS=' ' '
    { a[NR]=sprintf("%\047i", $0); wid=length(a[NR]) }
    wid > max { max = wid }
    END { for (i=1; i<=NR; i++) printf "|  %*s  |\n", max, a[i] }
' file
|   1.941.102.556  |
|   1.750.145.810  |
|       2.604.905  |
|   7.000.793.682  |
|   5.160.065.824  |
|   3.000.350.768  |
|   6.449.300.295  |
|   3.046.118.928  |
|  12.693.055.728  |
|     257.664.864  |
|  13.471.943.769  |

See unable-to-print-period-thousands-separators for how to determine which locale on your computer will use . as the thousands separator.

If your awk doesn't support %' to add thousands separators you can always do this or similar for that part:

$0=sprintf("%0*d",999,$0); gsub(/.../,".&"); sub(/^[0.]+/,"")

e.g.:

$ awk -v RS=' ' '{$0=sprintf("%0*d",999,$0); gsub(/.../,".&"); sub(/^[0.]+/,""); print "|", $0, "|"}' file | column -t -R0
|   1.941.102.556  |
|   1.750.145.810  |
|       2.604.905  |
|   7.000.793.682  |
|   5.160.065.824  |
|   3.000.350.768  |
|   6.449.300.295  |
|   3.046.118.928  |
|  12.693.055.728  |
|     257.664.864  |
|  13.471.943.769  |

If in the unlikely event you have numbers of more than 999 digits then change 999 to some higher multiple of 3.

3

I would probably reverse each line, put the dots in, and reverse it back.

$ tr " " "\n" < numbers_in_one_line.txt |
                                    rev |
       sed -E 's/(...)/\1./g; s/\.$//g' |
                                    rev | xargs printf '| %15s  |\n' 
|   1.941.102.556  |
|   1.750.145.810  |
|       2.604.905  |
|   7.000.793.682  |
|   5.160.065.824  |
|   3.000.350.768  |
|   6.449.300.295  |
|   3.046.118.928  |
|  12.693.055.728  |
|     257.664.864  |
|  13.471.943.769  |
1
  • 2
    Or with a single sed without rev: sed -E ':1; s/([0-9])(...)($|\.)/\1.\2/g; t1'
    – aviro
    Commented Jun 23 at 12:52
1

You can print the numbers in thousands format and then change all commas to dots by tr command. Here is one way:

$ cat long_list | tr ' ' '\n' | 
  while read num; do 
    printf "%'d\n" "$num"
  done | tr ',' '.'
1.941.102.556
1.750.145.810
2.604.905
7.000.793.682
5.160.065.824
3.000.350.768
6.449.300.295
3.046.118.928
12.693.055.728
257.664.864
13.471.943.769

where:

$ cat long_list 
1941102556 1750145810 2604905 7000793682 5160065824 3000350768 6449300295 3046118928 12693055728 257664864 13471943769
6
  • 2
    This depends on your locale though. Not all locales use commas.
    – terdon
    Commented Jun 23 at 11:48
  • 2
    and this also doesn't right-align like the OP's example
    – phuclv
    Commented Jun 23 at 16:52
  • Finally, it could be just <long_list tr ' ' '\n' | [...] without the UUoC.
    – kos
    Commented Jun 23 at 19:36
  • @terdon I think they're catering for that with the last tr command. It's a bit ugly, I'm not gonna lie, but it should work, unless a locale would use something other than . or ,? Could that even be?
    – kos
    Commented Jun 23 at 19:42
  • 3
    @kos nah, this only works in locales that use ,. If your locale already uses . it won't work. For example, try LC_ALL=POSIX printf "%'d\n" 10000 | tr ',' '.'. Since POSIX uses no separator, it won't work. And yes, other separators exist. Off the top of my head, French (fr_FR.utf8) uses a space, for example, and there will probably be more.
    – terdon
    Commented Jun 23 at 20:10

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .