Skip to content

Latest commit

 

History

40 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

.NET Coverage

regex

Search and replace with .NET regular expressions from the command line, using the same engine as your C# code.

regex CLI and C# share the .NET regular expression engine: same pattern, same engine

Why not grep or ripgrep?

Use them for general text search, they are faster and more widely installed. Use regex when you need the .NET flavor:

  • Try a pattern on the CLI, then paste it into C# unchanged. No dialect differences.
  • .NET-only features such as balancing groups, and named groups in replacements (--replace "id=${name}").
  • Lookarounds and backreferences without extra flags or a separate engine.
  • One tool with the same behavior on Linux, macOS and Windows, installed via dotnet tool.

Features

Install

Requires the .NET 10 SDK or runtime.

dotnet tool install --global cap.regex

Update to the latest version:

dotnet tool update --global cap.regex

Options

Usage:

  regex [<pattern> [<path>...]] [options]

Arguments:
  <pattern>  The search pattern as .NET Regular Expression (RegEx).
  <path>     File or directory to operate. (A directory must end with a directory separator)

Options:
  -R, --replace <replace>        replacement Pattern (regex)
  -n, --dry-run                  with --replace: report what would change, but do not write any file
  -d, --diff                     with --replace: print the changes as unified diff instead of the summary, but do not write any file (implies --dry-run)
  -c, --case-sensitive           enables case-sensitive behavior - btw. disables the by default enabled ignore-case option
  -f, --filter <filter>          wildcard based file filter, e.g. *.txt [default: *.*]
  -r, --recursive                progress all subdirectories
  --offset-width <offset-width>  output-formatting: set the count of characters used for the offset column [default: 6]
  -o, --only-matching            prints only the match
  -m, --max-count <max-count>    limit matches to the given count
  -v, --verbose                  show additional information
  -V, --version                  show version information
  -?, -h, --help                 Show help and usage information

Exit code is 0 on success and 1 on errors (e.g. missing pattern, invalid regular expression or invalid option value).

Usage Examples

Every example is self-contained: the files it starts from, the command, and the exact output. Click to expand.

Search a directory tree for a word in *.txt files

Starting point:

docs/
├── intro.txt        "Hello World" / "hello again" / "Goodbye"
└── sub/
    ├── more.txt     "Say Hello to everyone" / "nothing here"
    └── skip.md      "no match"

Command (--recursive walks subfolders, --filter limits the files, a directory must end with /):

$ regex --recursive --filter '*.txt' Hello docs/
Offset:0      Hello World
Offset:12     hello again
docs/intro.txt: found 2 matches
Offset:4      Say Hello to everyone
docs/sub/more.txt: found 1 match

Matching is case-insensitive by default, so hello again is found too. The offset is the character position of the match in the file. skip.md is ignored by the filter.

Case-sensitive search with --case-sensitive

notes.txt:

Hello there
hello there

Default (case-insensitive) versus --case-sensitive:

$ regex Hello notes.txt
Offset:0      Hello there
Offset:12     hello there
notes.txt: found 2 matches

$ regex --case-sensitive Hello notes.txt
Offset:0      Hello there
notes.txt: found 1 match
Print only the match with --only-matching and --max-count

names.txt:

Name:Alice
Name:Bob
Age:42

Print just the matched text instead of the whole line:

$ regex --only-matching 'Name:\w+' names.txt
Name:Alice
Name:Bob

Stop after the first match:

$ regex --max-count 1 --only-matching 'Name:\w+' names.txt
Name:Alice
Replace with a named group (rewrites the file)

names.txt before:

Name:Alice
Name:Bob
Age:42

Command. Use single quotes in bash/zsh so the shell does not expand ${name}:

$ regex 'Name:(?<name>[A-Za-z]+)' --replace 'Hello ${name}, how are you?' names.txt
names.txt: did 2 replacements

names.txt after:

Hello Alice, how are you?
Hello Bob, how are you?
Age:42

Files without matches are left untouched.

Preview a replacement with --dry-run (counts only)

--dry-run reports what would change and writes nothing. It also works across a whole tree.

docs/intro.txt contains 2 matches for hello, docs/sub/more.txt contains 1:

$ regex --recursive --filter '*.txt' --dry-run hello --replace bye docs/
docs/intro.txt: would do 2 replacements
docs/sub/more.txt: would do 1 replacement

Both files are unchanged afterwards.

Preview as unified diff with --diff, then apply it with patch

names.txt:

Name:Alice
Name:Bob
Age:42

--diff prints the changes as a unified diff and writes nothing:

$ regex 'Name:(?<name>[A-Za-z]+)' --replace 'id=${name}' --diff names.txt
--- names.txt
+++ names.txt
@@ -1,3 +1,3 @@
-Name:Alice
-Name:Bob
+id=Alice
+id=Bob
 Age:42

The diff goes to stdout without colors when redirected, so you can save it, review it and apply it later with patch -p0 or git apply -p0 (use relative paths, git apply rejects absolute ones):

$ regex 'Name:(?<name>[A-Za-z]+)' --replace 'id=${name}' --diff names.txt > names.patch
$ patch -p0 < names.patch
patching file names.txt

names.txt after patch:

id=Alice
id=Bob
Age:42

Changelog

See CHANGELOG.md.

About

regex tool for the command line for macOS, Linux and Windows

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages