3

Let's say my input file looks like:

ID1
1    5
6    8
ID2
1    4
5    7

I'm trying to formulate a loop that can combine these actions:

  • If the line begins with a letter: keep the '\ n' after the field

  • If the line begins with a number: replace every '\ t' and '\ n' with a comma ( sed 's / \ t /, / g; s / \ n /, / g ' ) EXCEPT if the next line begins with a letter ( sed 's / \ t /, / g' )

Expected output:

ID1     
1, 5, 6, 8
ID2     
1, 4, 5, 7

4 Answers 4

4
sed '/^[0-9]/{:a;s/[\t\n ]\+/,/g;N;/\n[A-Z]/!ba;}'

will do the stuff.

Explanation:

/^[0-9]/ will match only to lines started with number and apply command group to it

{} command group to apply

{:a;s/[\t\n ]\+/,/g;N;/\n[A-Z]/!ba;} will read in cycle line by line and replace all spaces, tabs and newlines to comma until line started with letter.

1
  • I'll accept this answer because you proposed a sed method, but @fedorqui's answer using awk works perfectly as well!
    – dovah
    Jul 28, 2014 at 18:13
1

You should be able to achieve that as follows: if the current line starts with a digit, append the next line into pattern space and then replace sequences of spaces, tabs and newlines with comma-space:

$ sed '/^[0-9]/{N;s/[ \t\n]\+/, /g}' file
ID1
1, 5, 6, 8
ID2
1, 4, 5, 7
2
  • 1
    Note that it will not work if OP has 3 or more lines with numbers. If it's just like in example, then it's fine.
    – rush
    Jul 28, 2014 at 11:40
  • @rush - yeah, mine too, I think. I should probably fix that.
    – mikeserv
    Jul 28, 2014 at 18:52
1

I would go for awk, that provides more control and generalizes the problem:

awk 'BEGIN{FS="\t"; OFS=","}
      /^[^A-Z]/ {for (i=1; i<=NF; i++) {if (!a) a=$i; else a=a OFS $i} next}
      {if (a) print a; a=""; print}
     END{print a}'

Explanation

  • BEGIN{FS="\t"; OFS=","} set input field separator as tab and output field separator as comma.
  • /^[^A-Z]/ {for (i=1; i<=NF; i++) {if (!a) a=$i; else a=a OFS $i} next} on lines not starting with capital letter, store the values in a variable a.
  • {if (a) print a; a=""; print} in the rest of the cases (that is, lines starting with capital letter), print the stored value together with current line.
  • END{print a} after processing the whole file, print the last stored variable with the values from the last block.

See output:

$ awk 'BEGIN{FS="\t"; OFS=","}/^[^A-Z]/ {for (i=1; i<=NF; i++) {if (!a) a=$i; else a=a OFS $i} next} {if (a) print a; a=""; print} END{print a}' file
ID1
1,5,6,8
ID2
1,4,5,7

In sed, remember you can always use the -e option to combine commands.

1
tr -s '\t\nI' '  \n' <<\DATA |\
sed 's/^/I/;s/  */\n/;s/  *[0-9]/,&/g'
ID1
1    5
6    8
ID2
1    4
5    7
DATA

OUTPUT

ID1
1, 5, 6, 8
ID2
1, 4, 5, 7    
  • First tr translates all tabs and newlines into spaces and all capital Is into newlines. It also squeezes repeats. That makes it really easy because at this point it passes to sed input that looks like:

    ^D [num] [num] [num] [num] [num] ... [num] $

  • Next sed puts the I back, substitutes a newline for the first space on the line, cleans up any trailing spaces, then...

  • Sticks a comma in front of every space remaining on the line and it's done.

You must log in to answer this question.

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