3 Answers
3

It's like ? in many other regular expression engines, and means "match zero or one of whatever came before it".

In your example, the \? is applied to the [ -], meaning it tries to match a space or a minus, but that the space or minus is optional.

So any of these will match:

555 1234
555-1234
5551234

The reason it's written as \? rather than ? is for backwards compatibility.

The original version of grep used a different type of regular expression called a "basic regular expression" where ? just meant a literal question mark.

So that GNU grep could have the zero or one functionality, they added it, but had to use the \? syntax so that scripts that used ? still worked as expected.

Note that grep has an -E option which makes it use the more common type of regular expression, called "extended regular expressions".

man 1 grep:

-E, --extended-regexp
Interpret PATTERN as an extended regular expression
(ERE, see below). (-E is specified by POSIX.)
-G, --basic-regexp
Interpret PATTERN as a basic regular expression (BRE, see below).
This is the default.

...

Repetition
A regular expression may be followed by one of several repetition operators:
? The preceding item is optional and matched at most once.

...

grep understands three different versions of regular expression syntax:
“basic,” “extended” and “perl.”

Unfortunately, the exact syntax of regular expressions varies slightly between different programs: grep regexes aren't exactly the same as sed regexes, which aren't exactly the same as Emacs regexes, which aren't exactly the same as C++ regexes, and so on. To make matters worse, even a "standard" tool like grep can vary slightly between different Unix-like operating systems.

In a regex, some characters have special meaning (such as the square brackets in your example), and revert to their normal meaning as literal characters when you "escape" them by putting a backslash in front of them (so a literal bracket would be written as \[). Others work the other way around, and only take on special meaning when escaped (e.g. plain n is just a letter, but \n is a line feed). And these, again, can vary between regex implementations.

In most regex implementations, a question mark means that the previous item is optional, while an escaped question mark (\?) is a literal question mark. But in a few dialects, it's the other way around. Your example could make sense either way around, but I suspect you have one of the dialects where ? is a literal and \? is the optional symbol. So your regex probably means "three digits, optionally followed by a space or dash, followed by four digits".

(Another clue can be seen in constructs like \{3\}, which is clearly intended to mean "exactly 3 of the previous item". In most regex dialects this would be written {3}, and \{ would be a literal brace.)

This is a quick summary of information that's already contained in the other answers.

In grep, ? matches a literal question-mark character, and \? denotes zero or one occurrence of whatever precedes it. So in the example in your question, [ -]\? matches either a space, or a hyphen, or nothing.

In egrep or grep -E, it's the other way around; \? matches a literal question mark, and ? denotes zero or one occurrence.

This applies to GNU grep; the details for non-GNU grep implementations may differ slightly. In particular, grep and egrep were historically two separate programs, and I don't think old greps had the -E option. POSIX does specify grep -E, but (I was surprised to discover) doesn't mention egrep.