Software engineering
When to Use rg, nl, Vim, and apply_patch
How ripgrep, nl, Vim, and apply_patch divide repository search, numbered evidence, interactive editing, and reviewable automated changes.
On this page
- Four tools, four responsibilities
- rg searches the repository
- rg compared with grep, git grep, find, and fd
- nl turns excerpts into stable evidence
- nl compared with cat -n, rg -n, and awk
- Vim is for an interactive editing session
- Vim compared with an IDE, sed, and patch editing
- apply_patch describes the intended change
- apply_patch compared with patch, git apply, and sed -i
- The tools form a sequence
- 1. Locate the relevant code
- 2. Read enough context
- 3. Apply the smallest coherent edit
- 4. Review and verify
- Common command pairings
- Selection rules
- References
Why would an automated coding workflow use rg, nl, and apply_patch instead of opening every file in Vim?
Because these tools solve different parts of the job. Searching a repository, producing stable line references, editing interactively, and applying a reviewable change are separate operations. Treating them as one operation makes automation harder to inspect and less reliable.
Four tools, four responsibilities
| Tool | Primary job | Interactive | Changes files | Best fit |
|---|---|---|---|---|
rg | Search text across files | No | No | Locate symbols, errors, configuration keys, and files containing a pattern |
nl | Add line numbers to text | No | No | Produce stable, readable excerpts with exact line references |
| Vim | Browse and edit in a terminal session | Yes | Yes | Human-driven reading, navigation, and editing |
apply_patch | Apply explicit file changes from a patch | No | Yes | Automated edits that should be narrow and reviewable |
The distinction is more useful than asking which tool is best. rg cannot replace an editor. Vim can search and show line numbers, but it introduces an interactive session that an automated process does not need. apply_patch can change a file, but it is not a discovery tool.
rg searches the repository
rg is the executable name for ripgrep. It recursively searches files for a regular-expression pattern. Its default repository behavior is significant: it respects ignore files such as .gitignore and skips hidden and binary files unless the caller overrides those filters.[1]
Search the current repository:
rg "SecurityConfig"
Restrict the search to Java files:
rg "AccessDenied" -t java
Show only matching file names:
rg -l "AccessDenied"
Search ignored and hidden content only when that is intentional:
rg --hidden --no-ignore "legacySetting"
This default filtering is helpful for source code, but it can also hide the answer. Generated files, ignored local configuration, and hidden directories require explicit flags. rg -uuu disables all automatic filtering, so it should be used deliberately rather than as the normal starting point.
rg compared with grep, git grep, find, and fd
| Tool | What it searches | Repository-aware default | Works outside Git | Prefer it when |
|---|---|---|---|---|
rg | File contents | Respects ignore files and skips hidden or binary files | Yes | Searching source trees quickly with concise flags |
grep | File contents or standard input | No | Yes | Portability matters or another command is producing the input stream |
git grep | Contents of files known to Git | Uses the Git index and working tree | No | The search must stay within tracked repository content |
find | Paths and filesystem metadata | Not applicable | Yes | Selecting files by name, type, size, owner, or timestamp |
fd | Paths | Respects ignore files by default | Yes | Finding files with shorter syntax than find |
The closest content-search alternatives are grep and git grep. find and fd answer a different question: which paths match? They are often paired with a content search, but they do not replace one.
nl turns excerpts into stable evidence
nl reads files or standard input and writes the text with line numbers added. GNU Coreutils documents separate numbering styles for body, header, and footer sections; -ba selects all body lines, including blank ones.[2]
Number every line:
nl -ba src/App.java
Read a focused range while preserving the original line numbers:
nl -ba src/App.java | sed -n '100,140p'
The second form is useful in code review and automated analysis. The result can be cited as src/App.java:117 without relying on terminal height, cursor position, or editor state.
nl does not search and does not modify the file. It prepares text for inspection. When only matching lines matter, rg -n is shorter:
rg -n "handleRequest" src/
Use nl when the surrounding block matters. Use rg -n when the matching locations are enough.
nl compared with cat -n, rg -n, and awk
| Tool | Output scope | Blank-line behavior | Search capability | Prefer it when |
|---|---|---|---|---|
nl -ba | Every input line | Numbers blank lines | No | A complete excerpt needs stable source line numbers |
cat -n | Every input line | Numbers blank lines | No | A quick full-file listing is enough |
rg -n | Matching lines | Only reports lines that match | Yes | Match locations matter more than surrounding context |
awk '{print NR, $0}' | Every input line | Numbers blank lines | Can filter with an expression | Numbering and field-aware transformation must happen together |
cat -n is the shortest substitute for a simple listing. awk is more programmable, but that flexibility makes the intent less obvious when the only requirement is line numbering. nl -ba communicates that requirement directly.
Vim is for an interactive editing session
Vim combines navigation, search, editing, buffers, undo history, and commands inside a terminal interface. Its documentation is split between a task-oriented user manual and a detailed reference manual, both designed around an interactive editing session.[3]
A human can jump directly to a line:
vim +100 src/App.java
Inside Vim, /pattern, :set number, quickfix lists, and project plugins can cover much of the same ground as external tools. That is useful when one person owns the session and can interpret the screen.
An automated process has different constraints:
- terminal dimensions affect what is visible;
- modes and cursor state must be tracked;
- key sequences depend on timing and configuration;
- an interrupted session may leave swap files or partial edits;
- the visible screen is harder to turn into structured evidence.
Vim remains a strong choice for human editing. It is simply the wrong interface for most unattended search-and-edit steps.
Vim compared with an IDE, sed, and patch editing
| Tool or method | Interaction model | Multi-file navigation | Change visibility | Prefer it when |
|---|---|---|---|---|
| Vim | Full-screen terminal session | Built in, with buffers and quickfix support | Reviewed after saving | A person wants keyboard-driven exploration and editing |
| IDE or graphical editor | Full-screen graphical session | Usually built in with language tooling | Reviewed after saving | Refactoring depends on language servers, debugging, or visual navigation |
sed -i | Non-interactive command | Operates on named files | The command shows the transformation, not the resulting context | A predictable textual substitution is intentionally broad |
| Patch editing | Non-interactive structured change | Can update several files | Removed, added, and surrounding lines are explicit | Automation needs a narrow edit that can be reviewed before verification |
Vim and an IDE optimize the human editing loop. sed -i and patch-based editing optimize repeatable execution. The distinction is interaction and reviewability, not editing power.
apply_patch describes the intended change
apply_patch is not a standard Linux utility. In Codex it is a patch-editing tool with explicit operations for adding, deleting, moving, and updating files. The current OpenAI implementation parses patch hunks and resolves their paths before applying file changes.[4]
A small update looks like this:
*** Begin Patch
*** Update File: src/config.ts
@@
-const timeout = 3000;
+const timeout = 5000;
*** End Patch
The old line provides context and the new line states the intended result. If the expected context no longer exists, the patch can fail instead of blindly changing an unrelated occurrence.
The format follows the same review principle as a unified diff: unchanged context identifies the location, while - and + identify removed and added lines.[5] Codex’s markers are its own patch grammar, so apply_patch should not be confused with the Unix patch utility or git apply.
After applying a change, Git remains the source of truth for review:
git diff -- src/config.ts
apply_patch performs the edit. It does not prove that the code builds, passes tests, or behaves correctly.
apply_patch compared with patch, git apply, and sed -i
| Tool | Input format | Context checking | Can check the Git index | Prefer it when |
|---|---|---|---|---|
apply_patch | Codex *** Begin Patch grammar | Yes | No | Codex must add, update, move, or delete files with explicit context |
patch | Conventional context or unified diff | Yes | No | A standard patch must be applied outside a Git-specific workflow |
git apply | Git-compatible patch | Yes | Yes, with --index | A conventional diff should update a working tree without creating a commit |
sed -i | Substitution expression | Pattern-based rather than hunk-based | No | The transformation is simple, mechanical, and safe across every match |
apply_patch is closest to patch and git apply, but its envelope and file-operation markers are Codex-specific. sed -i is shorter for a uniform substitution; it is riskier when the same text appears in unrelated contexts.
The tools form a sequence
A typical codebase task has four distinct steps.
1. Locate the relevant code
rg -n "DEFAULT_TIMEOUT|requestTimeout" src/
2. Read enough context
nl -ba src/config.ts | sed -n '35,80p'
3. Apply the smallest coherent edit
Use apply_patch with the surrounding function or configuration block as context.
4. Review and verify
git diff --check
npm test
The commands are intentionally separate. Search output explains why a file was selected. The numbered excerpt establishes the current state. The patch records the change. The diff and tests verify different properties afterward.
Common command pairings
The tools remain useful together because path selection, content search, context reading, and editing are separate operations:
# Find files by path, then search their contents.
find src -name '*.java' -print
rg "AccessDenied" -t java src
# Search logs from another command.
kubectl logs deployment/api | rg "ERROR|WARN"
# Read a known code range with line numbers.
nl -ba src/App.java | sed -n '80,130p'
Selection rules
Use rg when the question is “where does this text occur?”
Use nl when the question is “what exactly is at these lines?”
Use Vim when a person wants an interactive editing environment.
Use apply_patch when an automated change should state its scope and preserve reviewable context.
None of these tools verifies the final behavior. After the edit, inspect the diff and run the narrowest relevant formatter, type checker, build, or test. That separation keeps discovery, modification, and verification independently visible.
References
[1] Andrew Gallant. “ripgrep README.”
[2] GNU Project. “nl: Number lines and write files.”
[3] Vim Project. “Vim User Manual: About the manuals.”