5 releases
Uses new Rust 2024
| 0.4.2 | Jun 22, 2025 |
|---|---|
| 0.4.1 | Jun 22, 2025 |
| 0.4.0 | Jun 22, 2025 |
| 0.3.2 | Jun 21, 2025 |
| 0.3.1 | Jun 21, 2025 |
#25 in Text editors
164 downloads per month
490KB
14K
SLoC
vicut
vicut is a Vim-based, scriptable, headless text editor for the command line.
It combines the power of Vim motions/operators with the general-use applicability of command line tools like sed, awk, and cut. vicut can be used to extract fields, edit text files in-place, apply global substitutions, and more.
Why vicut?
I'm fluent with Vim and often find myself wishing I could use its expressive editing features outside the interactive editor — especially when writing shell scripts. Tools like awk, sed, and cut are powerful for formatting command output and extracting fields, but I’ve lost count of how many times I’ve thought:
"This would be way easier if I could just ask Vim to do it."
So I decided to repurpose my shell's line editor into a CLI tool that can process files and input streams using Vim commands.
Features
🔍 Core Editing
- Apply Vim-style editing commands (e.g.
:%s/foo/bar/g,d2W,ci}) to one or more files, or stdin. - Use Vim motions to extract or modify structured data.
- Perform in-place file edits with
-i, or print to stdout by default.
📦 Flexible Output
- Output in plain text, JSON (
--json), or interpolate fields using a format string (--template). - Chain multiple editing/extraction commands.
- Capture multiple fields using
-c, and structure them using-nand--delimiter.
⚡ High Performance
- Use
--linewisefor stream-style processing likesed, but multi-threaded. - Combine regex pattern matching with Vim-style editing in a single tool.
⚙️ Usage
vicut uses an internal text editing engine based on Vim. File names can be given as arguments, or text can be given using stdin. There are six command flags you can use to issue commands to the internal editor.
-c/--cut <VIM_CMD>executes a Vim command (something like5w,vi),:%s/foo/bar/g, etc) and returns the span of text covered by the cursor's motion as a field. Any arbitrary number of fields can be extracted using-c. If no-ccommands are given,vicutwill print the entire buffer as a single field.-m/--move <VIM_CMD>silently executes a Vim command.-mdoes not extract a field from the buffer like-cdoes, making it ideal for positioning the cursor before-ccalls, or making edits to the buffer.-r/--repeat <N> <R>repeatsNprevious commandsRtimes. Repeats can be logically nested.-n/--nextconcludes the current 'field group' and starts a new one. Each field group is printed as a separate record in the output, or as a separate JSON object if using--json-g/--global <PATTERN> <COMMANDS>allows for conditional execution of command flags. Any command flags following-gwill only execute on lines that match the pattern given after-g. Fallback commands can be given using the--elseflag. You can return from the-gscope with the--exitflag, which will allow you to continue writing unconditional commands. For the purpose of repetition with-r, the entire-gblock counts as a single command to be repeated.-v/--not-global <PATTERN> <COMMANDS>same behavior as-g, except it executes the contained command flags on lines that don't match the given pattern.
Command flags can be given any number of times, and the commands are executed in order of appearance.
Output Format Options
Output can be structured in three different ways using these options:
-j/--jsonemits the extracted field data as a json object, ready to be piped into other programs, such asjq-d/--delimiter <STR>lets you give a field separator as an argument to the flag. The separator is placed inbetween each field in each record.-t/--template <STR>lets you define a custom output format using a format string. Fields are interpolated on placeholders that look like{{1}}or{{field_name}}.
Execution Behavior Options
-iIf you have given files as arguments to read from, the-iflag will makevicutedit the contents of those files in-place. This is an atomic operation, meaning changes will only be written to the files if all operations succeed.--backupIf the-ioption has been set, this will create a backup of the files to be edited.--backup-extensionAllows you to set an arbitrary file extension to use for the backups. Default is.bak--keep-modeThe internal editor always returns to Normal mode after each call to-mor-c. This flag prevents that behavior, and causes the internal editor's mode to persist between calls.--linewiseMakesvicuttreat each line of text in the input as a separate buffer. The sequence of commands you give tovicutwill be applied to every line. This operation utilizes multi-threading to operate on lines in parallel, making it far faster than full buffer editing.--serialMakes--linewisemode operate on each line sequentially instead of using multi-threading.--jobsRestricts the number of threads--linewisecan create for operating on lines.--trim-fieldsTrims leading and trailing whitespace from fields extracted by-c.
ℹ️ Examples and in-depth usage ideas can be found on the wiki
🚀 Performance
While tools like awk and sed do beat vicut in speed for full-buffer processing, vicut's --linewise mode emulates the stream processing behaviors of awk and sed by treating each line of input as an independent buffer, and processing each in parallel. This allows vicut's performance to scale horizontally across CPU cores, giving it an edge over traditional Unix text processors for non-trivial inputs— even while executing more semantically rich operations like Vim motions.
On structured input, execution speed of vicut in --linewise mode is comparable to or faster than the speeds of sed and awk on datasets up to 1 million lines.
Here's a benchmark using a generated data set that looks like this:
00001) Provider-1 (City-1, State-1) [924.05 km]
00002) Provider-2 (City-2, State-2) [593.91 km]
00003) Provider-3 (City-3, State-3) [306.39 km]
00004) Provider-4 (City-4, State-4) [578.94 km]
00005) Provider-5 (City-5, State-5) [740.13 km]
...
With the target output being:
00001 ---- Provider-1 ---- City-1, State-1 ---- 924.05 km
00002 ---- Provider-2 ---- City-2, State-2 ---- 593.91 km
00003 ---- Provider-3 ---- City-3, State-3 ---- 306.39 km
00004 ---- Provider-4 ---- City-4, State-4 ---- 578.94 km
00005 ---- Provider-5 ---- City-5, State-5 ---- 740.13 km
...
25,000 lines
| Tool | Command | Wall-Clock Time |
|---|---|---|
sed |
sed -E -e 's/[][]//g' -e 's/(\) | \()/ ---- /g' |
20.0ms |
awk |
awk -vOFS=" --- " -F'[][()]' '{ print $1, $2, $3, " " $5 }' |
13.7ms |
vicut |
vicut --linewise --delimiter ' ---- ' -c 'e' -m '2w' -c 't(h' -c 'vi)' -c 'vi]' |
11.9ms |
100,000 lines
| Tool | Command | Wall-Clock Time |
|---|---|---|
sed |
sed -E -e 's/[][]//g' -e 's/(\) | \()/ ---- /g' |
76.0ms |
awk |
awk -vOFS=" ---- " -F'[][()]' '{ print $1, $2, $3, " " $5 }' |
51.6ms |
vicut |
vicut --linewise --delimiter ' ---- ' -c 'e' -m '2w' -c 't(h' -c 'vi)' -c 'vi]' |
35.2ms |
1,000,000 lines
| Tool | Command | Wall-Clock Time |
|---|---|---|
sed |
sed -E -e 's/[][]//g' -e 's/(\) | \()/ ---- /g' |
756.4ms |
awk |
awk -vOFS=" ---- " -F'[][()]' '{ print $1, $2, $3, " " $5 }' |
499.1ms |
vicut |
vicut --linewise --delimiter ' ---- ' -c 'e' -m '2w' -c 't(h' -c 'vi)' -c 'vi]' |
296.0ms |
Benchmark recorded using an AMD Ryzen 9 9950X (16-Core) running Arch Linux
The command used to generate the datasets was this, if you want to reproduce these benchmarks at home:
seq -w 1 1000000 | awk 'BEGIN { OFMT="%.2f" } { printf "%05d) Provider-%d (City-%d, State-%d) [%.2f km]\n", $1, $1, $1, $1, rand()*1000 }' > providers.txt
📦 Installation
Note: Building requires the Rust toolchain, which includes the
rustccompiler and thecargopackage manager.
Install via Cargo (Recommended)
If you have Rust installed, you can install vicut directly from crates.io using:
cargo install vicut
This will install the vicut binary to ~/.cargo/bin — make sure that directory is in your $PATH.
Build from Source
Alternatively, you can build vicut manually:
-
Clone the repository and navigate into it:
git clone https://github.com/km-clay/vicut cd vicut -
Build the binary:
cargo build --release -
Install it to a directory in your
$PATH:install -Dm755 target/release/vicut ~/.local/bin
Here's a one-liner for all that:
(git clone https://github.com/km-clay/vicut && \
cd vicut && \
cargo build --release && \
mkdir -p ~/.local/bin && \
install -Dm755 target/release/vicut ~/.local/bin && \
echo -e "\nInstalled the binary to ~/.local/bin — make sure that is in your \$PATH")
Notes
vicut is experimental and still in early development. The core functionality is stable and usable, but many of Vim's more obscure motions and operators are not yet supported. The logic for executing the Vim commands is entirely home-grown, so there may be some small inconsistencies between Vim and vicut. The internal editor logic is adapted from the line editor I wrote for fern, so some remnants of that may still appear in the codebase. Any and all contributions are welcome.
Dependencies
~8–11MB
~194K SLoC