Output current day using cal

The proper way to do this is just to use date:

% date +%d
19

The underscore you see is not an underscore, but a control character shown by your terminal. If you really want to use cal, you need to find out what that control character is and look for it. You can use this by using something like cal | xxd.

Another problem is that cal only outputs this control character when stdout is a tty, so you need to convince it that it is one (consider script for this).


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'

I'm not in front of a GNU cal command at the moment, but this is closer to what you want:

cal | grep -Eo '_[[:digit:]]+' | sed 's/_//'

The grep -E says to allow extended regular expressions (for the [[:digit:]] class); the -o flag says to only output the matching portion; the sed command removes the underscore.

That's the cal + grep way to do it, but as Chris said, the normal way to get the current date is with the date command.