merge first line in textblock with every next line in the block

Viewed 59

I've been trying to figure out how I can merge the first line in a text block with every other line the block using awk or sed. Each block consists of 2 or more lines, for example:

AA  
1  

BB  
2  
3  
4  

CC  
5  
6  
7  
8  

How can I merge this into the output below:

AA 1  
BB 2  
BB 3  
BB 4  
CC 5  
CC 6  
CC 7  
CC 8  

Any help is very much appreciated.

4 Answers

Assuming POSIX-conformant awk and shell*, this will work…

awk '/^[[:space:]]*$/ {a=""} {if (a=="") {a=$0} else print a,$0}'

Explanation of how it works (UPDATE):

There are two actions. The first reacts to an empty line (except for optional whitespace) because it is preceded by this pattern: /^[[:space:]]*$/. The slashes, of course, simply delimit the pattern. The caret (^) matches the beginning of the line and the dollar sign ($) matches the end. This forces matching of the whole line (otherwise, this pattern would match any line at all, because every possible input line contains zero or more whitespace characters). [[:space:]]* matches zero or more (*) characters from the predefined character class of whitespace characters. The action for this pattern resets a variable (meaninglessly named a) to the empty string. This is used to detect the beginning of the next text block.

The second action has no pattern and is therefore executed once for every input line. It checks whether a prefix (again, variable a) has already been set, and:

  • If it hasn't, it then sets the prefix to the contents of the current line ($0). (Note that this uselessly executes on an empty line also.)
  • If it has, it prints the prefix followed by the contents of the current line. (awk automatically inserts a space due to the use of comma.)

Ironically, I discovered a bug while writing this update: A line containing only whitespace will be accepted as a prefix. This is because the check if (a=="") fails in this case. There are multiple ways to solve this. I don't know enough about awk myself to recommend a particular approach, but this seems to do it:

awk '/^[[:space:]]*$/ {s=1;a=""} {if (s==0 && a=="") a=$0; else print a,$0; s=0}'

*) I initially (and lazily) wrote "GNU awk and bash" just because that's what I used for testing. Ed Morton pointed out that my solution doesn't use any non-portable features, however.

$ awk -v RS= '{for (i=2;i<=NF;i++) print $1, $i}' file
AA 1
BB 2
BB 3
BB 4
CC 5
CC 6
CC 7
CC 8

Another awk!

$ cat pckh.txt
AA
1

BB
2
3
4

CC
5
6
7
8

$ awk ' NF && !/[A-Z]/ { print prev,$0 } /[A-Z]/ {  prev=$0 }  ' pckh.txt
AA 1
BB 2
BB 3
BB 4
CC 5
CC 6
CC 7
CC 8

$

GNU sed:

$ sed -rn '/^\s*$/{s/.*//;h;be};/./{x;/./!be;x;G;s/(.*)\n(.*)/\2 \1/p};:e' file
AA 1
BB 2
BB 3
BB 4
CC 5
CC 6
CC 7
CC 8

Treats spaces/tabs only lines as empty lines too.
If no such lines, can be shorter:

sed -rn '/^$/{h;be};/./{x;/./!be;x;G;s/(.*)\n(.*)/\2 \1/p};:e'
Related