結論

TSV(Tab-Separated Values)とは、表のデータを「値と値の間をタブ文字で区切る」決まりで書いたテキストファイルです。CSV との違いは区切り文字(タブかカンマか)だけに見えますが、実際には「値の中に区切り文字・改行・引用符が入ったときの扱い」が違い、ここで読み書きの結果が分かれます。

この記事でわかること

  • TSV と CSV の違いを、同じ名簿のバイト列・コマンドの結果で比べた一覧
  • タブ・改行・引用符・BOM・Shift_JIS で TSV が崩れる実物と、直し方
  • cut・awk・Python・pandas・SQLite・LibreOffice での読み書きと変換コマンド

2026年10月29日更新|Python 3.13・pandas 3.0・SQLite 3.45・LibreOffice 24.2・GNU coreutils 9.4 で実行確認

TSV は、データベースの書き出し、ログ、機械学習のデータセット、コマンドの出力などでよく使われる形式です。拡張子は .tsv が一般的ですが、.txt や .tab で配られることもあります。中身はただの文字なので、メモ帳などのテキストエディタで開けます。

flowchart TD
    A[表のデータ] --> B{値にカンマが<br>多い?}
    B -->|少ない| D[CSV]
    B -->|多い| C{値にタブ・<br>改行がある?}
    C -->|ない| E[TSV]
    C -->|ある| F[CSV か<br>エスケープ<br>した TSV]

よく調べられることの答えを先にまとめます。

知りたいこと 答え 詳しい節
TSVとは 値をタブ文字で区切ったテキスト形式。1行が1件、1行目は項目名が多い TSVとは(意味とファイルの中身)
TSV と CSV の違い 区切りがタブかカンマか。CSV は値を引用符で囲む決まりがあり、TSV の元の定義は値にタブを入れない TSVとCSVの違い(一覧表)
TSV の読み方 ティーエスブイ。Tab-Separated Values の頭文字 TSVとは(意味とファイルの中身)
TSV を開く方法 テキストエディタで開ける。列をそろえて見るなら column -t -s $'\t' TSVをコマンドで読む
TSV が崩れる・列がずれる 値の中のタブ・改行、行頭の引用符、CRLF のどれかが原因のことが多い TSVが崩れる場面
TSV を CSV に変換する tr や sed の置換ではなく、Python の csv モジュールか LibreOffice の変換で TSVとCSVを変換する方法
TSV が文字化けする UTF-8・BOM付き・Shift_JIS(cp932)のどれかを確かめ、読む側で指定する TSVの文字コード
TSV と CSV のどちらを使う 受け取る相手のソフトと、値にカンマ・タブ・改行が入るかで決める TSVとCSVのどちらを使うか

