Reshaping text

It’s not often that I run into a new (to me) Unix command. But when I had a problem yesterday, I came across a simple solution using the rs command, which I’d never heard of before. Maybe you’ll find it useful, too.

As I mentioned in a post last year, I often have deal with lists of alphanumeric strings. Usually, these are serial numbers, but the lists I was dealing with yesterday were lists of apartment numbers in a high-rise building. After various manipulations and comparisons, I had two lists that I wanted to send to a client in an email so we could discuss them. I could have put the lists into a spreadsheet, but that would mean the client would have to open the email and then open the attachment. If you knew the level of computer literacy my clients have, you would understand why I wanted to avoid that and just have the lists in the body of the email.

The problem was that the lists were long. One had 84 items and the other had 30. Pasting them into the message in the form I had them—one item per line—would take up too much space. Pasting them in as a comma-separated series—which I could convert them to with a simple search-and-replace—would be very hard to read. What I wanted was nicely formatted set of rows and columns that would be easy to read without taking up too much space. But I couldn’t think of a quick, automated way to put the list into that form.1

After some Googling, I came across this Ask Ubuntu question, which led me to rs, an old BSD command that comes installed on macOS. The name stands for “reshape,” and it will take any row/column set of data and rearrange it into a different number of rows and columns. Because my lists were a special case—just one column each—they fit the input requirements.

With my list of 84 items on the clipboard, I tried the simplest form of the command,

pbpaste | rs 14

and got this output:

203   204   207   215   302   306
1502  1507  1509  1510  1512  1515
1610  1700  1701  1704  1705  1706
1710  1801  1803  1806  1809  1815
1903  1905  2000  2001  2002  2004
2009  2101  2102  2103  2105  2106
2108  2112  2201  2203  2208  2209
2302  2306  2307  2309  2406  2407
2503  2504  2506  2509  2515  2606
3003  3007  3008  3009  3100  3201
3202  3205  3208  3300  3301  3415
3500  3505  3506  3600  3606  3700
3706  3715  3807  3815  3900  3907
4009  4111  4115  4203  4306  4308

The first argument to rs is the number of rows in the reshaped output. Since 84 is the product of 6 and 14, I got 14 rows of 6 items each. This was fine, but I wanted a little more room between the columns. In rs parlance, the intercolumn space is called the gutter, and you set the size of the gutter with the -g option. So

pbpaste | rs -g5 14

gave me

203      204      207      215      302      306
1502     1507     1509     1510     1512     1515
1610     1700     1701     1704     1705     1706
1710     1801     1803     1806     1809     1815
1903     1905     2000     2001     2002     2004
2009     2101     2102     2103     2105     2106
2108     2112     2201     2203     2208     2209
2302     2306     2307     2309     2406     2407
2503     2504     2506     2509     2515     2606
3003     3007     3008     3009     3100     3201
3202     3205     3208     3300     3301     3415
3500     3505     3506     3600     3606     3700
3706     3715     3807     3815     3900     3907
4009     4111     4115     4203     4306     4308

Note that the number used for the gutter size has to come immediately after the -g. You can’t leave a space between the option and its argument as you can with many other commands.

Again, this was nice, but I thought it would be better if the list were laid out column-by-column instead of row-by-row. That called for the -t (transpose) option,

pbpaste | rs -g5 -t 14

which gave me

203      1701     2002     2302     3008     3606
204      1704     2004     2306     3009     3700
207      1705     2009     2307     3100     3706
215      1706     2101     2309     3201     3715
302      1710     2102     2406     3202     3807
306      1801     2103     2407     3205     3815
1502     1803     2105     2503     3208     3900
1507     1806     2106     2504     3300     3907
1509     1809     2108     2506     3301     4009
1510     1815     2112     2509     3415     4111
1512     1903     2201     2515     3500     4115
1515     1905     2203     2606     3505     4203
1610     2000     2208     3003     3506     4306
1700     2001     2209     3007     3600     4308

Finally, I wanted the three-digit numbers properly aligned. You right-justify the items within their columns using the -j option,

pbpaste | rs -g5 -tj 14

which prints out

 203     1701     2002     2302     3008     3606
 204     1704     2004     2306     3009     3700
 207     1705     2009     2307     3100     3706
 215     1706     2101     2309     3201     3715
 302     1710     2102     2406     3202     3807
 306     1801     2103     2407     3205     3815
1502     1803     2105     2503     3208     3900
1507     1806     2106     2504     3300     3907
1509     1809     2108     2506     3301     4009
1510     1815     2112     2509     3415     4111
1512     1903     2201     2515     3500     4115
1515     1905     2203     2606     3505     4203
1610     2000     2208     3003     3506     4306
1700     2001     2209     3007     3600     4308

I did a similar thing with the list of 30 and then sent off a nice, compact email.

I should mention that rs is reasonably smart about leaving blanks at the ends of rows or columns. If there were 79 items in my list instead of 84,

pbpaste | rs -g5 -tj 14

would give

 203     1701     2002     2302     3008     3606
 204     1704     2004     2306     3009     3700
 207     1705     2009     2307     3100     3706
 215     1706     2101     2309     3201     3715
 302     1710     2102     2406     3202     3807
 306     1801     2103     2407     3205     3815
1502     1803     2105     2503     3208     3900
1507     1806     2106     2504     3300     3907
1509     1809     2108     2506     3301     4009
1510     1815     2112     2509     3415
1512     1903     2201     2515     3500
1515     1905     2203     2606     3505
1610     2000     2208     3003     3506

with the empty “cells” at the end of the last column. Without the -t option,

pbpaste | rs -g5 -j 14

gives

 203      204      207      215      302      306
1502     1507     1509     1510     1512     1515
1610     1700     1701     1704     1705     1706
1710     1801     1803     1806     1809     1815
1903     1905     2000     2001     2002     2004
2009     2101     2102     2103     2105     2106
2108     2112     2201     2203     2208     2209
2302     2306     2307     2309     2406     2407
2503     2504     2506     2509     2515     2606
3003     3007     3008     3009     3100     3201
3202     3205     3208     3300     3301     3415
3500     3505     3506     3600     3606     3700
3706     3715     3807     3815     3900     3907
4009

with the empty cells at the end of the last row.

There are lots of options for dealing with different types of input and generating different types of output. I don’t see any value in trying to remember them; I can look them up in the man page as needed. The most important thing is to know that rs exists.


  1. Yes, I could use column selection in BBEdit to edit my lists into rows and columns by hand, but if you think I’d do that, you haven’t been reading this blog very long.