TSV#

Tab-Separated Values, the underrated cousin of CSV. Same idea (rows separated by newlines, fields separated by a delimiter) with one practical difference; the delimiter is the TAB character (\t), which almost never appears inside real data.

In practice, you can usually parse TSV with split('\t'). CSV’s quoting machinery becomes optional.

The Format#

The format is exactly as it sounds, tab between fields, newline between records, no quoting machinery. The simplicity is the whole point; CSV’s RFC 4180 specification exists because comma is too common in real text, and the same problem just doesn’t apply to tab.

id\tname\temail
1\tAda\tada@example.com
2\tAlan\talan@example.com
3\tGrace\tgrace@example.com

(Tabs shown as \t here for visibility; in a real file they’re literal 0x09 bytes.)

The Spec That Almost Exists#

There is no RFC for TSV the way there is for CSV. “Everyone agrees on tab-as-delimiter, newline-as-record, no quoting” is the working consensus, with two community variants where quoting actually matters. The closest spec references.

  • IANA registration text/tab-separated-values (1993, IETF draft), a brief description, never finalized.

  • POSIX ``tabs(1)``, only describes terminal tab stops.

  • Practice, everyone agrees on tab-as-delimiter, newline-as- record, no quoting (or RFC-4180-style quoting if you must).

Two community variants worth naming.

  • Simple TSV, no escaping at all. Fields containing tabs or newlines are illegal; if you have such data, you have a different problem.

  • IANA / “extended” TSV, escapes \t, \n, \r, \\ with backslash sequences. Less common in practice.

Why TSV Is Often Better Than CSV#

The TSV-vs-CSV argument from a technical perspective. CSV dominates because spreadsheet defaults made it the interoperable choice; TSV wins on every other axis when you control both ends of the pipeline.

  • No quoting in 99% of cases, text data rarely contains tabs.

  • Trivial to parse, line.split('\t') works.

  • Trivial to generate, printf "%s\t%s\t%s\n".

  • Streaming-friendly, line-oriented; one record per line, period.

  • Robust to commas, text with embedded commas Just Works.

  • Awk-friendly, awk -F'\t' '{ print $2 }'.

  • Readable, aligned by terminal tab stops; column -t -s$'\t' gives a pretty-printed view.

If you control both producer and consumer, TSV is usually the better choice. The reason CSV dominates is interoperability with spreadsheet software defaults, not technical merit.

The Tab Problem#

The 1% of cases where tabs do appear, in code samples, pasted spreadsheet values, certain user-generated text. Two responses, both legitimate; pick one and document it for the dataset.

  • Code samples (indentation).

  • Pasted-in spreadsheet values.

  • Some user-generated text fields.

Two responses.

  1. Refuse them at validation time, “fields may not contain TAB or LF”. Cleaner; turns a content problem into a data problem.

  2. Escape with backslashes, \t for tab, \n for newline, \\ for backslash. Compatible with the IANA-style draft.

Either is fine; document which one applies to your dataset.

TSV in the Wild#

Where TSV is genuinely the standard rather than the underdog. Bioinformatics, large research datasets, Hadoop / Hive, and every Linux text-processing tool default to tab as the field delimiter; TSV is the native format of the Unix pipe.

  • Bioinformatics, TSV is the dominant tabular format (BED, VCF-as-TSV, BLAST output, GTF).

  • Web search / NLP datasets, many corpora distribute as TSV.

  • NSF, NIH, statistical-bureau bulk data, TSV is common.

  • Hadoop / Hive, the default text input format is \t-separated.

  • Database exports, mysqldump --tab, PostgreSQL’s COPY ... TO STDOUT (TAB by default), SQLite’s .mode tabs.

  • Linux pipe culture, tools like sort, cut, join, paste all default to tab.

CLI Tools#

Everything that handles CSV usually handles TSV with a flag. The Linux text utilities (awk, cut, sort, join) default to tab; csvkit / xsv / mlr / DuckDB all accept a delimiter override; column -t makes TSV display correctly in the terminal.

$ awk -F'\t' '{ print $1, $3 }' input.tsv

