ファイルの文字コードを変換したい

Shift_JISで書かれたCSVをUTF-8で読みたい場合や、逆にUTF-8のテキストをWindows向けにShift_JISへ戻したい場合、iconv コマンドを使う。

iconv はglibcやlibiconvに含まれるため、Linuxとmacのどちらでも追加インストールなしで使える。

基本的な使い方

-f に変換元、-t に変換先の文字コードを指定する。結果は標準出力に流れるため、リダイレクトでファイルに保存する。

$ iconv -f SHIFT_JIS -t UTF-8 sjis.txt > utf8.txt

パイプの途中でも使える。

$ cat sjis.txt | iconv -f SHIFT_JIS -t UTF-8
こんにちは、世界
日本語テキストのテスト

iconv は変換元の文字コードを自動判定しないため、-f の指定は必須である。分からない場合は事前に file コマンドなどで調べる。

fileコマンドでMIME typeや文字エンコーディングを調べる方法

日本語のテキストであれば、判定と変換をまとめて処理する nkf も使える。

【nkf】文字コードを判定して変換する

指定できる文字コードを調べる

-l で対応する文字コードの一覧を表示する。数が多いためgrepと組み合わせる。

$ iconv -l | grep -iE 'cp932|shift_jis|^euc-jp'
CP932//
EUC-JP-MS//
EUC-JP//
SHIFT_JIS//
SHIFT_JISX0213//

末尾の // は後述の //IGNORE などのサフィックスを付ける位置を示す区切りである。CP932CP932// は同じ意味のため、指定するときは省略してよい。

出力先を -o で指定する

リダイレクトの代わりに -o でも出力先を指定できる。

$ iconv -f SHIFT_JIS -t UTF-8 -o utf8.txt sjis.txt

ただし入力と同じファイルは指定してはならない。小さなファイルではたまたま成功するが、バッファサイズを超えるファイルでは読み込み中に出力が上書きされ、途中で異常終了してファイルが壊れる。

# 約6.8MBのファイルに対して実行した例
$ iconv -f UTF-8 -t CP932 big.txt -o big.txt
Bus error (core dumped)
$ wc -c big.txt
28672 big.txt

リダイレクトで同じファイルを指定した場合も、シェルが先にファイルを空にするため中身が消える。

$ iconv -f UTF-8 -t CP932 c.txt > c.txt
$ wc -c c.txt
0 c.txt

上書きしたい場合は一時ファイルを経由する。

$ iconv -f UTF-8 -t CP932 c.txt > c.txt.tmp && mv c.txt.tmp c.txt

変換できない文字への対処

変換先の文字コードで表現できない文字があると、iconv はその位置で処理を中断する。EUC-JPに存在しない丸数字を含むファイルを変換すると、エラーになって途中までしか出力されない。

$ cat t.txt
価格は 100円 ①②③ ~ です
$ iconv -f UTF-8 -t EUC-JP t.txt > out.txt
iconv: illegal input sequence at position 17
$ echo $?
1

-c で読み飛ばす

-c を付けると変換できない文字を捨てて処理を続ける。終了ステータスも0になる。

$ iconv -c -f UTF-8 -t EUC-JP t.txt > out.txt
$ echo $?
0
$ iconv -f EUC-JP -t UTF-8 out.txt
価格は 100円  ~ です

//IGNORE との違い

変換先に //IGNORE を付けた場合も文字は捨てられるが、最後にエラーメッセージを出力し終了ステータスは1になる。set -e を有効にしたシェルスクリプトで使うなら -c を選ぶ。

$ iconv -f UTF-8 -t EUC-JP//IGNORE t.txt > out.txt
iconv: illegal input sequence at position 38
$ echo $?
1

//TRANSLIT で似た文字に置き換える

//TRANSLIT は、変換できない文字を意味の近い文字に置き換える。丸数字の (1) になる。

$ iconv -f UTF-8 -t EUC-JP//TRANSLIT t.txt > out.txt
$ iconv -f EUC-JP -t UTF-8 out.txt
価格は 100円 (1)(2)(3) ~ です

置き換え規則はロケールに依存する。glibcでは LC_ALL=C のときアクセント記号付き文字が ? になり、UTF-8のロケールでは基底文字に置き換わる。

$ printf 'café naïve\n' | LC_ALL=C iconv -f UTF-8 -t ASCII//TRANSLIT
caf? na?ve
$ printf 'café naïve\n' | LC_ALL=C.UTF-8 iconv -f UTF-8 -t ASCII//TRANSLIT
cafe naive

Shift_JIS には CP932 を指定する

Windows由来のテキストを扱う場合、SHIFT_JIS ではなく CP932 を指定する。丸数字や全角チルダなどの機種依存する文字は規格上のShift_JISに含まれず、変換に失敗する。

$ printf '①②③ ~ ¥ 髙\n' | iconv -f UTF-8 -t SHIFT_JIS > /dev/null
iconv: illegal input sequence at position 0

CP932 はマイクロソフトの拡張を含むため、同じ入力を変換できる。

$ printf '①②③ ~ ¥ 髙\n' | iconv -f UTF-8 -t CP932 | iconv -f CP932 -t UTF-8
①②③ ~ ¥ 髙

Excelが出力するCSVやWindowsのメモ帳で保存したテキストは CP932 として扱えばよい。

BOM の扱い

UTF-16 を指定するとBOM付きで出力される。バイト順は実行環境に従うため、x86_64のLinuxではリトルエンディアンを示す ff fe が付く。BOMを付けたくない場合は UTF-16LEUTF-16BE のようにバイト順まで指定する。

$ printf 'あA\n' | iconv -f UTF-8 -t UTF-16 | od -An -tx1
 ff fe 42 30 41 00 0a 00
$ printf 'あA\n' | iconv -f UTF-8 -t UTF-16LE | od -An -tx1
 42 30 41 00 0a 00

一方、UTF-8のBOM(ef bb bf)は iconv では除去できない。BOMを1文字(U+FEFF)として素通しするため、UTF-16を経由しても残る。

$ od -An -tx1 bom.txt
 ef bb bf e3 81 82 41 0a
$ iconv -f UTF-8 -t UTF-16 bom.txt | iconv -f UTF-16 -t UTF-8 | od -An -tx1
 ef bb bf e3 81 82 41 0a

UTF-8のBOMを外すにはsedなどで先頭の3バイトを削る。

$ sed '1s/^\xef\xbb\xbf//' bom.txt | od -An -tx1
 e3 81 82 41 0a

nkf を使う場合は -w で出力するとBOMが外れる。

複数ファイルをまとめて変換する

iconv は複数のファイルを引数に取れるが、結果は連結されて1つの出力になる。ファイルごとに変換する場合はループを回す。

$ mkdir -p utf8
$ for f in *.csv; do
    iconv -f CP932 -t UTF-8 "$f" > "utf8/$f"
  done

macOS の iconv との違い

macOSの iconv はlibiconv由来で、GNU版とは挙動が異なる。

項目Linux(glibc)macOS(libiconv)
-o オプションありなし(リダイレクトを使う)
-c の終了ステータス01(警告も出力する)
//TRANSLIT の結果ロケールに応じて変わるロケールによらず固定。対応がなければ
-l の出力1行1エンコーディング別名を同じ行に列挙

macOSで変換に失敗すると、原因の分かりにくいメッセージが出る点にも注意する。

$ iconv -f UTF-8 -t SHIFT_JIS m.txt > out.txt
iconv: iconv(): Inappropriate ioctl for device

両方の環境で動かすスクリプトでは、-o を避けてリダイレクトを使い、終了ステータスへの依存も減らしておくと安全である。

参考