A timesaver: get a first regular expression from a few examples instead of writing it from memory, with every command run for real and tested against sample data.
▶Prefer to watch? This short video explains what grex is for, then builds patterns from examples on a real Ubuntu 24.04 machine and tests them with grep, with on-screen captions and no voiceover. Click to play, or watch it on YouTube.
grex: build a regular expression from examples.
grex 2026-10-20 2026-11-03 the examples are the arguments
grex -d -r EXAMPLES digits as \d, repeats as {n}
grex -f examples.txt one example per line
grep -P "$(grex ...)" file use the pattern straight away
grex is a command-line program that takes strings that should match and prints a regular expression that matches exactly those strings. With flags it generalises: every digit becomes \d, repeated parts become {4}, spaces become \s, and so on. It is a fast way to a first pattern when you know what the strings look like but not the syntax, or when you have a long list of IDs or codes. It is a start, not a finished answer: the pattern fits your examples and you must test it on strings it should not match.
This page is the written version of the Timesavers video. Every command below was run for real, in an Ubuntu 24.04 container with grex 1.4.6, and the patterns were tested with GNU grep (with -P), Python's re module and, in two places, grep -E. The examples are made-up dates, IDs and a small log. The section "What was not run" lists what was not tried.
What grex is for
Use grex when you can show the strings but cannot write the pattern:
a list of IDs or codes one pattern for all of them, from a file
dates, versions, order numbers the shape of the string, with \d and {4}
a quick first draft then refine it by hand in your editor or on a regex tester
pulling IDs out of a log the same pattern without anchors, with grep -o
It does not know what you mean. It sees only your examples and the flags, so it can produce a pattern that is too loose (it also matches strings you did not want) or too tight (it matches only what you typed, character by character). The sections below show both. It also does not replace learning the basics of regular expressions, because you must read the result to know if it is right.
Install
The project's README lists several routes: a release binary from GitHub, cargo (cargo install grex), Homebrew, Nix and others. The lab used the release tarball for Linux (x86_64, musl). The project does not publish a checksum file next to the release in the version used here, so if that matters, build it from source with cargo or check the release yourself:
$ mkdir -p ~/.local/bin
$ curl -fsSL https://github.com/pemistahl/grex/releases/download/v1.4.6/grex-v1.4.6-x86_64-unknown-linux-musl.tar.gz | tar xz -C ~/.local/bin
$ ls -l ~/.local/bin/grex | awk '{print $5, $9}'
4027800 /home/ubuntu/.local/bin/grex
$ grex --version
grex 1.4.6
The binary is one file of about 4 MB. On Ubuntu a new login shell finds ~/.local/bin when the directory exists.
Your first pattern
Give grex the examples as arguments. With no flags it matches the examples literally, and it is clever about shared parts:
$ grex a b c
^[a-c]$
$ grex ab abc abcd
^ab(?:cd?)?$
The first is a character range, and the second says "ab, optionally followed by c, and then optionally d". By default every pattern starts with ^ and ends with $, so it matches a whole string and nothing else.
Make it more general: -d and -r
Three dates, with no flags:
$ grex 2026-10-20 2026-11-03 2025-01-15
^202(?:5\-01\-15|6\-1(?:0\-20|1\-03))$
That matches those three dates and nothing else, so it is useless for the next date. -d turns every digit into \d. -r finds repeated parts and writes them as counts:
$ grex -d 2026-10-20 2026-11-03 2025-01-15
^\d\d\d\d\-\d\d\-\d\d$
$ grex -d -r 2026-10-20 2026-11-03 2025-01-15
^\d{4}(?:\-\d{2}){2}$
The last one reads "four digits, then twice (a hyphen and two digits)". That is the shape of a date written year-month-day. Other flags generalise other kinds of characters: -w turns word characters into \w, -s turns spaces into \s, and the capital letters do the opposite (-D for anything that is not a digit, -S for anything that is not a space, -W for anything that is not a word character):
$ grex -s 'ERROR 12' 'ERROR 405'
^ERROR\s(?:405|12)$
$ grex -w foo_1 bar_22
^\w\w\w\w\w(?:\w)?$
$ grex -D a1 b2
^\D[12]$
$ grex -S 'a b' 'c d'
^\S \S$
$ grex -W a-b c.d
^(?:a\Wb|c\Wd)$
Look at the -s and -w results. The first is exactly what you want for a log level and a number, with "12" and "405" kept as two alternatives because no -d was given. The second shows how general -w is: \w\w\w\w\w(?:\w)? matches any five or six word characters, not only your two strings. The more flags, the looser the pattern.
Check it against real data
Test the pattern before you trust it. Use it with grep -P, which understands this kind of pattern, on a file with good and bad lines:
$ cat dates.txt
2026-10-20
2026-11-03
2025-01-15
2027-12-31
20261020
10/20/2026
2026-1-3
$ grep -P "$(grex -d -r 2026-10-20 2026-11-03 2025-01-15)" dates.txt
2026-10-20
2026-11-03
2025-01-15
2027-12-31
The first three lines are the examples, and the pattern also matches 2027-12-31, which was not an example. That is the generalising doing its work: it knows the shape and not the dates. It correctly refused 20261020, 10/20/2026 and 2026-1-3, whose shape is different. If a date from 2027 should not match, grex cannot know that from digits alone, and you would need to edit the pattern (for example by hand to 202[56]).
The same pattern in Python:
$ python3 -c "import re,sys; print([l for l in open('dates.txt').read().split() if re.search(sys.argv[1], l)])" "$(grex -d -r 2026-10-20 2026-11-03 2025-01-15)"
['2026-10-20', '2026-11-03', '2025-01-15', '2027-12-31']
The patterns use Perl-style syntax (\d, non-capturing groups with (?:...)). Plain grep -E does not understand them, as the next section shows.
Examples from a file or standard input
-f reads one example per line, which is how to feed a long list:
$ cat ids.txt
INV-2026-0001
INV-2026-0042
INV-2025-1130
$ grex -d -r -f ids.txt
^INV(?:\-\d{4}){2}$
A hyphen in place of the examples reads them from standard input, so grex works at the end of a pipe:
$ printf '%s\n' ORD-0042 ORD-0107 | grex -
^ORD\-0(?:042|107)$
(Here, with no -d, the digits stay as they are: "0", then either 042 or 107.) Without examples, or with a file that does not exist, grex stops with an error:
$ grex -f nosuchfile
error: the specified file could not be found
$ grex
error: the following required arguments were not provided:
--file <FILE>
<INPUT>...
Usage: grex [OPTIONS] {INPUT...|--file <FILE>}
For more information, try '--help'.
Match inside a longer line: anchors
The default ^ and $ make a pattern match a whole string. A log line has other text around the ID, so remove the anchors with --no-anchors (or only one of them with --no-start-anchor or --no-end-anchor):
$ cat orders.log
10:02 order ORD-0042 paid
10:05 order ORD-0107 shipped
10:09 login ok
10:11 order ORD-9931 refunded
$ grex -d -r --no-anchors ORD-0042 ORD-0107 ORD-9931
ORD\-\d{4}
$ grep -oP "$(grex -d -r --no-anchors ORD-0042 ORD-0107)" orders.log
ORD-0042
ORD-0107
ORD-9931
grep -o prints only the matching part of each line, so this pulls the order IDs out of the log. Notice that the pattern was built from two examples and still found ORD-9931, because -d and -r made it describe "ORD, a hyphen, four digits". The two one-sided flags remove only one anchor:
$ grex --no-start-anchor ab abc
abc?$
$ grex --no-end-anchor ab abc
^abc?
Ignore case, and read the result
-i makes the pattern case-insensitive, with (?i) at the start:
$ grex -i Apple apple APPLE
(?i)^apple$
$ printf 'APPLE\nApple\napple\napples\n' | grep -P "$(grex -i Apple apple APPLE)"
APPLE
Apple
apple
"apples" was correctly left out, because of the $ anchor. -i also works with the other flags:
$ grex -d -r -i Id-12 ID-345
(?i)^id\-\d{2,3}$
Long patterns are hard to read. -x prints a verbose, indented version, and -c colours the parts of the pattern (the colours cannot be shown on this page, see the video). -g makes the groups capturing groups instead of non-capturing ones, which you need when you want to use the matched parts later:
$ grex -d -r -x ORD-0042 ORD-0107 ORD-9931
(?x)
^
ORD\-\d{4}
$
$ grex -g cat cow
^c(at|ow)$
-x starts the pattern with (?x), which turns on the verbose mode of the regex engine, and puts it on several lines. That form is meant to be read and pasted into code that supports it. It is not meant for grep, as the next section shows.
Repeats and other options
-r is controlled by two numbers. --min-repetitions is how many times something must repeat before it becomes a count, and --min-substring-length is how long a repeated piece must be:
$ grex -d -r --min-repetitions 3 aaaa aaaaaaa
^(?:a{4}|a{7})$
$ grex -r --min-substring-length 2 ababab abab
^(?:ab){2,3}$
The second one found the repeated two-letter piece "ab" and wrote it as (?:ab){2,3}. -e writes every non-ASCII character as an escape, which is useful when the pattern goes into a file that must stay plain ASCII:
$ grex -e größe straße 日本
^(?:stra\u{df}e|gr\u{f6}\u{df}e|\u{65e5}\u{672c})$
The escapes are in the \u{df} form, which grex prints and some engines do not read. See the next section. The other options in grex --help are --with-surrogates (with -e, for engines that use surrogate pairs) and the -h and -v flags.
Where the pattern will and will not work
grex writes Perl-style patterns (\d, (?:...), (?i), (?x)). That works in many places and not in some. What the lab showed:
Plain grep -E does not understand \d or (?:...). It printed a warning and, in this case, matched nothing:
$ grep -E "$(grex -d -r 2026-10-20 2026-11-03 2025-01-15)" dates.txt
grep: warning: ? at start of expression
exit status: 1
grep -P accepts a single pattern, so the several lines of -x break it:
$ grep -P "$(grex -d -r -x ORD-0042 ORD-0107 ORD-9931)" orders.log
grep: the -P option only supports a single pattern
exit status: 2
And the \u{df} escapes from -e are not understood by the PCRE library that grep -P uses:
$ printf 'straße\ngröße\n' | grep -P "$(grex -e größe straße)"
grep: PCRE2 does not support \F, \L, \l, \N{name}, \U, or \u
exit status: 2
So: use grep -P or Python or another Perl-compatible tool for the plain patterns, keep -x for reading, and check -e output on the engine you will use. Python's re module did accept the plain date pattern above.
What was not run in the lab
Only the commands on this page were run. These were not: the colours of -c (the screen was a terminal, but a page cannot show them), the library use of grex (it is also a Rust crate with Python bindings, according to the project), the --with-surrogates flag, very long lists of examples (the longest was three), and large or Unicode-heavy input beyond the one -e example. The patterns were tested with GNU grep -P, Python's re and, for the failure, grep -E. They were not tested with JavaScript, Java, PCRE in PHP, sed, awk or an editor. Windows and macOS were not tried. The release tarball was not checked against a checksum, because none was published next to it. Everything ran on Ubuntu 24.04 with grex 1.4.6.
Common problems and tips
The pattern only matches my examples. You used no generalising flags. Add -d for digits and -r for repeats, and -w or -s if letters or spaces vary.
The pattern matches things I do not want. It is too general (with -d, 2027 matches too). Add more examples that show what varies, drop a flag, or edit the pattern by hand.
grep -E says "? at start of expression", or matches nothing. The pattern is Perl-style. Use grep -P.
grep -P says it only supports a single pattern. That was the multi-line output of -x. Use the normal output for grep.
It does not match inside a line. The default ^ and $ anchor the pattern to the whole string. Use --no-anchors.
Tips. Give grex examples that differ in the ways the real data differs: a short one, a long one, one with a zero. Read the result: the shape (such as \d{4}(?:\-\d{2}){2}) tells you what grex understood. Always test the pattern on lines that should not match. Treat grex as a draft tool, and write the final pattern yourself.