Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Awk combining multiple lines conditionally

Tags:

grep

sed

awk

I want to combine values from multiple lines of varying length into one line if they match IDs.

Input example is:

ID:  Value:
a-1  49
a-2  75
b-1  120
b-2  150
b-3  211
c-1  289
d-1  301
d-2  322

Desired output example is:

ID:  Value:
a 49,75
b 120,150,211
c 289
d 301,322

How would I write an awk expression (or sed or grep or something) to check if the IDs matched, and then to print all those values on to one line? I can of course just print them into different columns and combine them later, so really the problem is just conditionally printing if the IDs match and if not starting a new line.

like image 425
Micah Manary Avatar asked Aug 15 '11 17:08

Micah Manary


3 Answers

In sed, assuming the IDs are clustered together:

sed -n -e '1p;2{s/-.* / /;h};3,${H;x;s/\(.*\) \(.*\)\n\1-.* /\1 \2,/;/\n/{P;s/.*\n//;s/-.* / /};x};${x;p}' your_input_file

Bellow is a commented sed script file that can be run with sed -n -f script your_input_file:

# Print the 1st line as is.
1p
# For the 2nd line, remove what is after - in the ID and save in the hold space.
2{s/-.* / /;h}
# For all the other lines...
3,${
# Append the line to the hold space and place it in the pattern space.
H;x
# Substitute identical ids by a ,.
s/\(.*\) \(.*\)\n\1-.* /\1 \2,/
# If we have a \n left in the pattern space, it is a new ID, so print the old and prepare the next.
/\n/{P;s/.*\n//;s/-.* / /}
# Save what remains in hold space for next line.
x}
# For the last line, print what is left in the hold space.
${x;p}
like image 116
jfg956 Avatar answered Nov 10 '22 21:11

jfg956


Given your input:

awk '
  NR == 1 {print; next}
  {
    split($1,a,/-/)
    sep = values[a[1]] == "" ? "" : ","
    values[a[1]] = values[a[1]] sep $2
  }
  END {for (key in values) print key, values[key]}
'

produces

ID:  Value:
a 49,75
b 120,150,211
c 289
d 301,322

A language that supports "hash-of-lists" would be handy too. Here's a Perl version

perl -lne '
  if ($. == 1) {print; next}
  if (/^(.+?)-\S+\s+(.*)/) {
    push @{$values{$1}}, $2;
  }
  END {
    $, = " ";
    foreach $key (keys %values) {
    print $key, join(",", @{$values{$key}});
    }
  }
'
like image 43
glenn jackman Avatar answered Nov 10 '22 21:11

glenn jackman


Given your inputs in input.txt file:

awk '{split($1, a, "-"); hsh[a[1]]=hsh[a[1]]$2","}END{for (i in hsh){print i" "hsh[i]}}' input.txt | sed 's/,$//'

OUTPUT

a 49,75
b 120,150,211
c 289
d 301,322
like image 1
Newbiee Avatar answered Nov 10 '22 23:11

Newbiee