2

I'm looking for a quick way to add rows of data like

3333456332....
3334456332....
3343446332....
3331355332....

(but longer) and add it up per digit to this result:

12 12 13 11 15 19 22 12 12 4

Is this possible with a one-liner? Or more complex?

Tom Fenech
  • 72,334
  • 12
  • 107
  • 141
Valen
  • 21
  • 1
  • 2
    I think you mean columns! – Mark Setchell May 22 '14 at 12:02
  • 1
    possible duplicate of http://stackoverflow.com/questions/14956264/how-to-sum-each-column-in-a-file-using-bash – PradyJord May 22 '14 at 12:05
  • Note that "Linux command line" is a misnomer here. Linux is a kernel and doesn't have a "command line" (except the one line passed by the bootloader). You probably meant to ask how to do this in a Unix (or POSIX) shell environment. And if you're using GNU Bash, GNU awk, GNU sed, GNU coreutils, etc. (e.g. if you're on Debian, Ubuntu, or any other of the dozens of GNU/Linux distros), then this would be a case where "GNU" alone is technically more correct than "Linux" alone, in case GNU/Linux is too wordy. :-) – TaylanKammer May 22 '14 at 12:35
  • @TaylanUB I've edited the title and tags, hopefully it is a little clearer now. – Tom Fenech May 22 '14 at 13:11

3 Answers3

3

How about a nice bit of awk:

awk -v FS="" '{for(i=1; i<=NF; ++i) a[i]+=$i} END {for (i=1; i<=NF; ++i) printf "%d ", a[i] }' file

This creates an array a that accumulates all of the sums for each digit. At the END of the file, go through the array and print all of the sums.

The FS variable is important here, as it means that each digit on the line is treated as a separate field.

Output:

12 12 13 11 15 19 23 12 12 8

update

The above code won't print a newline after the sums, which may be what you want. As suggested in the comments by fedorqui, one way round that would be to do:

awk -v FS="" '{for (i=1; i<=NF; ++i) a[i]+=$i} END {for (i=1; i<=NF; ++i) printf "%d%s", a[i], (i<NF?" ":"\n") }' file

Which prints a space between all of the values, then a newline at the end.

Alternatively, if you like concatenating strings, you could do:

awk -v FS="" '{for(i=1; i<=NF; ++i) a[i]+=$i} END{for (i=1; i<=NF; ++i) s=s a[i] " "; print s}' file

Which first builds up a string s and then prints it. print will add the newline by default for you.


Inspired by choroba's answer, here's an alternative version using Perl:

perl -F'' -wane 'pop @F; $i=0; $s[$i++]+=$_ for @F }{ print "@s\n"' file

as in awk, set the field delimiter to ''. Use the "autosplit" switch -a which creates the array @F, each element containing one digit.

The last element of @F is the newline "\n", so pop removes it. The array @s keeps all of the sums in. The -n switch means that the whole command is surrounded in while (<>) { ... }, so using the }{ "eskimo operator" effectively creates an END block. By setting the special variable $" to a space $" is a space by default, so the array can be printed in one go, with each element separated by a space.

Community
  • 1
  • 1
Tom Fenech
  • 72,334
  • 12
  • 107
  • 141
  • Good one. In the `END{}` you can make use of `printf "%d%s", a[i], (i==NF?"\n":" ")` to print a new line at the end and a space in other cases. – fedorqui May 22 '14 at 12:07
  • @fedorqui thanks, I've added your suggestion (and another of my own). – Tom Fenech May 22 '14 at 12:24
2

Here's my version

grep -o . | pr -4 -t | sed -r 's/\s+/+/g' | bc | xargs
Vytenis Bivainis
  • 2,308
  • 21
  • 28
  • I guess you meant `grep -o . file`? Unfortunately this only works for files with 4 lines. I guess you could change it to `pr -$(wc -l < file) -t` in the middle! – Tom Fenech May 22 '14 at 16:51
  • Right, I didn't want to clutter such an elegant solution :) I assume data comes from stdin. – Vytenis Bivainis May 22 '14 at 17:28
  • It certainly is an interesting way of solving the problem. Perhaps it would benefit from some explanation of each step. – Tom Fenech May 22 '14 at 17:32
  • There are some limitations of course and you may need additional pr parameters depending on the size of a matrix. But here's how it works: with grep -o we transform matrix to single dimensional array,then back to transposed matrix with pr, convert each row "a b" to "a+b" and calculate sum of each row with bc. We now have sums one per row and we transpose it to one row with xargs (with no parameters it reads parameters from stdin and executes echo arg1 arg2 ...). – Vytenis Bivainis May 22 '14 at 17:51
0

Perl solution:

perl -ne 'chomp; 1 == $. ? @s : @F = split //; $_ += shift @F for @s  }{ print "@s\n"'
choroba
  • 231,213
  • 25
  • 204
  • 289
  • Nice answer (not that I understand it completely!), although there is an extra `0` at the end of the output. Using the `-w` shows some warnings, which I think are related. I added a Perl version to my answer which you might like :) – Tom Fenech May 22 '14 at 12:55
  • @TomFenech: You can prepend `chomp;` to the perl code (updated) to avoid the problems. – choroba May 22 '14 at 14:34
  • Nice. Looking at it again, I think I get it now. The first bit in particular is tricky! You can get rid of the warnings using: `'chomp; @F = split //; if (1 == $.) { @s = @F; next } $_ += shift @F for @s }{ print "@s\n"' file`, as this avoids `shift`ing `@F` when it is empty. – Tom Fenech May 22 '14 at 15:31
  • @TomFenech: Or simply `perl -wne 'chomp; 1 == $. ? @s : @F = split //; next unless @F; $_ += shift @F for @s }{ print "@s\n"'`, but who cares about warnings in one-liners... – choroba May 22 '14 at 16:59