The Linux Rain Linux General/Gaming News, Reviews and Tutorials

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.



About the Author

Bob Mesibov is Tasmanian, retired and a keen Linux tinkerer.

Tags: linux tutorial scripts text lines interleaving paste awk nl tail
blog comments powered by Disqus