Cal Command – Output Current Day

calcommand linegrepoutput

I'm asked to output the current day using the cal command.

So far, I discovered that before the current date there is a _ symbol. I decided to use grep here: cal | grep '\b_*', but it outputs the whole week. I've tried several variants, but it didn't work out.

Actually, there is also a case, when current day has only one digit, so it seems I have to usetr -d ' ' here.

I have no idea how to combine all these commands together.

Best Answer

When the output of the cal command is not a terminal, it applies poor man's underlining to the day number for today, which consists of putting an underscore and a backspace character before each character to underline. You can see that by displaying the characters visually (^H means control-H, which is the backspace character):

cal | cat -A
cal | cat -vet

or by looking at a hex dump:

cal | hd
cal | od -t x1

So what you need is to detect the underlined characters and output them.

With GNU grep, there's an easy way to print all the matches of a regular expression: use the -o option. An underline character is matched by the extended regular expression _^H. where ^H is a literal backspace character, not the two characters ^ and H, and . is the character to print. Instead of typing the backspace character, you can rely on the fact that this is the only way cal uses underscores in its output. So it's enough to detect the underscores and leave the backspaces as unmatched characters.

cal | grep -o '_..'

We're close, but the output contains the underscore-backslash sequence, and the digits are on separate lines. You can strip away all non-digit characters (and add back a trailing newline):

cal | grep -o '_..' | tr -d 0-9; echo

Alternatively, you can repeat the pattern _.. to match multiple underlined digits. This leaves the underlining in the output, you can use tr or sed to strip it off.

cal | grep -E -o '(_..)*'
cal | grep -E -o '(_..)*' | tr -d '\b_'
cal | grep -E -o '(_..)*' | sed 's/_.//g'

You can do this with sed, but it isn't completely straightforward. Sed offers an easy way to print only matching lines (use the -n option to only get lines that are printed explicitly), but no direct way to print multiple occurrences of a match on a line. One way to solve this is to take advantage of the fact that there are at most two underlined characters, and have one s command to transform and output lines containing a single underlined character and another for lines with two. As before, I won't match the backspaces explicitly.

cal | sed -n 's/.*_.\(.\)_.\(.\).*/\1\2/p; s/.*_.\(.\).*/\1/p'

An alternative approach with sed, assuming that there is only one underlined segment on a line, is to remove everything before it and everything after it.

cal | sed -n 's/^[^_]*_/_/; s/\(_..\)[^_]*$/\1/p'

This leaves the underscores; we can remove them with a third replacement.

cal | sed -n 's/^[^_]*_/_/; s/\(_..\)[^_]*$/\1/; s/_.//gp'
Related Question