The following command is used to search for a 7-digit phone number:
grep "[[:digit:]]\{3\}[ -]\?[[:digit:]]\{4\}" file
What does \? stand for?
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.”
...
Basic vs Extended Regular Expressions
In basic regular expressions the meta-characters ?, +, {, |, (, and )
lose their special meaning; instead use the backslashed versions
\?, \+, \{, \|, \(, and \).
Further info:
egrep command is equivalent to grep -E. For versions other than GNU grep, grep might or might not accept the -E option, and egrep might be a separate program.
grep -E is the official POSIX way. egrep was deprecated in susv2 (1997) and removed in susv3 (2001) from the POSIX and Unix specs.
\? is a GNUism though.
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.