Compare and print last column in a file

Viewed 80

I have a file

(n34)); 1
Z(n2)); 1
(n52)); 2
(n35)); 3
(n67)); 3
(n19)); 4
(n68)); 4
(n20)); 5
(n36)); 5
(n53)); 5
(n69)); 5
N(n3)); 5
(n54)); 6
(n70)); 7
N(n4)); 7

I want output such that whenever we have same number after semicolon print that lines in single line with field separator as;.

Output should be

(n34)); 1;Z(n2)); 1
(n52)); 2
(n35)); 3;(n67)); 3
(n19)); 4;(n68)); 4
(n20)); 5;(n36)); 5;(n53)); 5;(n69)); 5;N(n3)); 5
(n54)); 6
(n70)); 7;N(n4)); 7

I tried the code below

awk -F';' 'NR == FNR { count[$2]++;next}

In this I am not getting how to print it on same line if same numbers are present.

6 Answers

1st solution: Could you please try following, written and tested with shown samples in GNU awk and considering that your Input_file is sorted by 2nd column.

awk '
BEGIN{ OFS=";" }
prev!=$2{
  if(val){ print val }
  val=""
}
{
  val=(val?val OFS:"")$0
  prev=$2
}
END{
  if(val){ print val }
}
' Input_file

2nd solution: OR in case your 2nd field is not sorted then try following.

sort -nk2 Input_file | 
awk '
BEGIN{ OFS=";" }
prev!=$2{
  if(val){ print val }
  val=""
}
{
  val=(val?val OFS:"")$0
  prev=$2
}
END{
  if(val){ print val }
}
'

Explanation of awk code:

awk '                      ##Starting awk program from here.
BEGIN{ OFS=";" }           ##Setting output field separator as semi colon here.
prev!=$2{                  ##Checking condition if 2nd field is NOT equal to current 2nd field then do following.
  if(val){ print val }     ##If val is set then print value of val here.
  val=""                   ##Nullifying val here.
}
{
  val=(val?val OFS:"")$0   ##Creating val variable and keep adding values to it with OFS in between their values.
  prev=$2                  ##Setting current 2nd field to prev to be checked in next line.
}
END{                       ##Starting END block for this program from here.
  if(val){ print val }     ##If val is set then print value of val here.
}
' Input_file               ##Mentioning Input_file name here.

Another awk:

$ awk -F\; '{a[$2]=a[$2] (a[$2]==""?"":";") $0}END{for(i in a)print a[i]}' file

Output:

(n34)); 1;Z(n2)); 1
(n52)); 2
(n35)); 3;(n67)); 3
(n19)); 4;(n68)); 4
(n20)); 5;(n36)); 5;(n53)); 5;(n69)); 5;N(n3)); 5
(n54)); 6
(n70)); 7;N(n4)); 7

Explained:

$ awk -F\; '{                           # set delimiter (probably useless)
    a[$2]=a[$2] (a[$2]==""?"":";") $0   # keep appending where $2s match
}
END {                                   # in the end
    for(i in a)                         # output
        print a[i]
}' file

Edit: for(i in a) will produce order that appears random. If you need to order it, you can pipe the output to:

$ awk '...' | sort -t\; -k2n

Perl to the rescue!

perl -ne '($x, $y) = split;
          $h{$y} .= "$x $y;";
          END { print $h{$_} =~ s/;$/\n/r for sort keys %h }
         ' -- file

It splits each line on whitespace, stores the value in a hash table %h keyed by the second column, and when the file has been read, it prints the remembered values, sorting them by the second column. We always store the semicolon at the end, so we need to replace the final one with a new line in the output.

I would harness GNU AWK array for that task following way. Let file.txt content be:

(n34)); 1
Z(n2)); 1
(n52)); 2
(n35)); 3
(n67)); 3
(n19)); 4
(n68)); 4
(n20)); 5
(n36)); 5
(n53)); 5
(n69)); 5
N(n3)); 5
(n54)); 6
(n70)); 7
N(n4)); 7

then:

awk '{data[$2]=data[$2] ";" $0}END{for(i in data){print substr(data[i],2)}}' file.txt

output is:

(n34)); 1;Z(n2)); 1
(n52)); 2
(n35)); 3;(n67)); 3
(n19)); 4;(n68)); 4
(n20)); 5;(n36)); 5;(n53)); 5;(n69)); 5;N(n3)); 5
(n54)); 6
(n70)); 7;N(n4)); 7

Explanation: I exploit facts that GNU AWK arrays are lazy and remember order of insertion (latter is not guaranteed in all AWKs). For every line I concatenate whole line to what is under $2 key in array data using ;. If there is not already value stored it is same as empty string. This lead to ; appearing at begins of every record in data so I print it starting at 2nd character. Keep in mind this solution stores everything in data so it might not work well for huge files.

(tested in gawk 4.2.1)

datamash has a similar function built in:

<infile datamash -W groupby 2 collapse 1

Output:

1   (n34));,Z(n2));
2   (n52));
3   (n35));,(n67));
4   (n19));,(n68));
5   (n20));,(n36));,(n53));,(n69));,N(n3));
6   (n54));
7   (n70));,N(n4));

This might work for you (GNU sed):

sed -E ':a;N;s/( \S+)\n(.*\1)$/\1;\2/;ta;P;D' file

Append the following line to the current line.

If both lines end in the same number (string), delete the intervening newline and repeat.

Print/Delete the first line in the pattern space and repeat.

Related