rsomics-seq-grep
Filter FASTA/FASTQ records by ID, full header, or sequence content — a Rust
port of seqkit grep.
Install
cargo install rsomics-seq-grep
Usage
# Keep records by exact ID (default target is the ID — the header up to the
# first space/tab, not the whole header line)
rsomics-seq-grep -p seq1,seq2 in.fa
# Match against the full header line instead of just the ID
rsomics-seq-grep -n -p "seq1 description text" in.fa
# 10k-ID allow-list from a file (a pattern file always overrides -p entirely)
rsomics-seq-grep -f ids.txt in.fq.gz
# Regex against ID/name (unanchored — partial match, unlike the default
# exact-match mode)
rsomics-seq-grep -r -p 'sample_[0-9]+$' in.fa
# Search the sequence itself; both strands are searched by default
rsomics-seq-grep -s -p GAATTC in.fa
# IUPAC degenerate motif (implies -s)
rsomics-seq-grep -s -d -p RYSW in.fa
# Restrict sequence search to a 1-based region (implies -s)
rsomics-seq-grep -s -R 1:12 -p ATG in.fa
# Invert: drop matching records, keep the rest
rsomics-seq-grep -f ids.txt --invert-match in.fa
# Count matches without writing records
rsomics-seq-grep -C -f ids.txt in.fa
# stdin
cat in.fa | rsomics-seq-grep -p seq1 -
Semantics (verified against seqkit v2.13.0, both by reading
shenwei356/seqkit/shenwei356/bio source and by black-box comparison)
- Default target is the ID, defined the same way as
seqkit'sDefaultIDRegexp(^(\S+)\s?): the header up to the first space or tab, whichever comes first.-n/--by-nameswitches the target to the entire header line (ID + description) instead. - Default matching is whole-string equality, not substring —
-p seq1does not match a record namedseq1_dup. This isseqkit's own documented deviation from POSIX/GNU grep.-r/--use-regexpswitches to an unanchored (partial-match) regex — the regex source is used exactly as given, not escaped. -s/--by-seqswitches the target to the sequence itself and switches matching to substring search (not equality). Both the plus and minus (reverse-complement) strand are searched by default —-P/--only-positive-strandrestricts to the plus strand only. Strand search short-circuits: the first strand that hits wins, matchingseqkit'sfor strand := range strands { if hit { break } }.-d/--degenerateexpands an IUPAC nucleotide motif (ACGTURYMKSWHBVDN, case-preserved) into the exact regex character classes fromseqkit'sbio/seq/seq.goDegenerateBaseMapNucltable, then matches as an unanchored regex. Implies-s. Protein degenerate codes (DegenerateBaseMapProt) are not implemented —-dhere is nucleotide-only, the overwhelmingly common use (restriction sites, primer motifs, degenerate probes).-R/--region <start:end>restricts-ssearch to a 1-based window, applied independently per strand (so-R 1:12on the minus strand means the first 12 bases of the reverse complement). An out-of-range region skips that strand rather than erroring. Negative indices count from the end (-12:-1= last 12 bases); mixed-sign spans (start<0, end>0) are rejected as ambiguous, matchingseqkit. Implies-s.- An empty pattern (
-p "") is legal and specific: in ID/name mode it matches only records with an empty ID/name; in-smode (bytes.Containssemantics upstream) it matches only records with an empty sequence rather than "matches everywhere" —seqkitspecial-cases this rather than relying on the substring-search library's usual empty-needle-always-matches behaviour, and so do we. In regex/degenerate mode an empty pattern compiles to^$(exact-empty match) rather than the empty regex (which would match every position). -i/--ignore-caselowercases both sides for equality/substring search and prefixes(?i)for regex/degenerate search — same as upstream.- A pattern file (
-f) overrides-pentirely when both are given — this isseqkit's actual behaviour (verified empirically, not just from reading the help text): the two sources are never merged. Pattern-file lines are read verbatim except for a trailing\r/\nstrip; blank lines are skipped. - FASTA output line width defaults to 60 (
-w/--line-width, 0 = no wrap), matchingseqkit's default. FASTQ output always ignores-w—seqkit grepforces line width to 0 for FASTQ regardless of the flag (fastx.ForcelyOutputFastq/ the per-fileLineWidth = 0override ingrep.go), and so do we. - Header lines are reproduced verbatim — grep never reconstructs or reflows the header, only the sequence/quality body is wrapped.
- Input: plain or gzip FASTA/FASTQ, content-sniffed (not by file extension);
-reads stdin. Output order always matches input order.
Flags implemented vs. deferred
Implemented to real feature-completeness: -p/--pattern, -f/--pattern-file,
-n/--by-name, -r/--use-regexp, -i/--ignore-case, -s/--by-seq,
-d/--degenerate, --invert-match, -R/--region,
-P/--only-positive-strand, -w/--line-width, -C/--count, -o/--output.
Deferred — not defined on the CLI at all, so clap itself rejects them with "unexpected argument" (exit 2) rather than silently ignoring them:
| Flag | Why deferred |
|---|---|
-m/--max-mismatch |
Upstream's mismatch search builds an FM-index (BWT) per record via a separate goroutine-parallel code path — a materially different algorithm, high effort for a rarely-used flag (upstream's own docs suggest alignment tools instead for large genomes). |
-c/--circular |
Niche (plasmid/circular-genome wraparound search); not exercised by the common FASTA/FASTQ filtering use case this crate targets. |
-D/--allow-duplicated-patterns |
Edge-case interaction with duplicate pattern counting; no behavioural difference for the normal (non-duplicated) pattern case this crate is built for. |
--delete-matched |
Upstream performance-only optimisation for very large pattern sets under -r; does not change output for the common case. |
-I/--immediate-output |
Buffering behaviour only, no output difference. |
-t/--seq-type, --alphabet-guess-seq-length, --id-ncbi, --id-regexp, --compress-level, -X/--infile-list, --skip-file-check |
Alphabet-guessing / ID-parsing / IO knobs outside this crate's scope; the default ID/alphabet behaviour (documented above) already matches upstream's default (auto/DefaultIDRegexp) path. |
One further documented divergence: upstream auto-detects the input
alphabet and forces -P (positive-strand-only) automatically for
protein/unclassified sequences, since reverse-complementing a protein
sequence is meaningless. This crate always honours -s's "both strands by
default" rule regardless of alphabet — it only matters if -s is used
without -P on protein input, an unusual combination for a nucleotide-first
tool.
-v is not used for invert-match here (unlike upstream) because -v is
already CommonFlags' --verbose short flag across every rsomics-* tool
in this campaign; --invert-match is long-only by the established
conflict-resolution convention.
Origin
Independent Rust reimplementation of seqkit grep, informed by:
- The seqkit paper: Shen, W. et al. SeqKit2: A Swiss Army Knife for Sequence and Alignment Processing. iMeta (2024) [doi:10.1002/imt2.191].
- Reading the upstream source (seqkit is MIT-licensed, so reading and citing
is allowed and standard practice in this project):
seqkit/cmd/grep.go(flag wiring, matching precedence, strand loop, region handling),shenwei356/bioseqio/fastx/reader.go(parseHeadIDAndDesc,DefaultIDRegexp),seqio/fastx/records.go(Format/FormatToWriter— the exact FASTA wrap / FASTQ-always-unwrapped output rule),seq/seq.go(DegenerateBaseMapNucl,RevCom/Complement), andseq/alphabet.go(theDNAredundantcomplement pairing table). - Black-box behaviour comparison against
seqkitv2.13.0 across ID/name exact match, regex, ignore-case, invert-match, by-seq substring, both strands, only-positive-strand, IUPAC degenerate motifs, region restriction, line-width variants, gzip input, stdin, pattern files, and count-only mode (tests/compat.rs, frozen goldens intests/golden/).
License: MIT OR Apache-2.0. Upstream credit: seqkit (MIT).
External-dep quadrant classification
needletail— Quadrant ① (pure Rust FASTA/FASTQ parser; content-sniffed gzip via itsflate2/zlib-rsbackend).regex— Quadrant ① (pure Rust, SIMD-accelerated literal prefilter internally).memchr— Quadrant ① (pure Rust, SIMD substring search for-sliteral matching).rayon— Quadrant ① (pure Rust parallelism; drives the by-seq path's per-record matching across threads, honouring-t/--threadsviarsomics-common's default-on rayon-pool wiring).rsomics-common,rsomics-help,clap,serde— Quadrant ④.
JSON output schema (--json)
{
"schema_version": "1.0",
"tool": "rsomics-seq-grep",
"tool_version": "0.1.0",
"status": "ok",
"result": {
"input": "in.fa",
"output": "-",
"pattern_count": 3,
"total_records": 200000,
"matched": 51994
}
}