Reformatting FreeBSD cal(1) with awk(1)

=====

cal(1) in FreeBSD can't print month starting with Monday. ncal(1) can do it
but it prints weeks vertically which is quiet uncomfortable. So let's try
a simple usage of awk(1) to modify cal(1).

First of all, we need to remove unprinted characters from cal output.
To see them do:

$ cal | cat -e
     June 2021        $
Su Mo Tu We Th Fr Sa  $
       1  2  3  4  5  $
 6  7  8 _^H _^H9 10 11 12  $
13 14 15 16 17 18 19  $
20 21 22 23 24 25 26  $
27 28 29 30           $

Underlining character with _^H is the way FreeBSD highlights console symbols,
but it don't work in plaintext or in pipe. Besides it's awkward to work with
extra field in a current week, so remove it with sed(1). But we can't just
print '^H' because it isn't '^H' for real. ^H means raw character code here.
To insert this raw character into sed match field we need to press Ctrl+V and
then press needed key (Backspace here):

$ cal | cat -e | sed 's/_^H//g'
     June 2021        $
Su Mo Tu We Th Fr Sa  $
       1  2  3  4  5  $
 6  7  8  9 10 11 12  $
13 14 15 16 17 18 19  $
20 21 22 23 24 25 26  $
27 28 29 30           $

Everything is good now, so exclude 'cat -e' from pipe further.
First line of output should not be modified, but we need to reprint second line
with a Sunday at the and of it. Start using awk here and place code to mcal.sh:

$ cat mcal.sh
cal | sed 's/_^H//g' | awk '
	NR == 1 { print };
	NR == 2 { print "Mo Tu We Th Fr Sa Su " }
'

NR and NF are variables generated by awk. They mean number of rows and number of
fields in a current row respectively.

The main thought of reformatting cal is to move Sunday column to the end of week
and next up it a one day higher. The easiest way to do it in a one pass is to
print day of Sunday, then print '\n', and then print the rest of days:

{
	printf "%2s \n%2s %2s %2s %2s %2s %2s ", $1, $2, $3, $4, $5, $6, $7;
}
or
{
	printf "%2s \n", $1;
	for (i = 2; i <= NF; i++)
		printf "%2s ", $i;
}

While fourth and fifth lines always contain seven fields, third line can contain
less. In case first day of month falls on a Sunday (7 days in first week), this
day must be printed in the end of third line (2 + 6 * 3 = 20 characters in a
string):

NR == 3 && NF == 7 {
	printf "%20s \n", $1;
	for (i = 2; i <= NF; i++)
		printf "%2s ", $i;
}

In case first week of month contains less then 7 days, we need to just remove
three initial spaces in it's line and reprint it without final newline symbol:

NR == 3 && NF < 7 {
	sub(/^.../, "");
	sub(/ $/, "");
	printf "%s", $0;
}

Function sub(regexp, substitute, string) replaces regexp with substitute in a
string (whole current string by default). Second usage of sub() here removes
last space in a string printed by cal.

The rest of strings of cal output can be processed as shown earlier.
Put it all together, combine some actions and echo final empty string:

$ cat mcal.sh
cal | sed 's/_^H//g' | awk '
	NR == 1 { print };
	NR == 2 { print "Mo Tu We Th Fr Sa Su" }
	NR == 3 && NF < 7 {
		sub(/^.../, "");
		sub(/ $/, "");
		printf "%s", $0;
	}
	NR == 3 && NF == 7 {
		printf "%20s \n", $1;
	}
	NR > 3 {
		printf "%2s \n", $1;
	}
	NR > 3 || (NR == 3 && NF == 7) {
		for (i = 2; i <= NF; i++)
			printf "%2s ", $i;
	}
'
echo ''

$ sh mcal.sh
     June 2021        
Mo Tu We Th Fr Sa Su
    1  2  3  4  5  6
 7  8  9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30   
  
$

Fine, but it lacks highlighting of today because it was removed earlier by sed.
To indicate it again I can save today's number in a variable and use it later
in sed substitute to put the day in parenthesis:

$ cat mcal.sh
day=$(date | awk '{ print $3 }');
cal | sed 's/_^H//g' | awk '
	NR == 1 { print };
	NR == 2 { print "Mo Tu We Th Fr Sa Su" }
	NR == 3 && NF < 7 {
		sub(/^.../, "");
		sub(/ $/, "");
		printf "%s", $0;
	}
	NR == 3 && NF == 7 {
		printf "%20s \n", $1;
	}
	NR > 3 {
		printf "%2s \n", $1;
	}
	NR > 3 || (NR == 3 && NF == 7) {
		for (i = 2; i <= NF; i++)
			printf "%2s ", $i;
	}
' | sed "s/^/ /; s/ $day /($day)/";
echo ''

$ sh mcal.sh
      June 2021        
 Mo Tu We Th Fr Sa Su
     1  2  3  4  5  6
  7  8 (9)10 11 12 13
 14 15 16 17 18 19 20
 21 22 23 24 25 26 27
 28 29 30   
  
$

I've added additional space to beginning of lines to prevent offset of line due
to putting Monday in parenthesis.