squeeze extracts 20 kinds of data from text, including URLs, emails, IPs,
hashes, and TODOs. Like grep -o, with the patterns built in.
$ cat notes.md
See [the docs](https://example.com/docs), or https://en.wikipedia.org/wiki/Squeeze_(disambiguation).
Ping ops@example.com, then check <https://status.example.com>.
$ grep -oE 'https?://[^ ]+' notes.md
https://example.com/docs),
https://en.wikipedia.org/wiki/Squeeze_(disambiguation).
https://status.example.com>.
$ squeeze --url notes.md
https://example.com/docs
https://en.wikipedia.org/wiki/Squeeze_(disambiguation)
https://status.example.com- Recognizes values in prose, Markdown, JSON, and HTML, handling surrounding punctuation and balanced parentheses in links.
- Reads pipes, files, globs, and directory trees, respecting ignore files.
- Prints one value per line, editor locations, or JSON, YAML, and CSV.
- Available as a CLI and a Rust library.
Homebrew (macOS and Linux):
brew install aymericbeaumet/tap/squeezemise (Linux, macOS, and Windows):
mise use -g github:aymericbeaumet/squeezeCargo (Rust 1.95+):
cargo install --locked squeeze-cliAll three install the squeeze command. Prebuilt binaries support Linux,
macOS, and Windows on amd64 and arm64. You can also download them from
GitHub Releases,
verify the archive against SHA256SUMS, and put the binary on your PATH.
Shell completions: squeeze --completions <shell> prints the script for
bash, zsh, fish, elvish, or PowerShell; Homebrew installs them for you.
Pick one or more finders, or none to run all of them. Results contain one
matched value per line; use --with-kind to label them. With no path, or
with -, squeeze reads stdin. Paths can be files, directories, or quoted
glob patterns. Directory scans skip hidden entries, ignored paths, and
binary files.
# Everything squeeze recognizes in a file, one value per line
squeeze app.log
# Unique URLs on a web page
curl -s https://example.com | squeeze --url --uniq
# Everyone who committed to a repository
git log --format='%an <%ae>' | squeeze --email --sort --uniq
# IPs, request IDs, and timestamps in logs, labeled by kind
kubectl logs deploy/api | squeeze --ip --uuid --datetime --with-kind
# TODOs and FIXMEs across a repository, with locations your editor understands
squeeze --todo --fixme --with-location .
# Rust sources only
squeeze --todo --fixme --with-location 'src/**/*.rs'
# Extract URLs as JSON, with match locations
squeeze --url --with-location --output json notes.mdWith several finders, results come out in the order they appear:
$ echo '2026-01-15T10:30:00Z GET /health from 10.0.4.2 took 3ms' | squeeze --datetime --ip --with-kind
datetime 2026-01-15T10:30:00Z
ip 10.0.4.2| Finder | Flag | Examples |
|---|---|---|
| CIDR | --cidr |
192.168.1.0/24, 2001:db8::/32 |
| Codetags | --codetag, --todo, --fixme |
TODO: fix this, FIXME(#42): bug |
| Colors | --color |
#ff0000, rgb(255, 0, 0), hsl(0, 100%, 50%) |
| Datetimes | --datetime |
2024-01-15, 2024-01-15T10:30:00Z |
| Domains | --domain |
example.com, mail.example.co.uk |
| Emails | --email |
user@example.com, first.last+tag@company.co.uk |
| Emojis | --emoji |
π, π¨βπ©βπ§βπ¦, 1οΈβ£ |
| Env vars | --env |
$HOME, ${PATH}, ${VAR:-default} |
| Handles | --handle |
@alice, @user@example.social |
| Hashes | --hash, --md5, --sha1, --sha256, --sha512 |
5d41402abc4b2a76b9719d911017c592 |
| IPs | --ip, --ipv4, --ipv6 |
192.168.1.1, ::1, 2001:db8::1 |
| JSON | --json |
{"key": "value"}, [1, 2, 3] |
| JWTs | --jwt |
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0.c2ln |
| MACs | --mac |
00:1A:2B:3C:4D:5E, 001A.2B3C.4D5E |
| Modelines | --modeline |
vim: set ts=4 sw=4 et: |
| Paths | --path |
/etc/hosts, ./src/main.rs:42:10 |
| Phones | --phone |
+14155551234, (415) 555-1234 |
| Semver | --semver |
1.0.0, v2.3.1-rc.1+build.42 |
| URIs | --uri, --url, --http, --https |
https://example.com, mailto:hi@example.com |
| UUIDs | --uuid |
550e8400-e29b-41d4-a716-446655440000 |
Some finders take a filter: --codetag=todo,fixme, --hash=sha256,
--uri=https,ssh. --url is shorthand for the schemes people usually mean
by a URL (http, https, ftp, mailto, β¦); --uri accepts any scheme.
Finders favor precision over recall: they recognize common formats and may
skip unusual valid values. They extract candidates, without checking whether
a URL is reachable or an email address exists. Use --strict for URI
syntax following RFC 3986.
| Flag | Description |
|---|---|
-1, --first |
stop after the first result |
--last |
only print the last result |
--sort |
sort the results |
--uniq |
deduplicate the results, keeping the first occurrence |
--with-kind |
print the finder name (uri, email, β¦) with each result, aligned in a terminal and tab-separated otherwise |
--with-location |
print path:line:column: before each result |
--output <fmt> |
text (default), json, yaml, csv, or none |
--no-overlap |
drop matches that overlap an earlier one (--precedence longest keeps the longest instead) |
--copy |
copy the results to the clipboard |
--open |
open each result with the default application |
-j, --jobs <N> |
scanning threads; auto (default) uses every core for directories, stdin, and files of 8 MiB and more, output of a single input kept in order |
Walking a directory:
| Flag | Description |
|---|---|
--hidden |
also search hidden files and directories (.git is always skipped) |
--no-ignore |
do not respect .gitignore, .ignore, and git exclude rules |
--follow |
follow symbolic links |
JSON and YAML return lists of values by default; CSV returns one value per
row. With --with-kind or --with-location, structured output includes
kind, value, line, column, start, end, and source. Lines and
columns start at 1; byte offsets start at 0 and the end is exclusive.
Matches can overlap: a URL can also contain a domain or an IP address.
Use --no-overlap to suppress overlapping results. Run squeeze --help
for the full list of options.
Press Enter in visual mode to open the first URL in the selection:
" ~/.vimrc
vnoremap <silent> <CR> :<C-U>'<,'>w !squeeze -1 --url --open<CR><CR>Load every TODO and FIXME of a project into the quickfix list:
:cexpr system("squeeze --todo --fixme --with-location 'src/**/*'")Press Enter in copy mode to open the first URL in the selection:
# ~/.tmux.conf
bind -T copy-mode-vi enter send -X copy-pipe-and-cancel "squeeze -1 --url --open"Pick a URL from the scrollback of the current tmux pane and open it:
tmux capture-pane -pJ -S - | squeeze --url --uniq | fzf --tac | squeeze --url --openList the URLs from your shell history, most recent first:
# ~/.bashrc, ~/.zshrc
urls() { fc -rl 1 | squeeze --url --uniq; }No regex engine is involved: SIMD searches and byte classification narrow the
positions where a match can start. Big files, streams, and directory trees can
be scanned across available cores; -j 1 selects a single thread.
On a generated 56 MiB mixed corpus, single-threaded squeeze used 18 ms of CPU to extract URLs versus ripgrep's 35 ms. The tools use different URL grammars and return different match counts. See the benchmarks for the commands, data, results, and limitations.
Add squeeze-lib, imported as squeeze:
[dependencies]
squeeze = { package = "squeeze-lib", version = "1.0" }use squeeze::{email::Email, scanner::Scanner, uri::URI};
let scanner = Scanner::new(vec![Box::new(URI::default()), Box::new(Email::default())]);
let line = "Ping ops@example.com about https://example.com/status";
for m in scanner.scan_line(line) {
let kind = scanner.finders()[m.finder_index].id();
println!("{kind}\t{}", &line[m.range]);
}See the API documentation for individual finders and scanner options.
Install mise, then run the same checks as CI:
mise trust
mise install
mise run checkSee development and releases for the other tasks and the release process. Bug reports with a sample input that squeeze gets wrong are especially welcome.
