Cleaning a CSV without corrupting the rest of the file

A CSV lands in your inbox and it needs a little work before it's usable. A dollar sign glued to every number in the cost column, a stray leading apostrophe, casing that can't make up its mind, a header sitting three rows down under a title block. None of it is hard to fix. You open the file to fix it, and the tool you opened it with quietly breaks something else.

Open it in Excel and the account numbers in the first column — the ones with leading zeros — come back as 41, 72, 108. The sixteen-digit part numbers arrive as 1.23E+15. The dates get reformatted to whatever the machine's locale prefers. You went in to strip a dollar sign and came out with a file whose identifiers no longer match the system they came from.

Reach for pandas instead and the same thing happens one layer down. read_csv infers a type for every column, to_csv writes the inferred version back, and the round trip reintroduces the exact damage — the dropped zeros, the scientific notation — whether or not your script touched those columns. The six lines you wrote to clean one field rewrote three others behind your back.

So I made a tool that edits the values you point it at and changes nothing else.

xled brings the muscle memory of sed and awk to CSV and DSV files. You address part of the table the way a spreadsheet does — a column by header name or letter, a span of rows, a rectangle, a set of cells matched by a regular expression — and give it a command. Here is an asset register with the damage built in: annual costs stored as text with dollar signs and thousands separators, and asset tags that are really identifiers, leading zeros and all.

asset_tag,system,annual_cost,renewed
0041,Payroll,"$84,500.00",2026-01-15
0072,CRM,"$12,000",2026-03-01
0108,Data Warehouse,"$156,000.00",2026-02-20

One substitution, scoped to the one column, clears the formatting:

$ xled '[annual_cost] s/[$,]//g' assets.csv
asset_tag,system,annual_cost,renewed
0041,Payroll,84500.00,2026-01-15
0072,CRM,12000,2026-03-01
0108,Data Warehouse,156000.00,2026-02-20

The costs are clean and every other cell is byte-for-byte what it was. The asset tags are still 0041, 0072, 0108 — not 41, 72, 108 — because xled never looked at them. Those tags are the keys that join this file to a contract system and a CMDB; the moment a tool renders them as integers, the join is broken, and nobody notices until a report comes back wrong.

xled keeps them because it has no type inference to get in the way. It treats every cell as text and coerces nothing on its own: you cast a value to a number explicitly, in the one place you're doing arithmetic, and everywhere else the bytes stand. That single decision is the difference between a tool that cleans a file and one that cleans a field while damaging the file.

The addressing transfers from a spreadsheet without a manual. A column is [annual_cost] by name or C by letter; 2:4 is a span of rows; B2:C3 is a rectangle; /Payroll/ selects the cells that match. The commands are sed's and awk's, unchanged where they can be: s/re/rep/ with backreferences and case folds, and a small compute layer for a derived column, so [total] = num([annual_cost]) * 3 writes a three-year figure without leaving the file. There is no control flow and no query engine, by design; that restraint is what keeps the tool small enough to hold in your head.

Run it against a file and the result goes to stdout, ready to pipe or redirect, with the original untouched until you decide otherwise. Open the file with no script and you get an interactive prompt that previews each edit before it commits, keeps an undo stack, and writes only when you say so. You see what a command will do to the data before the data changes.

xled edits values, and that is all it does. It is one of four small tools I reach for on tabular data, each with one job and a shared dialect — the same column letters, header names, and delimiter flags across all of them, so what you learn on one carries to the rest. Before I touch an unfamiliar file I profile it with xray, which reads it without changing a byte and names what will bite: a header buried under a title block, a column of numbers that is secretly text, a duplicate key. When the problem is the shape of the table rather than its values — one column per year that needs to be one row per year, a single cell holding three tags that should be three rows — that is xshape. And when I need to ask the data a question instead of changing it, to filter, group, join, or count, that is xql, which runs SQL against a CSV or, with the same grammar, against a live SharePoint list. Profile, reshape, edit, query: four verbs over one way of naming the parts of a table.

xled is a single binary, open source under the MIT license, written in pure Rust. The install steps — one line for cargo, Homebrew, or WinGet — and a tutorial that maps each sed and awk habit to its xled form are on the xled page. If you already know sed and awk, it is a short read.

I built xled because a CSV is data, not a spreadsheet, and the tools built to clean them keep treating the two as the same thing. Type inference is a convenience that quietly corrupts the one class of column that matters most, the identifiers, and it does it in the files you were trying to fix. A tool that edits tabular data should change what you name and leave the rest as it found it.

Getting tabular data cleanly out of one system and into another, without corrupting the identifiers that tie the two together, is a quiet and load-bearing part of the migration and portfolio work I do for clients. A spreadsheet is often the interchange format between two systems that have to agree, and the handoff is where the agreement breaks. If that is the kind of seam your team keeps getting caught in, that is exactly the kind of work I like being brought in on.

Get in touch See xled →