Bash and Linux

Print only valid phone numbers

easygrep and regex

Problem statement

A support export contains one phone number per line, in mixed formats. Print only the lines that are a valid number in one of two formats: xxx-xxx-xxxx or (xxx) xxx-xxxx, where x is a digit. Anything else, including extra spaces or missing separators, must be dropped.

file.txt

TEXT
987-123-4567
123 456 7890
(123) 456-7890
(555)123-4567
555-1234-567
(212) 555-0199
415-555-0123 ext 9

Examples

Example 1

Input: Run the command on `file.txt`.

Output: 987-123-4567 (123) 456-7890 (212) 555-0199

Explanation: (555)123-4567 has no space after the area code, 555-1234-567 has the wrong grouping, and the last line has trailing text.

Hints

Approach

Optimal

grep -E enables extended regular expressions. The pattern splits into two parts. The prefix ([0-9]{3}-|\([0-9]{3}\) ) is an alternation: either three digits and a dash, or three digits wrapped in literal parentheses followed by one space. The literal parentheses are escaped as \( and \) because in ERE a bare ( starts a group. The suffix [0-9]{3}-[0-9]{4} is common to both formats. ^ and $ anchor the match to the whole line, which is what rejects 415-555-0123 ext 9.

Bash
grep -E '^([0-9]{3}-|\([0-9]{3}\) )[0-9]{3}-[0-9]{4}$' file.txt

Follow-up questions

  • How would you do the same with sed or awk instead of grep?
  • How would you print the invalid lines with their line numbers? (grep -vnE ...)

Frequently asked questions

Plain grep uses basic regular expressions, where {3}, | and grouping ( ) must be written as \{3\}, \| and \( \), and a bare ( is a literal. Mixing the two styles is the most common cause of a regex that 'matches nothing'. Use -E (or egrep) and escape only the characters you want literally.