CertGrid CertGrid
Hands-on Lab·Red Hat Certified System Administrator

grep and Regular Expressions

One objective covering both grep's options and regular expressions. The trap worth the guide is that grep has two dialects: in basic expressions `{`, `+` and `?` are literal characters and need escaping, and in extended expressions they are operators. The same pattern silently means different things.

Essential Tools Guide 6 of 67 Beginner

Written against the versions above. `egrep` still works on RHEL 10 but prints a deprecation warning on some builds and is no longer the documented spelling. Use `grep -E`.

Every command on this page runs on RHCSA-A01.
Server NameIP AddressOSRolesCPURAMHDD
RHCSA-A01192.168.0.31RHEL 10.0 (Coughlan)Practice node (graded) - spare /dev/sda2 Core4 GB50 GB + 15 GB

Before you start

  1. Finding lines, counting them, and inverting

    root:x:0:0:Super User:/root:/bin/bash
    operator:x:11:0:operator:/root:/sbin/nologin

    Note the second line - grep root matched it because /root contains the string. grep matches anywhere on the line unless you tell it otherwise.

    The options that earn their keep:

    | Option | Does | |---|---| | -n | prefix the line number | | -c | count matching lines, print no text | | -v | invert: lines that do NOT match | | -i | ignore case | | -w | match whole words only |

    grep -c "" counting every line and grep -vc nologin counting the rest is the pattern worth remembering: -v plus -c answers "how many are not" in one command.

    bash Example session
    grep root /etc/passwdroot:x:0:0:Super User:/root:/bin/bashoperator:x:11:0:operator:/root:/usr/sbin/nologingrep -n bash /etc/passwd | head -41:root:x:0:0:Super User:/root:/bin/bash39:sysadmin:x:1000:1000:System Admin:/home/sysadmin:/bin/bashgrep -c "" /etc/passwd; echo "--- lines that are NOT nologin:"; grep -vc nologin /etc/passwd41--- lines that are NOT nologin:5grep -i RooT /etc/passwd | head -2root:x:0:0:Super User:/root:/bin/bashoperator:x:11:0:operator:/root:/usr/sbin/nologin

    Expected resultMatches anywhere on the line, counted and inverted.

    Success conditionYou can answer "how many" and "which not" without a pipeline.

  2. Whole words, and context

    mail:x:8:12:mail:/var/spool/mail:/sbin/nologin

    versus:

    mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
    systemd-oom:x:...

    -w requires a word boundary either side, so mail no longer matches inside spool/mail on other lines. On /etc/passwd, searching a short username without -w matches every path that happens to contain it.

    -B1 -A1 prints one line either side of the match, and -C1 does both. On a log file that is usually what you want: the error line alone rarely explains itself, and the line before it often does.

    bash Example session
    grep -w mail /etc/passwdmail:x:8:12:mail:/var/spool/mail:/usr/sbin/nologingrep mail /etc/passwdmail:x:8:12:mail:/var/spool/mail:/usr/sbin/nologingrep -B1 -A1 "^sync" /etc/passwdlp:x:4:7:lp:/var/spool/lpd:/usr/sbin/nologinsync:x:5:0:sync:/sbin:/bin/syncshutdown:x:6:0:shutdown:/sbin:/sbin/shutdown

    Expected resultA narrowed match, then surrounding context.

    Success conditionYou can stop a short search term matching half the file.

  3. Anchors and character classes

    root
    rpc

    ^ anchors to the start of the line, $ to the end:

    sync
    root
    sysadmin

    bash$ finds accounts with a login shell, which is a genuinely useful query and a common exam task.

    Brackets are a set of alternatives for one character:

    adm
    bin
    chrony

    and a leading ^ inside the brackets negates the set:

    bin
    daemon
    adm
    lp

    That is two different meanings for ^ in one syntax: outside brackets it anchors, inside them it negates. [^r] is "any character that is not r", and ^[^r] is "lines whose first character is not r".

    bash Example session
    grep "^r" /etc/passwd | cut -d: -f1rootrtkitrpcrpcusergrep "bash$" /etc/passwd | cut -d: -f1rootsysadmingrep "^[abc]" /etc/passwd | cut -d: -f1binadmavahicleviscolordchronygrep "^[^r]" /etc/passwd | cut -d: -f1 | head -4bindaemonadmlp

    Expected resultAnchored matches, a character set, and a negated set.

    Success conditionYou can anchor a pattern and express "one of these".

  4. The dialect trap

    Three lines - aa, aaa, aaaa - and a pattern asking for three a's:

    grep "a\{3\}"
    aaa
    aaaa

    Backslashed braces, and it works. Now the same thing written the way it looks like it should be:

    grep "a{3}"
    exit=1 - basic grep read the braces literally

    No matches, and no error. Plain grep uses Basic Regular Expressions, in which {, }, + and ? are ordinary characters. It searched for a literal a{3} and correctly found nothing.

    That silence is what makes this expensive: a pattern that is wrong does not complain, it just returns nothing, and you conclude the data is absent.

    -E switches to Extended Regular Expressions, where they are operators:

    grep -E "a{3}"
    aaa
    aaaa

    Same with ?:

    grep -E "colou?r"     -> color, colour
    grep "colou\?r"     -> color, colour

    Both work; one needs the backslash and one does not. Use grep -E and write the pattern the normal way.

    bash Example session
    printf 'aa\naaa\naaaa\n' > /tmp/g.txt; grep "a\{3\}" /tmp/g.txtaaaaaaagrep "a{3}" /tmp/g.txt; echo "exit=$? - basic grep read the braces literally"exit=1 - basic grep read the braces literallygrep -E "a{3}" /tmp/g.txtaaaaaaaprintf 'color\ncolour\n' > /tmp/g2.txt; grep -E "colou?r" /tmp/g2.txt; echo "--- and in basic grep:"; grep "colou\?r" /tmp/g2.txtcolorcolour--- and in basic grep:colorcolour

    Expected resultThe same quantifier silently ignored, then honoured.

    Success conditionYou will reach for -E before debugging a pattern that returns nothing.

  5. Searching trees, and extracting rather than matching

    /etc/ssh/sshd_config:#PermitRootLogin prohibit-password

    -r recurses a directory, and prefixes each hit with its file. On a task like "find which config sets X", that is the whole answer in one command.

    -l prints only the filenames, which is what you want when the matching line is long or there are many:

    /etc/dnf/dnf.conf
    /etc/resolv.conf

    -o prints only the matched part rather than the whole line:

    root
    bin
    daemon
    adm
    lp

    grep -oE "^[a-z_]+" extracts every username without cut, awk or a field separator. Between -o, -l and -c, most "list the X that Y" questions are one grep rather than a pipeline.

    bash Example session
    sudo grep -r "PermitRootLogin" /etc/ssh/ 2>/dev/null | head -4/etc/ssh/sshd_config:#PermitRootLogin prohibit-password/etc/ssh/sshd_config:# the setting of "PermitRootLogin prohibit-password".grep -oE "^[a-z_]+" /etc/passwd | head -5rootbindaemonadmlpgrep -rl "nameserver" /etc/ 2>/dev/null | head -3/etc/resolv.conf/etc/services/etc/selinux/targeted/contexts/files/file_contexts

    Expected resultA recursive search, extracted matches, and a filename-only list.

    Success conditionYou can find which file sets a setting, and pull values out of it.

Troubleshooting

Official sources