確認環境:Ubuntu 24.04.5 LTS の作業用フォルダーで、自作の会員名簿(先頭が0の番号・漢字の名前・カンマ入りの住所・引用符入りの名前・改行入りのメモ・タブ入りのメモ)を、TSV・CSV・引用符なしの TSV・エスケープした TSV・BOM付き UTF-8・Shift_JIS(cp932)の各ファイルに書き出し、読み書きを比べました。使ったのは Python 3.13.18 の csv モジュール、pandas 3.0.5、GNU coreutils 9.4 の cut・tr・expand、mawk 1.3.4、util-linux 2.39.3 の column と hexdump、sqlite3 3.45.1(Ubuntu の .deb を作業用フォルダーに展開して使用)、LibreOffice 24.2.7.2 のヘッドレス変換、iconv(glibc 2.39)、nkf 2.1.5、file 5.45 です。速さと大きさは20万行の TSV・CSV を作り、5回の中央値を測りました。TSV の定義は IANA のメディアタイプ登録「text/tab-separated-values」、CSV の定義は RFC 4180、各ツールの仕様は Python・pandas・SQLite・LibreOffice の公式ドキュメントで確かめています。Excel・表計算ソフトの画面の操作は扱っていません。図は mermaid 11.16.0 で描画を確かめています(確認日 2026年10月29日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「TSVとは|CSVとの違いと、タブ・改行・文字コードで崩れる例」

TSVとは(意味とファイルの中身)

この節では、TSV という言葉の意味と、ファイルの中身が実際にどうなっているかが分かります。

TSV は Tab-Separated Values(タブで区切られた値)の頭文字で、「ティーエスブイ」と読みます。日本語では「タブ区切りテキスト」「タブ区切り形式」とも呼ばれます。表計算の表やデータベースの表を、次の決まりで文字だけのファイルにしたものです。

  • 1行が1件(1レコード)
  • 1件の中の値(フィールド)は、タブ文字で区切る
  • 1行目は項目名(ヘッダー)にすることが多い

タブ文字はキーボードの Tab キーで入る見えない文字で、文字コードでは16進数の 09 です。画面では空白に見えるため、スペースと区別がつきません。cat -T を使うと、タブを ^I と表示して見分けられます。

$ cat -T clean.tsv
id^Iname^Iaddress^Imemo
0012^I山田 太郎^I東京都千代田区1-1, 2F^Iよろしく
0034^I鈴木 花子^I大阪府大阪市^I「至急」と言われた
0056^I佐藤 "ジロー"^I福岡県福岡市^I1行目 2行目
0078^I髙橋 一郎^I北海道札幌市^IA B

名前の「山田 太郎」の間はスペース、^I の位置がタブです。住所のカンマも、名前の中の引用符も、TSV では普通の文字として書かれています。

「TSV」が別の意味で使われる場面

TSV という3文字は、分野によって別のものを指します。この記事で扱うのはデータ形式の TSV です。

場面 TSV の意味 見分け方
データ・プログラミング Tab-Separated Values。タブ区切りのテキスト形式 拡張子 .tsv、「CSV」「タブ区切り」と一緒に出てくる
半導体 Through-Silicon Via。シリコンを貫通する電極 「積層」「チップ」「パッケージ」と一緒に出てくる
通販番組 その日の特別価格の商品を指す呼び名 商品名・価格と一緒に出てくる

最初の1分で TSV を作って読む

Linux・Mac のターミナルや Git Bash なら、次の3行で TSV を作り、2列目を合計できます。printf の \t がタブ、\n が改行です。

printf 'name\tscore\nA\t90\nB\t85\n' > first.tsv
cat -T first.tsv
awk -F'\t' 'NR>1{s+=$2} END{print "合計", s}' first.tsv
name^Iscore
A^I90
B^I85
合計 175

-F'\t' は「タブで区切る」という指定です。TSV が扱いやすいと言われるのは、このように「区切り文字で分けるだけ」で列が取り出せるためです。

TSVとCSVの違い(一覧表)

この節では、TSV と CSV の違いを、決まり(仕様)と実際の動きの両方から一覧で比べられます。

違いは区切り文字だけではありません。CSV には「区切り文字・改行・引用符を含む値は引用符で囲む」決まりがあり、TSV の元の定義には「値の中にタブを入れない」決まりがあります。この差が、読み書きのしやすさと崩れやすさを分けます。

項目 TSV CSV
正式な名前 Tab-Separated Values Comma-Separated Values
区切り文字 タブ(16進数 09) カンマ(16進数 2C)
拠り所になる文書 IANA のメディアタイプ登録 text/tab-separated-values RFC 4180(情報提供の文書で、標準ではない)
メディアタイプ(MIME) text/tab-separated-values text/csv
値に区切り文字が入るとき 元の定義では入れてはいけない 値をダブルクォートで囲む
値に改行が入るとき 元の定義では1件は1行。入れられない 値をダブルクォートで囲む
値に引用符が入るとき 普通の文字として書ける(ツールによっては注意) 引用符を2つ重ねて書く("" とする)
住所・金額のカンマ そのまま書ける 値を引用符で囲む必要がある
cut・awk で列を取り出す 区切るだけで正しく取れる 引用符の中のカンマで列がずれる
file コマンドの判定(5.45) Unicode text, UTF-8 text CSV Unicode text, UTF-8 text
対応しているソフト 多いが、CSV より少ない ほとんどのソフト・サービスが対応

IANA の登録には、次のように書かれています。

Fields in a record are separated from each other by a tab character. … Note that fields that contain tabs are not allowable in this encoding.

(1件の中のフィールドはタブ文字で互いに区切られる。…タブを含むフィールドは、この形式では許されない。)同じ登録では、1行目が項目名であることも定めています。一方 RFC 4180 は、CSV について「改行・ダブルクォート・カンマを含むフィールドはダブルクォートで囲む」「囲んだ中のダブルクォートは、もう1つのダブルクォートを前に付けて表す」と書いています。

TSV の考え方

区切り文字(タブ)を値に入れない前提

引用符の決まりが要らず、区切るだけで読める

値にタブ・改行が来ると、元の定義では書けない

CSV の考え方

区切り文字(カンマ)が値に入る前提

引用符で囲めば、カンマ・改行・引用符も書ける

読む側は引用符の決まりを理解する必要がある

実際には、多くのツールが TSV にも CSV と同じ引用符の決まりを当てはめて読み書きします(後の節で Python・pandas・SQLite・LibreOffice の動きを見ます)。「TSV なら引用符は出てこない」とは限らないことが、つまずきの元になります。

CSV・JSON・XML などデータ形式全体の比べ方は、関連記事「CSV・JSON・XML・YAML・TOMLの違いと使い分け|同じデータで比較」で扱っています。

同じデータをTSVとCSVで書くと(バイトで比べる)

この節では、同じ名簿を TSV と CSV で書いたときに、ファイルの中で何が違うのかがバイト単位で分かります。

値にカンマや引用符が無い行は、区切りの1バイト(09 か 2C)が違うだけです。1行目の項目名を16進数で見ると、次のようになりました。

$ head -1 clean.tsv | hexdump -C
00000000  69 64 09 6e 61 6d 65 09  61 64 64 72 65 73 73 09  |id.name.address.|
00000010  6d 65 6d 6f 0a                                    |memo.|
$ head -1 clean.csv | hexdump -C
00000000  69 64 2c 6e 61 6d 65 2c  61 64 64 72 65 73 73 2c  |id,name,address,|
00000010  6d 65 6d 6f 0a                                    |memo.|

違いが出るのは、値の中にカンマや引用符がある行です。同じ内容を CSV で書くと、住所と名前が引用符で囲まれました。

$ cat clean.csv
id,name,address,memo
0012,山田 太郎,"東京都千代田区1-1, 2F",よろしく
0034,鈴木 花子,大阪府大阪市,「至急」と言われた
0056,"佐藤 ""ジロー""",福岡県福岡市,1行目 2行目
0078,髙橋 一郎,北海道札幌市,A B
値 TSV での書き方 CSV での書き方
東京都千代田区1-1, 2F そのまま "東京都千代田区1-1, 2F"
佐藤 "ジロー" そのまま "佐藤 ""ジロー"""
0012(先頭が0) そのまま(読む側が数値にすると 12 になる) そのまま(同じく読む側次第)
ファイルの大きさ(5行) 249バイト 255バイト(引用符の6バイト分大きい)

TSV のほうが人の目で読みやすく、ファイルも少し小さくなります。ただしこれは「値にタブも改行も無い」ときの話です。

TSVが崩れる場面(タブ・改行・引用符)

この節では、TSV の列や行がずれる3つの原因を、実際に崩した結果で確かめられます。

TSV が崩れる原因は、ほとんどが「値の中のタブ」「値の中の改行」「値の先頭の引用符」です。名簿のメモ欄に 1行目(改行)2行目 と A(タブ)B を入れ、何も工夫せずにタブでつないで書き出した naive.tsv を作りました。

$ cat -T naive.tsv
id^Iname^Iaddress^Imemo
0012^I山田 太郎^I東京都千代田区1-1, 2F^Iよろしく
0034^I鈴木 花子^I大阪府大阪市^I「至急」と言われた
0056^I佐藤 "ジロー"^I福岡県福岡市^I1行目
2行目
0078^I髙橋 一郎^I北海道札幌市^IA^IB

これを awk で1行ずつ読み、列の数を数えると次のようになりました。

$ awk -F'\t' '{print NR": "NF"列 memo=["$4"]"}' naive.tsv
1: 4列 memo=[memo]
2: 4列 memo=[よろしく]
3: 4列 memo=[「至急」と言われた]
4: 4列 memo=[1行目]
5: 1列 memo=[]
6: 5列 memo=[A]

1値の中の改行で、1件が2行に割れる

4件の名簿が6行になり、5行目は「2行目」だけの1列の行になった。wc -l の行数と件数が合わなくなる。

2値の中のタブで、列が1つ増える

メモの「A(タブ)B」が2つの列に割れ、5列の行ができた。メモは「A」だけになり、「B」は存在しない5列目に入る。

3値の先頭の引用符で、後ろの行を飲み込む

CSV の決まりで読むツールは、値の先頭の " を「囲みの始まり」とみなす。閉じる引用符が無いと、ファイルの最後まで1つの値になる。

値の先頭が引用符のとき

3つ目は気づきにくい崩れ方です。メモが "重要" 要確認 の行と、閉じ忘れた "重要 の行で、Python の csv モジュールと pandas の動きを比べました。

=== quote.tsv(メモが "重要" 要確認)
csv既定    [['id', 'name', 'memo'], ['0012', '山田', '重要 要確認'], ['0034', '鈴木', '通常']]
QUOTE_NONE [['id', 'name', 'memo'], ['0012', '山田', '"重要" 要確認'], ['0034', '鈴木', '通常']]
=== quote2.tsv(メモが "重要 で閉じていない)
csv既定    [['id', 'name', 'memo'], ['0012', '山田', '重要\n0034\t鈴木\t通常\n']]
QUOTE_NONE [['id', 'name', 'memo'], ['0012', '山田', '"重要'], ['0034', '鈴木', '通常']]

既定の設定では、1つ目は引用符が消え、2つ目は次の行(鈴木さんの行)までがメモの値に飲み込まれました。pandas の read_csv(sep="\t") では、2つ目のファイルは次のエラーで止まりました。

pandas.errors.ParserError: Error tokenizing data. C error: EOF inside string starting at row 1

引用符を使わない TSV を読むときは、「引用符を特別扱いしない」設定(quoting=csv.QUOTE_NONE)を付けます。Python の公式ドキュメントは QUOTE_NONE を「reader に、引用符の特別な処理をしないよう指示する」と説明しています。

崩さないための3つの書き方

書き方 中身の例(メモ欄) 向いている場面 注意
書き出す前にタブ・改行を空白に置き換える 1行目 2行目 メモの改行が要らない集計用 元に戻せない
CSV と同じ引用符で囲む "1行目(改行)2行目" Python・pandas・SQLite・LibreOffice で読むとき cut・awk では崩れる
\t・\n の形にエスケープする 1行目\n2行目 cut・awk でも1行1件を守りたいとき 読む側で元に戻す処理が要る

3つ目の書き方は、PostgreSQL の COPY のテキスト形式などでも使われている考え方です(PostgreSQL の公式ドキュメントでは、バックスラッシュに続く t・n をタブ・改行として扱うと説明されています)。Python で書くと次のとおりで、書き出して読み戻した結果は元のデータと一致しました。

def esc(s):
    return s.replace("\\", "\\\\").replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r")

def unesc(s):
    m = {"t": "\t", "n": "\n", "r": "\r", "\\": "\\"}
    out, i = [], 0
    while i < len(s):
        if s[i] == "\\" and i + 1 < len(s):
            out.append(m.get(s[i + 1], s[i + 1])); i += 2
        else:
            out.append(s[i]); i += 1
    return "".join(out)
$ cat -T escaped.tsv
id^Iname^Iaddress^Imemo
0012^I山田 太郎^I東京都千代田区1-1, 2F^Iよろしく
0034^I鈴木 花子^I大阪府大阪市^I「至急」と言われた
0056^I佐藤 "ジロー"^I福岡県福岡市^I1行目\n2行目
0078^I髙橋 一郎^I北海道札幌市^IA\tB
往復で一致: True
$ awk -F'\t' '{print NR": "NF"列"}' escaped.tsv
1: 4列
2: 4列
3: 4列
4: 4列
5: 4列

TSVをコマンドで読む(cut・awk・column)

この節では、Linux・Mac のコマンドで TSV の列を取り出す・見やすく表示する方法と、CSV で同じことをしたときとの違いが分かります。

cut の区切り文字は、何も指定しないとタブです。そのため TSV は cut -f3 だけで3列目が取れます。CSV で同じことをすると、引用符の中のカンマで列がずれました。

$ cut -f3 clean.tsv
address
東京都千代田区1-1, 2F
大阪府大阪市
福岡県福岡市
北海道札幌市
$ cut -d, -f3 clean.csv
address
"東京都千代田区1-1
大阪府大阪市
福岡県福岡市
北海道札幌市

列をそろえて表のように見るときは column を使います。-s $'\t' で区切りをタブだけにすると、名前の中のスペースで割れません。

$ column -t -s $'\t' clean.tsv
id    name           address                memo
0012  山田 太郎      東京都千代田区1-1, 2F  よろしく
0034  鈴木 花子      大阪府大阪市           「至急」と言われた
0056  佐藤 "ジロー"  福岡県福岡市           1行目 2行目
0078  髙橋 一郎      北海道札幌市           A B
やりたいこと TSV のコマンド メモ
3列目だけ取り出す cut -f3 file.tsv cut の既定の区切りはタブ
1列目と3列目を取り出す cut -f1,3 file.tsv 出力もタブ区切り
4列目を合計する awk -F'\t' 'NR>1{s+=$4} END{print s}' NR>1 で項目名の行を飛ばす
条件に合う行だけ awk -F'\t' '$3=="大阪府大阪市"' CRLF のファイルは最後の列の比較が外れる
列の数がそろっているか awk -F'\t' '{print NF}' file.tsv | sort | uniq -c 数が2種類以上なら崩れている
表のように見る column -t -s $'\t' file.tsv 大きいファイルは head と組み合わせる
出力をタブ区切りにする awk 'BEGIN{OFS="\t"} …' OFS は出力の区切り

cut・tr・wc の詳しい使い方は、関連記事「Linux cut・tr・wcの使い方|列の抜き出しと行数カウント早見表」で扱っています。

引用符で囲んだ TSV は cut で読めない

Python の excel-tab 方言で書き出した TSV(値に改行・タブがあると引用符で囲む)を cut で読むと、次のように壊れました。cut も awk も引用符の決まりを知らないためです。

$ cat -T sample.tsv
id^Iname^Iaddress^Imemo
0012^I山田 太郎^I東京都千代田区1-1, 2F^Iよろしく
0034^I鈴木 花子^I大阪府大阪市^I「至急」と言われた
0056^I"佐藤 ""ジロー"""^I福岡県福岡市^I"1行目
2行目"
0078^I髙橋 一郎^I北海道札幌市^I"A^IB"
$ cut -f4 sample.tsv
memo
よろしく
「至急」と言われた
"1行目
2行目"
"A

引用符で囲んだ TSV は、引用符の決まりが分かるツール(次の節の Python・pandas、SQLite、LibreOffice)で読みます。cut・awk で読むなら、書き出す側でエスケープするか、タブ・改行を値から取り除きます。

タブがスペースに変わっていないか

端末の画面や Web ページからコピーした TSV は、タブがスペースに置き換わっていることがあります。expand でタブをスペースに変えたファイルを作って比べると、見た目はほぼ同じなのに、タブの行数が0になりました。

$ head -2 spaced.tsv
id      name    address memo
0012    山田 太郎   東京都千代田区1-1, 2F    よろしく
$ grep -c $'\t' spaced.tsv clean.tsv
spaced.tsv:0
clean.tsv:5

この状態だと cut -f3 は行全体をそのまま返します。列が取れないときは、まず cat -T か grep -c $'\t' でタブが残っているかを見ます。

PythonでTSVを読み書きする(csvモジュール・pandas)

この節では、Python の標準の csv モジュールと pandas で TSV を読み書きする書き方と、それぞれの既定の動きの違いが分かります。

Python では、CSV と同じ csv モジュールに delimiter="\t" を渡すか、dialect="excel-tab" を指定すると TSV を扱えます。ファイルは newline="" で開きます。公式ドキュメントは、これを指定しないと「引用符で囲んだ中の改行が正しく解釈されない」と注意しています。

import csv

# 読む
with open("sample.tsv", newline="", encoding="utf-8") as f:
    for row in csv.reader(f, dialect="excel-tab"):
        print(len(row), row)

# 書く(行末を LF にする)
rows = [["id", "name"], ["0012", "山田 太郎"]]
with open("out.tsv", "w", newline="", encoding="utf-8") as f:
    csv.writer(f, delimiter="\t", lineterminator="\n").writerows(rows)
4 ['id', 'name', 'address', 'memo']
4 ['0012', '山田 太郎', '東京都千代田区1-1, 2F', 'よろしく']
4 ['0034', '鈴木 花子', '大阪府大阪市', '「至急」と言われた']
4 ['0056', '佐藤 "ジロー"', '福岡県福岡市', '1行目\n2行目']
4 ['0078', '髙橋 一郎', '北海道札幌市', 'A\tB']

引用符で囲んだ値も、中の改行・タブも元どおりに読めました。注意したいのは、excel-tab で書き出すと行末が CRLF(0d 0a)になることです。csv モジュールの lineterminator の既定は '\r\n' です。

$ head -1 sample.tsv | hexdump -C
00000000  69 64 09 6e 61 6d 65 09  61 64 64 72 65 73 73 09  |id.name.address.|
00000010  6d 65 6d 6f 0d 0a                                 |memo..|

この CRLF のファイルで「4列目が よろしく の行」を awk で探すと0件で、tr -d '\r' で CR を取ると1件見つかりました。最後の列に見えない \r が付いているためです。Linux のツールに渡すファイルは lineterminator="\n" で書き出します。

$ awk -F'\t' '$4=="よろしく"' sample.tsv | wc -l
0
$ tr -d '\r' < sample.tsv | awk -F'\t' '$4=="よろしく"' | wc -l
1

pandas で TSV を読む

pandas では read_csv(sep="\t") か read_table() を使います。既定のままだと、先頭が0の番号が数値になって 0 が消えました。

>>> pd.read_csv("quote.tsv", sep="\t")
   id name    memo
0  12   山田  重要 要確認
1  34   鈴木      通常
>>> pd.read_csv("quote.tsv", sep="\t", quoting=csv.QUOTE_NONE, dtype=str)
     id name      memo
0  0012   山田  "重要" 要確認
1  0034   鈴木        通常
指定 何が変わるか 手元の結果
sep="\t" 区切りをタブにする(read_table は最初からタブ) 4行×4列で読めた
dtype=str 全部の列を文字列で読む 0012 が 12 にならない
quoting=csv.QUOTE_NONE 引用符を普通の文字として扱う "重要" の引用符が残る
keep_default_na=False 空欄を NaN にせず空の文字列にする 空欄が '' になった
sep=None, engine="python" 区切り文字を自動で判定する タブと判定された
encoding="cp932" Shift_JIS(Windows の拡張文字を含む)で読む 「髙」も読めた

空欄は、既定では NaN(欠損値)になります。末尾が空の行を含む TSV で確かめました。

     id name memo
0  0012   山田  NaN
1  0034  NaN  NaN
[{'id': '0012', 'name': '山田', 'memo': ''}, {'id': '0034', 'name': '', 'memo': ''}]

2つ目の行が keep_default_na=False を付けた結果です。pandas の公式ドキュメントは、空の文字列・"NaN"・"N/A"・"NULL" などを既定で欠損値として扱うと説明しています。「N/A」という文字そのものを残したい列では、この指定が要ります。

pandas で TSV に書き出す

to_csv(sep="\t", index=False) で TSV になります。値にタブ・改行・引用符があると、pandas は CSV と同じ決まりで引用符を付けました(行末は LF)。

id\tname\taddress\tmemo\n
0012\t山田 太郎\t東京都千代田区1-1, 2F\tよろしく\n
0034\t鈴木 花子\t大阪府大阪市\t「至急」と言われた\n
0056\t"佐藤 ""ジロー"""\t福岡県福岡市\t"1行目\n2行目"\n
0078\t髙橋 一郎\t北海道札幌市\t"A\tB"\n

(見やすくするため、repr() の結果を行ごとに折り返しています。)cut・awk に渡す予定なら、書き出す前に df.replace({"\t": " ", "\n": " "}, regex=True) などで値の中のタブ・改行を空白に置き換えるか、前の節の esc() を df.map(esc) で全部の値に当ててから書き出します(手元では全行が4列になりました。ただし引用符入りの名前は pandas が "佐藤 ""ジロー""" と囲んだので、引用符も素のまま残したいときは csv モジュールでタブをつなぐ書き方にします)。quoting=csv.QUOTE_NONE, escapechar="\\" を指定する方法は、手元では A\(タブ)B のようにバックスラッシュの後ろに本物のタブ・改行が残り、awk では列がずれたままでした。

CSV の読み書きのテンプレートと文字化けの直し方は、関連記事「PythonでCSVを読み書き|コピペ用テンプレートと文字化け対策」で扱っています。TSV では、そこに書かれた書き方に delimiter="\t"(pandas なら sep="\t")を足すだけです。

SQLiteでTSVを取り込む・書き出す

この節では、SQLite のコマンドラインツールで TSV を表に取り込む方法と、書き出すときに出力の形が設定で変わることが分かります。

sqlite3 では .mode tabs にしてから .import すると、TSV を表に取り込めます。表が無ければ、1行目の項目名で表が作られ、列はすべて文字列(TEXT)になります。

$ sqlite3 t196db.sqlite
sqlite> .mode tabs
sqlite> .import clean.tsv members
sqlite> .headers on
sqlite> SELECT * FROM members;
id	name	address	memo
0012	山田 太郎	東京都千代田区1-1, 2F	よろしく
0034	鈴木 花子	大阪府大阪市	「至急」と言われた
0056	佐藤 "ジロー"	福岡県福岡市	1行目 2行目
0078	髙橋 一郎	北海道札幌市	A B
sqlite> SELECT typeof(id), id FROM members LIMIT 1;
typeof(id)	id
text	0012

先頭の0は残りました。引用符で囲んだ sample.tsv も .mode tabs で取り込むと4件になり、改行入り・タブ入りのメモも1つの値として入りました。SQLite の公式ドキュメントは、--ascii 以外の取り込みでは RFC 4180 の決まりで引用符を外すと説明しています。

書き出しは .mode tabs と .mode csv+タブで結果が違う

取り込んだ名簿を書き出すと、sqlite3 3.45.1 では設定によって次のように変わりました。

-- .mode tabs のまま出力(タブ入りのメモ)
0078	A	B
-- .mode csv と .separator "\t" で出力
0078	"A	B"

.mode tabs は値の中のタブ・改行をそのまま出したため、4列のはずの行が5列になり、改行入りのメモは2行に割れました。.mode csv に .separator "\t" を組み合わせると、CSV と同じ決まりで引用符が付きました。最新の公式ドキュメントは tabs モードを「CSV モードと同じで、区切りだけタブ」と説明しており、引用符の付け方は版によって違う可能性があります。値にタブ・改行が入り得る表を書き出したら、列の数を awk で数えて確かめます。

大きな TSV をデータベースへ入れるときは、PostgreSQL の COPY(FORMAT text が既定でタブ区切り)や MySQL の LOAD DATA(既定のフィールド区切りがタブ)が速く扱えます。手順とエラーの直し方は、関連記事「PostgreSQL CSVインポート|COPYと\copyの違いとエラー対処」と、関連記事「MySQL CSVインポート|LOAD DATA INFILEとエラー解決」で扱っています。TSV のときは、それぞれ区切り文字の指定をタブにします。

TSVとCSVを変換する方法(コマンド別)

この節では、TSV と CSV を相互に変換する方法と、置換コマンドでやると崩れる理由が分かります。

区切り文字を置換するだけの変換(tr ',' '\t' や sed 's/\t/,/g')は、値の中のカンマ・タブ・引用符を区別できないため崩れます。実際に CSV を tr で TSV にすると、カンマ入りの住所が2列に割れました。

$ tr ',' '\t' < clean.csv | awk -F'\t' '{print NR": "NF"列 "$3}'
1: 4列 address
2: 5列 "東京都千代田区1-1
3: 4列 大阪府大阪市
4: 4列 福岡県福岡市
5: 4列 北海道札幌市

逆に TSV を sed で CSV にした場合も、住所の行だけが5列になりました(['東京都千代田区1-1', ' 2F'])。

CSV を TSV に変える

やってはいけないtr ',' '\t' で区切りを置き換える。引用符の中のカンマもタブになり、列がずれる
→
代わりにcsv モジュールで1件ずつ読み、値を確かめてからタブでつなぐ

見分ける問い:「値の中にカンマ・引用符・改行が1つも無いと言い切れるか」

TSV を CSV に変える

やってはいけないsed 's/\t/,/g' で置き換える。住所・金額のカンマが区切りと区別できなくなる
→
代わりにcsv.writer で書き出す。カンマを含む値には自動で引用符が付く

見分ける問い:「変換したあと、全部の行の列の数がそろっているか」

Python で CSV を TSV に変換する

次のスクリプトは、CSV を読み、値の中のタブ・改行を \t・\n にエスケープして TSV に書きます。BOM付きの CSV も読めるよう utf-8-sig で開きます。

import csv, sys
# 使い方: python3 csv2tsv.py 入力.csv 出力.tsv
src, dst = sys.argv[1], sys.argv[2]

def esc(s):
    return s.replace("\\", "\\\\").replace("\t", "\\t").replace("\r", "\\r").replace("\n", "\\n")

with open(src, newline="", encoding="utf-8-sig") as fi, \
     open(dst, "w", newline="", encoding="utf-8") as fo:
    for row in csv.reader(fi):
        fo.write("\t".join(esc(c) for c in row) + "\n")
$ python3 csv2tsv.py sample.csv conv.tsv
$ awk -F'\t' '{print NF}' conv.tsv | sort | uniq -c
      5 4

5行すべてが4列になりました。逆向き(TSV→CSV)は、csv.reader(f, delimiter="\t") で読んで csv.writer(f) で書くだけです。

LibreOffice のコマンドで変換する

LibreOffice は画面を出さずに(ヘッドレスで)変換できます。CSV フィルターの設定は「区切り文字の文字コード, 引用符の文字コード, 文字セット, 読み始める行, 列の形式」の順で、9 がタブ、44 がカンマ、34 がダブルクォート、76 が UTF-8 です(LibreOffice ヘルプの CSV Filter Options で確かめました)。

soffice --headless \
  --infilter="CSV:44,34,76,1,1/2" \
  --convert-to 'csv:Text - txt - csv (StarCalc):9,34,76,1' \
  --outdir out clean.csv

--infilter の最後の 1/2 は「1列目を文字列(2)として読む」という指定です。これを付けた場合と付けない場合で、出力の1列目が次のように変わりました。

-- 1/2 なし
"id"	"name"	"address"	"memo"
12	"山田 太郎"	"東京都千代田区1-1, 2F"	"よろしく"
-- 1/2 あり
"id"	"name"	"address"	"memo"
"0012"	"山田 太郎"	"東京都千代田区1-1, 2F"	"よろしく"

この版の LibreOffice は、文字列の値をすべて引用符で囲んだ TSV を出力しました。cut・awk で使うなら、引用符の扱いを決めてから使います。出力の拡張子は .csv になるので、必要なら .tsv に名前を変えます。1件の変換にかかった時間は約0.9秒でした。

方法 カンマ入りの値 改行・タブ入りの値 先頭の0
tr・sed の置換 崩れる 崩れる 残る
Python csv(エスケープ付き) 正しい \t・\n になる 残る
pandas(dtype=str) 正しい 引用符で囲む 残る(指定しないと消える)
LibreOffice(列の形式を指定) 正しい 引用符で囲む 残る(指定しないと消える)
SQLite(.mode csv+.separator) 正しい 引用符で囲む 残る

手元に無いデータの変換にオンラインの変換サービスを使う方法もありますが、名簿や売上など外に出せないデータは、ここで挙げた手元のツールで変換するほうが安全です。

TSVの文字コード(UTF-8・BOM・Shift_JIS)

この節では、TSV が文字化けする原因と、文字コードの見分け方・変換方法が分かります。

TSV にも CSV にも「この文字コードで書く」という決まりは無く、文字化けは書いた側と読む側の文字コードが違うときに起きます。同じ名簿を UTF-8・BOM付き UTF-8・Shift_JIS(cp932)で書き出し、見分け方と読み方を確かめました。

$ file bom.tsv sjis.tsv
bom.tsv:  Unicode text, UTF-8 (with BOM) text
sjis.tsv: Non-ISO extended-ASCII text
$ nkf -g bom.tsv sjis.tsv clean.tsv
bom.tsv: UTF-8
sjis.tsv: CP932
clean.tsv: UTF-8
$ head -c 16 bom.tsv | hexdump -C
00000000  ef bb bf 69 64 09 6e 61  6d 65 09 61 64 64 72 65  |...id.name.addre|

BOM は先頭の3バイト ef bb bf です。Python の csv モジュールで encoding="utf-8" のまま読むと、1列目の項目名が \ufeffid になり、row[0] == "id" が False になりました。encoding="utf-8-sig" にすると True になります。pandas 3.0.5 は、utf-8 のままでも BOM を外して id と読みました。

ファイル 読み方 結果(原文)
Shift_JIS pandas で encoding 指定なし UnicodeDecodeError: 'utf-8' codec can't decode byte 0x8e in position 26: invalid start byte
Shift_JIS(「髙」入り) Python で encoding="shift_jis" UnicodeDecodeError: 'shift_jis' codec can't decode byte 0xfb in position 163: illegal multibyte sequence
Shift_JIS(「髙」入り) pandas で encoding="cp932" 4件とも正しく読めた
BOM付き UTF-8 csv モジュールで encoding="utf-8" 1列目の名前が '\ufeffid' になる
BOM付き UTF-8 csv モジュールで encoding="utf-8-sig" 'id' として読めた

日本語の Windows で作られた「Shift_JIS」のファイルは、shift_jis ではなく cp932 を指定します。「髙」(はしごだか)や「①」は cp932 にはあっても、Python の shift_jis にはありません。iconv で UTF-8 から SHIFT_JIS に変換しようとしたときも、同じ「髙」の位置で止まりました。

$ iconv -f UTF-8 -t SHIFT_JIS clean.tsv > /dev/null
iconv: illegal input sequence at position 212
$ iconv -f UTF-8 -t CP932 clean.tsv > sjis.tsv    # こちらは成功
変換 コマンド
Shift_JIS → UTF-8 iconv -f CP932 -t UTF-8 in.tsv > out.tsv
UTF-8 → Shift_JIS iconv -f UTF-8 -t CP932 in.tsv > out.tsv
BOM を取る sed '1s/^\xEF\xBB\xBF//' in.tsv > out.tsv
CRLF を LF にする tr -d '\r' < in.tsv > out.tsv
文字コードを推測する nkf -g in.tsv か file in.tsv

UnicodeDecodeError の原因別の直し方は、関連記事「UnicodeDecodeErrorの原因と直し方|cp932・utf-8・BOM」で扱っています。

TSVとCSVの速さと大きさ(20万行で実測)

この節では、TSV と CSV で読み込みの速さやファイルの大きさにどれくらい差があるのかが、20万行の実測で分かります。

番号・名前・メモ・都道府県・金額の5列を20万行作り、メモの4分の1ほど(50,009行)に「要確認, 再送」というカンマ入りの値を入れました。同じデータを TSV と CSV で書き出し、読み込みを5回ずつ測った中央値です。

100KBTSV のほうが小さい分CSV 9,428,566 バイト、TSV 9,328,548 バイト
ほぼ同じ読み込みの速さ差は測るたびに入れ替わる範囲
25%awk で CSV を読んだ合計のずれTSV は正しい合計と一致
読み方 CSV TSV
Python csv モジュール(全行を読む) 433ms 363ms
Python の split("\t")(引用符を考えない) ― 210ms
pandas read_csv(dtype=str) 831ms 883ms
awk で金額を合計 201ms 204ms

このマシンは2 CPU で、ほかの処理も動いています。1回目の計測では pandas は CSV 732ms・TSV 630ms と逆の順でした。速さの差は誤差の範囲で、TSV を選ぶ理由にはなりません。ファイルの差の 100,018 バイトは、カンマ入りの50,009行の値を CSV が引用符2つで囲んだ分と一致します。

差がはっきり出たのは、結果の正しさです。金額の列を awk で合計すると、次のようになりました。

$ awk -F'\t' 'NR>1{s+=$5} END{print s}' big.tsv
10032408651
$ awk -F, 'NR>1{s+=$5} END{print s}' big.csv
7527098622
$ python3 -c "import pandas as pd; print(pd.read_csv('big.csv')['amount'].sum())"
10032408651

CSV を awk のカンマ区切りで読むと、カンマ入りのメモがある行で5列目が「都道府県」にずれ、合計が約25%少なくなりました。エラーは出ません。TSV の強みは速さではなく、「区切るだけで正しく読める」ことです。

よくあるエラーとつまずき(原文つき)

この節では、TSV を扱うときに出るエラーや崩れの症状から、原因と直し方を引けます。

症状・原文 原因 直し方
pandas.errors.ParserError: Error tokenizing data. C error: EOF inside string starting at row 1 値の先頭に閉じていない " がある quoting=csv.QUOTE_NONE を付ける
値の引用符が消える("重要" → 重要) TSV を CSV の引用符の決まりで読んでいる 引用符を使わない TSV なら QUOTE_NONE
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x8e … Shift_JIS のファイルを UTF-8 で読んだ encoding="cp932"
UnicodeDecodeError: 'shift_jis' codec can't decode byte 0xfb … 「髙」など cp932 だけにある文字 shift_jis ではなく cp932
iconv: illegal input sequence at position 212 変換先の SHIFT_JIS に無い文字 変換先を CP932 にする
1列目の名前が \ufeffid になり、列名で取れない BOM付き UTF-8 encoding="utf-8-sig"
awk・grep で最後の列の比較が一致しない 行末が CRLF で、値に \r が付いている tr -d '\r'、書き出しは lineterminator="\n"
cut -f で列が取れず、行全体が出る タブがスペースに変わっている cat -T でタブを確かめ、元のファイルから取り直す
行数と件数が合わない・1列の行がある 値の中の改行 引用符で囲むか、\n にエスケープする
列が1つ多い行がある 値の中のタブ 書き出す前にタブを処理する
0012 が 12 になる 読む側が数値として読んだ dtype=str、LibreOffice は列の形式を文字列に
空欄が NaN になる・"N/A" が消える pandas の既定の欠損値の扱い keep_default_na=False

崩れた TSV を受け取ったときは、次の順で確かめると原因を早く絞り込めます。

1

文字コードと BOM を確かめる

file と nkf -g で UTF-8・BOM付き・CP932 のどれかを見る。文字化けしているなら、ここが原因。

file・nkf・hexdump

2

区切りが本当にタブか確かめる

cat -T で ^I が出るか、grep -c $'\t' が0でないかを見る。0ならスペース区切りに変わっている。

cat・grep

3

行ごとの列の数を数える

awk -F'\t' '{print NF}' | sort | uniq -c で列の数が1種類か見る。違う行があれば、値の中のタブ・改行。

awk

4

引用符と行末を確かめる

grep -c '"' で引用符の数、hexdump -C | head で行末の 0d 0a を見る。引用符があれば CSV の決まりで読むツールを使う。

grep・hexdump

TSVとCSVのどちらを使うか(判断表)

この節では、データを渡すとき・保存するときに、TSV と CSV のどちらを選ぶかを場面別に判断できます。

受け取る相手が決まっているなら相手に合わせ、決まっていなければ「値にカンマが入るか」「cut・awk で処理するか」で選びます。

場面 向いている形式 理由
他社・他部署のシステムに取り込んでもらう 相手の仕様に合わせる(多くは CSV) CSV のほうが対応しているソフトが多い
住所・金額・文章などカンマを含む値が多い TSV 引用符が要らず、人の目でも読みやすい
cut・awk・sort などのコマンドで集計する TSV(値にタブ・改行が無い前提) 区切るだけで正しい列が取れる
値に改行が入る(問い合わせ本文など) CSV(引用符で囲む)か JSON Lines 元の TSV は1件1行のため改行を入れられない
機械学習・自然言語処理のデータセット TSV が多い 文章の中のカンマを気にせず書ける
Web の API で表のデータを返す CSV(text/csv)か JSON 受け取る側のライブラリが多い
ログ・コマンドの出力を残す TSV 1行1件で grep・awk と相性がよい
  1. 1相手が形式を決めている → それに従う区切り・文字コード・改行コード・引用符の有無まで確かめる
  2. 2値に改行が入る → CSV(引用符あり)TSV にするなら \n へのエスケープを相手と取り決める
  3. 3値にカンマが多い・コマンドで処理する → TSV書き出す前に値の中のタブを処理する
  4. 4どれにも当てはまらない → CSV対応しているソフトが多く、受け取った人が開きやすい

TSV を書き出す側は、次の4点を確かめてから渡すと、受け取った側で崩れにくくなります。

  • 1値の中のタブ・改行をどう扱うか決めた取り除く・引用符で囲む・\t \n にエスケープする、のどれかを選び、相手に伝える
  • 2文字コードと BOM の有無を伝えたUTF-8(BOM なし)が扱いやすい。Shift_JIS が要るなら CP932 で書き出す
  • 3行末を LF か CRLF にそろえたPython の csv モジュールは既定で CRLF。Linux のツールに渡すなら LF
  • 4全部の行の列の数がそろっているawk -F'\t' '{print NF}' | sort | uniq -c の結果が1行になる

よくある質問

この節では、TSV についてよく聞かれる疑問への短い答えが分かります。

Q. TSV は何の略ですか。
Tab-Separated Values の略で、「タブで区切られた値」という意味です。Tab-Delimited(タブ区切り)と呼ばれることもあります。

Q. TSV ファイルは何で開けますか。
中身は文字だけなので、メモ帳・VS Code などのテキストエディタで開けます。列をそろえて見たいときは、Linux・Mac では column -t -s $'\t' ファイル名 が便利です。

Q. TSV と CSV の違いは区切り文字だけですか。
区切り文字に加えて、値の中に区切り文字や改行があるときの扱いが違います。CSV は引用符で囲み、元の TSV の定義では値にタブを入れられません。ただ、実際のツールの多くは TSV にも CSV と同じ引用符の決まりを使います。

Q. TSV の拡張子は .tsv でないといけませんか。
決まりではありません。.txt や .tab のこともあります。中身がタブ区切りかどうかは cat -T で確かめられます。

Q. TSV にタブを含む値を入れたいときはどうしますか。
引用符で囲む(Python の csv・pandas が自動で行う)か、\t の形にエスケープします。どちらにするかは、読む側のツールに合わせて決め、受け取る人に伝えます。

Q. TSV のほうが CSV より速く読めますか。
20万行の実測では、差は測るたびに入れ替わる程度でした。速さよりも、cut・awk で区切るだけで正しく読めることが TSV の利点です。

Q. TSV を CSV に一括で変換するには。
Python の csv モジュールでタブ区切りとして読み、csv.writer で書き出します。sed でタブをカンマに置換すると、カンマを含む値で列がずれます。

Q. TSV の文字コードは何がよいですか。
決まりはありませんが、UTF-8(BOM なし)がプログラムでもコマンドでも扱いやすい形です。受け取る側が Shift_JIS を求めるときは、CP932 で書き出します。

Q. TSV のメディアタイプ(MIME タイプ)は何ですか。
IANA に text/tab-separated-values として登録されています。CSV は text/csv です。Web サーバーからダウンロードさせるときの Content-Type に使います。

Q. スペースで区切られたファイルは TSV ですか。
違います。スペース区切りは名前の中の空白と区別できないため、TSV として読むと列が取れません。pandas なら sep=r"\s+" のように別の指定で読みます。

まとめ

この節では、この記事の要点を5つにまとめて確かめられます。

  • TSV は値をタブで区切ったテキスト形式で、1行が1件、1行目は項目名が多い。拡張子は .tsv が一般的。
  • CSV との違いは区切り文字と引用符の扱い。CSV は値を引用符で囲み、元の TSV は値にタブを入れない。
  • TSV が崩れる原因は、値の中のタブ・改行・先頭の引用符と、CRLF。awk で列の数を数えると見つけられる。
  • 変換は置換コマンドではなく csv モジュールか LibreOffice で。先頭の0を残すには、列を文字列として読む。
  • 文字化けは cp932・utf-8-sig の指定で直る。速さは CSV とほぼ同じで、TSV の強みは区切るだけで正しく読めること。

参考・出典

この節では、この記事で確かめた公式の資料が分かります(確認日 2026年10月29日)。

  • IANA「text/tab-separated-values」:https://www.iana.org/assignments/media-types/text/tab-separated-values
  • RFC 4180「Common Format and MIME Type for Comma-Separated Values (CSV) Files」:https://www.rfc-editor.org/rfc/rfc4180
  • Python ドキュメント「csv — CSV File Reading and Writing」:https://docs.python.org/3/library/csv.html
  • pandas ドキュメント「pandas.read_csv」:https://pandas.pydata.org/docs/reference/api/pandas.read_csv.html
  • SQLite「Command Line Shell For SQLite」:https://sqlite.org/cli.html
  • LibreOffice ヘルプ「CSV Filter Options」:https://help.libreoffice.org/latest/en-US/text/shared/guide/csv_params.html
  • PostgreSQL ドキュメント「COPY」:https://www.postgresql.org/docs/current/sql-copy.html
  • MySQL リファレンスマニュアル「LOAD DATA Statement」:https://dev.mysql.com/doc/refman/8.4/en/load-data.html

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「TSVとは|CSVとの違いと、タブ・改行・文字コードで崩れる例」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ
参加受付中

AI駆動開発、 無料セミナー