How to manage alternating lines of text on the command line
By Bob Mesibov, published 06/10/2015 in Tutorials
In this article I've pulled together some command-line tips for alternating lines of text. Questions about alternating lines turn up regularly on online help forums, and they sometimes get complicated answers. Here I've tried to keep things simple.
Interleaving lines
For demonstration purposes I'll use the text files demo1, demo2 and demo3, which have just 3 lines each:
By 'interleaving lines', I mean combining the files so that the first line from demo1 is followed by the first line from demo2, the second demo1 line by the second demo2 line, and so on. The easiest way to do this is with the paste command. Pasting would normally join the lines side by side, using the tab character as separator:
$ paste demo1 demo2
Aaaaah Mmm
Bbbbbi Nnn
Cccccj Ooo
If I add the -d (delimiter) option and ask paste to separate the lines with a newline character, I get interleaving:
$ paste -d '\n' demo1 demo2
Aaaaah
Mmm
Bbbbbi
Nnn
Cccccj
Ooo
Interleaving can happen with any number of files fed to paste as arguments. Here I've interleaved demo3 after demo1 and demo2:
$ paste -d '\n' demo1 demo2 demo3
Aaaaah
Mmm
Vvvv
Bbbbbi
Nnn
Wwww
Cccccj
Ooo
Xxxx
Alternating lines
You can also do 'interleaving' with a single file. For example, suppose I want to number each line in demo1, but put that number on a line above the line to be numbered. A simple way to do this is with the nl command. To be fancy, I'll use the 'nrz' and '-w3' options for nl, which give me right-justified numbers with 3 leading zeroes, separated from the lines to be numbered by a tab:
$ nl -nrz -w 3 demo1
001 Aaaaah
002 Bbbbbi
003 Cccccj
If I tell nl to separate numbers from lines with a newline character (-s $'\n'), I get an alternating result:
$ nl -nrz -w 3 -s $'\n' demo1
001
Aaaaah
002
Bbbbbi
003
Cccccj
For more elaborate numbering, I recommend the seq command and its '-f' (format) option, which uses printf-style formatting. Here I'll generate 3 numbering lines that begin with 'Record No. ' and finish with numbers padded out to 3 spaces with leading zeroes:
$ seq -f "Record No. %03g" 3
Record No. 001
Record No. 002
Record No. 003
Now I'll interleave these strings with demo1 using paste, but I'll send the output from seq as standard input ('-') to paste, rather than create a new file with just the 'Record No. ' strings:
$ seq -f "Record No. %03g" 3 | paste -d $'\n' - demo1
Record No. 001
Aaaaah
Record No. 002
Bbbbbi
Record No. 003
Cccccj
Digression on numbering
In an earlier Linux Rain article I compared numbering with seq and with BASH brace expansion, and pointed out that each has its advantages and disadvantages. In the case above, seq is a bit better. You can use brace expansion to generate numbering strings
$ echo "Record No. "{001..3}
Record No. 001 Record No. 002 Record No. 003
but to get those into a list requires a little bit more work:
$ printf "%s %s %s\n" $(echo "Record No. "{001..3})
Record No. 001
Record No. 002
Record No. 003
or
$ tail -n +2 <(echo -e "\nRecord No. "{001..3})
Record No. 001
Record No. 002
Record No. 003
Back to alternation
The line to be alternated can be derived from the existing line. For example, this while loop extracts the last character from each of the lines in demo1:
$ while read line; do echo ${line:(-1)};done < demo1
h
i
j
The loop can also print the existing line after each line extracted. It's a direct way of generating alternating lines:
$ while read line; do echo ${line:(-1)}; echo "$line";done < demo1
h
Aaaaah
i
Bbbbbi
j
Cccccj
I prefer AWK for jobs like this. Here I set the field separator 'FS' to be a null character, so that every character in the line is a separate field. Then I get AWK to print that last field followed by the full line:
$ awk -v FS="" '{print $NF"\n"$0}' demo1
h
Aaaaah
i
Bbbbbi
j
Cccccj
or more elaborately:
$ awk -v FS="" '{print "Last character in next line is "$NF"\n"$0}' demo1
Last character in next line is h
Aaaaah
Last character in next line is i
Bbbbbi
Last character in next line is j
Cccccj
Making a gap
The simplest way to insert a blank line between alternating-line sets is with paste:
$ paste -d '\n' demo1 demo2 /dev/null
Aaaaah
Mmm
Bbbbbi
Nnn
Cccccj
Ooo
This command uses /dev/null, which is a lot like the Buddhist idea of the Void. It's pure Nothingness. If you send something to /dev/null, that something disappears. If you look into /dev/null you see nothing at all. In the command above, paste is interleaving a line from demo1, a line from demo2 and a line from /dev/null. That last line is - nothing.
If you'd rather not use something so spooky, both AWK and sed have ways to insert a blank line after (in this particular case) every second line. For convenience I'll demonstrate with the already-interleaved file inter2:
$ cat inter2
Aaaaah
Mmm
Bbbbbi
Nnn
Cccccj
Ooo
Each of the folowing commands gives the result shown for the last one:
$ sed n\;G inter2
$ sed '0~2 a\\' inter2
$ awk '!(NR%2) {$0=$0"\n"} 1' inter2
$ awk '{ORS=NR%2?"\n":"\n\n"} 1' inter2
Aaaaah
Mmm
Bbbbbi
Nnn
Cccccj
Ooo
Both the AWK commands use the expression 'NR%2', which is the numerical remainder after dividing the record (=line) number by 2. As AWK sees it, if 'NR%2' is a number other than zero, then the expression is 'true'. If the record number is evenly divisible by 2, the remainder is zero and 'NR%2' is 'false'.
The '1' at the end of each AWK command just tells AWK to print the whole record, '$0'. In the first AWK command, if 'NR%2' is not true ('!' means 'not'), the whole record $0 is replaced by the whole record plus a newline character. This happens at lines 2, 4 and 6.
The second AWK command also uses an if/else test. AWK's default input record separator is a newline, but you can also specify the output record separator with the variable 'ORS'. Here the command is: if the record number has a remainder when divisible by 2 (lines 1, 3 and 5), use a newline as the ORS, otherwise (lines 2, 4 and 6) use 2 newlines (doublespace).
Selecting lines
From a file with regularly alternating lines, you can extract a given sort of line (first, second, third, etc) most easily with AWK, using the '%' operator described above. Here I'll extract lines 1, 3 and 5 from inter2 by asking AWK to print only those lines for which 'NR%2' is 'true'. Note that the default action for AWK is 'print':
$ awk 'NR%2' inter2
Aaaaah
Bbbbbi
Cccccj
To get the even-numbered lines, we can either negate the test or add a 1 to the line number:
$ awk '!(NR%2)' inter2
Mmm
Nnn
Ooo
$ awk '(NR+1)%2' inter2
Mmm
Nnn
Ooo
And here's AWK selecting lines from the interleaved file inter3:
$ cat inter3
Aaaaah
Mmm
Vvvv
Bbbbbi
Nnn
Wwww
Cccccj
Ooo
Xxxx
$ awk 'NR%3==1' inter3
Aaaaah
Bbbbbi
Cccccj
$ awk 'NR%3==2' inter3
Mmm
Nnn
Ooo
$ awk 'NR%3==0' inter3
Vvvv
Wwww
Xxxx
$ awk '!(NR%3)' inter3
Vvvv
Wwww
Xxxx
Collapsing alternating lines
By 'collapsing' a file with regularly alternating lines I mean printing on one line what was previously on 2 or more lines. Once again, a good tool for the job is paste, used as shown below. Here paste is working on inter2 and inter3, first with its default separator (tab) and then with a comma as separator:
$ paste - - < inter2
Aaaaah Mmm
Bbbbbi Nnn
Cccccj Ooo
$ paste -d, - - < inter2
Aaaaah,Mmm
Bbbbbi,Nnn
Cccccj,Ooo
$ paste - - - < inter3
Aaaaah Mmm Vvvv
Bbbbbi Nnn Wwww
Cccccj Ooo Xxxx
$ paste -d, - - - < inter3
Aaaaah,Mmm,Vvvv
Bbbbbi,Nnn,Wwww
Cccccj,Ooo,Xxxx
Swapping alternating lines
The final tip shows one of the ways to swap every 2 lines in an alternating-lines file (even number of lines), using paste and the 'selecting lines' tricks. The file I'll use is inter2:
$ cat inter2
Aaaaah
Mmm
Bbbbbi
Nnn
Cccccj
Ooo
$ paste -d'\n' <(awk '!(NR%2)' inter2) <(awk 'NR%2' inter2)
Mmm
Aaaaah
Nnn
Bbbbbi
Ooo
Cccccj
In this case there's a simpler AWK alternative which uses the 'getline' function:
$ awk '{getline i; print i} 1' inter2
Mmm
Aaaaah
Nnn
Bbbbbi
Ooo
Cccccj
Here AWK 'gets' the next line after the first and saves the line in the variable 'i'. It prints the variable, then prints the first line (following its '1' instruction), before moving to line 3, and so on.