Linux Basics: Extract Fields with Bash Regex Captures
A shell script sometimes needs just one field from a small, predictable string. Bash can match a regular expression and keep its captured groups without starting a separate text-processing command. Practice extracting fields, rejecting incomplete input, and keeping a match before another test replaces it.
These examples were tested on Linux with GNU Bash 5.2.37. Use Bash rather than sh. The expressions use ASCII letters and digits under the C locale; they are deliberately small formats, not general parsers.
Prepare a scratch directory
You need Bash, a text editor, and the mktemp, rm, and rmdir utilities. Keep using the same terminal. Save each named source file in this directory before running it. The guards stop the shell if setup fails; invoking bash explicitly avoids changing executable permissions. No administrator access is needed.
start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 11. Read the captured groups
Save this as regex-fields.sh:
#!/usr/bin/env bash
export LC_ALL=C
line='build-42:ready'
pattern='^([a-z]+)-([0-9]+):([a-z]+)$'
if [[ $line =~ $pattern ]]; then
printf 'Full match: %s\n' "${BASH_REMATCH[0]}"
printf 'Name: %s\n' "${BASH_REMATCH[1]}"
printf 'Number: %s\n' "${BASH_REMATCH[2]}"
printf 'State: %s\n' "${BASH_REMATCH[3]}"
else
printf 'Unexpected record\n' >&2
exit 1
fiRun it:
bash regex-fields.shOutput:
Full match: build-42:ready
Name: build
Number: 42
State: readyEach parenthesized group gets an array element. Index zero contains the whole match, while indexes one through three follow the opening parentheses from left to right. The ^ and $ anchors require the entire value to fit. Without anchors, a matching substring would be enough.
Store the expression in a single-quoted variable assignment to make its punctuation easier to read. Keep the expansion after =~ unquoted when you want its regex operators to work. Inside [[ ... ]], that expansion does not undergo ordinary word splitting or filename expansion.
2. Reject a record with extra text
Save this as regex-setting.sh:
#!/usr/bin/env bash
export LC_ALL=C
pattern='^([a-z][a-z0-9_-]{0,15})=([0-9]{1,4})$'
if [[ $# -ne 1 ]]; then
printf 'Expected one setting\n' >&2
exit 2
fi
if [[ $1 =~ $pattern ]]; then
printf 'Key: %s; digits: %s\n' "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}"
else
printf 'Invalid setting\n' >&2
exit 2
fiRun it:
bash regex-setting.sh threads=0012
bash regex-setting.sh "threads=12 extra" 2>&1
printf 'Rejected status: %d\n' "$?"Output:
Key: threads; digits: 0012
Invalid setting
Rejected status: 2The key starts with a lowercase letter and contains at most sixteen characters. The value contains one to four decimal digits. Leading zeros remain text; matching digits does not convert them into a number or check an application-specific range.
The second invocation intentionally fails. Its error is redirected to standard output so the displayed transcript has a predictable order. Argument count is checked separately, so missing input and multiple arguments cannot silently become a valid record.
3. Understand quoting and changing match state
Save this as regex-state.sh:
#!/usr/bin/env bash
export LC_ALL=C
pattern='[0-9]+'
text='jobs=12'
if [[ $text =~ "$pattern" ]]; then
printf 'Quoted pattern: matched\n'
else
printf 'Quoted pattern: no match\n'
fi
if [[ $text =~ $pattern ]]; then
digits=${BASH_REMATCH[0]}
[[ label =~ ^([a-z]+)$ ]] || exit 1
printf 'Saved digits: %s\n' "$digits"
printf 'Latest match: %s\n' "${BASH_REMATCH[0]}"
fiRun it:
bash regex-state.shOutput:
Quoted pattern: no match
Saved digits: 12
Latest match: labelQuoting "$pattern" makes this pattern literal, so Bash looks for the characters [0-9]+ rather than a run of digits. The unquoted version finds 12 inside the larger string.
Read captures only after a successful match, and copy anything needed later. A subsequent regex test updates BASH_REMATCH; it is shared shell state, including across function calls. Declaring a local variable with that special name is not a reliable way to isolate matches.
Keep in mind
These are POSIX extended regular expressions, not PCRE patterns. A match returns zero, no match returns one, and an invalid expression returns two. Keep patterns fixed when possible, and distinguish malformed patterns from ordinary rejected input when patterns come from configuration.
Clean up
In the same terminal, remove only the named practice files, return to your original directory, and remove the empty scratch directory:
rm -- regex-fields.sh regex-setting.sh regex-state.sh
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dirReferences
Acknowledgements
This post was created with assistance from AI.