$ cut -f 1,3 input.tsv
$ sort -t $'\t' -k 2 input.tsv
$ join -t $'\t' a.tsv b.tsv

$ csvcut -t -c 1,3 input.tsv
$ xsv select -d $'\t' 1,3 input.tsv

$ mlr --tsv cat input.tsv
$ mlr --t2c cat input.tsv

$ duckdb -c "SELECT * FROM read_csv('input.tsv', delim='\t', header=true);"

$ column -t -s $'\t' input.tsv | less -S

Per-Language Bindings#

Almost identical to CSV; the same library, just pass \t as the delimiter. Most languages’ CSV libraries were designed with this in mind from the start; TSV gets treated as “CSV with a different separator”:

# Python
import csv
with open("data.tsv") as f:
    reader = csv.reader(f, delimiter='\t')
    header = next(reader)
    for row in reader:
        ...

# pandas
import pandas as pd
df = pd.read_csv("data.tsv", sep='\t')

# polars
import polars as pl
df = pl.read_csv("data.tsv", separator='\t')
import "encoding/csv"

r := csv.NewReader(file)
r.Comma = '\t'
let mut rdr = csv::ReaderBuilder::new()
    .delimiter(b'\t')
    .from_path("data.tsv")?;

Other Delimited Variants#

Other field separators show up too. PSV (pipe), SCSV (semicolon, used by European Excel locales), and ASV (ASCII separator characters) are the alternatives that come up in legacy and niche pipelines. ASV is the RFC-purist’s choice but rarely appears in practice.

  • PSV, pipe-separated (|). Popular in finance and logging.

  • SCSV, semicolon-separated; default in European Excel locales.

  • ASV, ASCII-separated. Uses field separator (``x1f``) and record separator (``x1e``), ASCII characters that exist precisely for this. Quoting genuinely never needed. Rarely used in practice but the RFC-purist’s choice.

If you find yourself reaching for ASV, you’ve earned the choice.

When to Pick TSV Over CSV#

The five conditions where TSV is the right tabular format. Pick CSV instead when the consumer is a spreadsheet or a system you don’t control; otherwise, TSV is usually better.

  • You control both ends of the pipeline.

  • Data is tabular and free of tabs (it usually is).

  • Linux pipe tools are part of the workflow.

  • Performance matters, TSV parses faster than CSV at the byte level (no quoting state machine).

  • Bioinformatics / scientific data, it’s the standard there.

Pick CSV when the consumer is a spreadsheet or a system you don’t control.

Pitfalls#

The bugs every TSV pipeline runs into. Most are copy-paste-induced (spaces masquerading as tabs), trailing- whitespace surprises, or “the user saved as CSV but called it TSV”, diagnosable with cat -A and a careful eye:

  • Spaces vs. tabs, copy-pasted “TSV” sometimes uses runs of spaces. cat -A reveals the truth.

  • Trailing tabs create empty trailing fields silently.

  • Inconsistent column counts between rows, as in CSV, your parser may or may not complain.

  • Tabs inside fields, decide your policy (reject vs. escape) and document it.

  • Excel, exports CSV by default, not TSV. Saving as “.txt (Tab delimited)” works but the user has to know to do it.

Workflow#

Extract, parse, filter, save. mlr (Miller) and awk are the standard CLI tools; csvkit works with --tabs.

Extract and inspect.

$ mlr --tsv head -n 5 data.tsv
$ mlr --tsv stats1 -a min,max,mean -f age data.tsv

Parse and filter.

# rows where field 3 == active
$ awk -F'\t' 'NR==1 || $3=="active"' data.tsv

# Miller: predicate + column projection
$ mlr --tsv filter '$status == "active"' then cut -f name,email data.tsv

# convert to CSV in the same pipeline
$ mlr --t2c filter '$age > 30' data.tsv

Save.

$ awk -F'\t' 'NR==1 || $3=="active"' data.tsv > active.tsv
$ mlr --tsv filter '$age > 30' then cut -f name,email data.tsv > over30.tsv

See Also#

  • CSV, the comma cousin and most CSV pitfalls apply here too.

  • Parquet, the columnar replacement when scale matters.

  • JSON, when nested data shows up.