各言語でファイル入出力+文字エンコーディング変換
各言語でファイル入出力と文字エンコーディング変換を書いてみたメモ。
やってる途中で、別々のエントリに分けた方が良かったかもと思ったりもしたが、例えばJavaなんかは内部的には「文字」はUTF-8だったりして入出力と文字エンコーディング変換が深くかかわっていたりするので、まぁいいかということで。
要件は以下の通り。
環境
手元にあるものということで、環境は以下のものに限定する。
- CentOS 7
Java
java.nio.charsetパッケージのCharsetDecoder / CharsetEncoderの存在を今更知ったので、2パターン書いてみた。
- パターン1
- パターン2
- 読み込んだバイト列をCharsetDecoderで文字列に変換
- 文字列をCharsetEncoderでバイト列に変換して書き込み
パターン1
import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; public class Main { public static void main(String[] args) { if (args.length != 1) { System.err.println("Usage: java Main dirname"); System.exit(1); return; } File inFile = new File(args[0], "in.txt"); File outFile = new File(args[0], "out.txt"); try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(inFile), "EUC-JP")); PrintWriter out = new PrintWriter(new OutputStreamWriter(new FileOutputStream(outFile), "Windows-31J")) ) { char[] buf = new char[1024]; int len; while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); } } catch (IOException e) { e.printStackTrace(); } } }
パターン2
import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; public class Main { public static void main(String[] args) { if (args.length != 1) { System.err.println("Usage: java Main dirname"); System.exit(1); return; } File inFile = new File(args[0], "in.txt"); File outFile = new File(args[0], "out.txt"); CharsetDecoder decoder = Charset.forName("EUC-JP").newDecoder(); decoder.reset(); CharsetEncoder encoder = Charset.forName("Windows-31J").newEncoder(); encoder.reset(); try (InputStream in = new BufferedInputStream(new FileInputStream(inFile)); PrintStream out = new PrintStream(new FileOutputStream(outFile)) ) { ByteBuffer inBuf = ByteBuffer.allocate(1024); ByteBuffer outBuf = ByteBuffer.allocate(1024); CharBuffer tmpBuf = CharBuffer.allocate(1024); byte[] buf = new byte[1024]; int len; while ((len = in.read(buf, 0, Math.min(buf.length, inBuf.remaining()))) > 0) { inBuf.put(buf, 0, len); inBuf.flip(); tmpBuf.clear(); CoderResult res = decoder.decode(inBuf, tmpBuf, false); if (res.isUnderflow()) { inBuf.compact(); tmpBuf.flip(); outBuf.clear(); encoder.encode(tmpBuf, outBuf, false); outBuf.flip(); outBuf.get(buf, 0, outBuf.limit()); out.write(buf, 0, outBuf.limit()); } } /* flush()するためのダミー処理 */ inBuf.clear(); inBuf.flip(); tmpBuf.clear(); decoder.decode(inBuf, tmpBuf, true); tmpBuf.flip(); outBuf.clear(); encoder.encode(tmpBuf, outBuf, true); outBuf.flip(); outBuf.get(buf, 0, outBuf.limit()); out.write(buf, 0, outBuf.limit()); /* flush() */ tmpBuf.clear(); decoder.flush(tmpBuf); tmpBuf.flip(); outBuf.clear(); encoder.flush(outBuf); outBuf.flip(); outBuf.get(buf, 0, outBuf.limit()); out.write(buf, 0, outBuf.limit()); } catch (IOException e) { e.printStackTrace(); } } }
なんか、CharsetDecoder / CharsetEncoderの挙動を理解するのにすごく時間が掛かった、というか、そもそもByteBuffer / CharBufferの挙動もよく分からんかった。今でも、上記プログラムでほんとに正しいのか全く自信が無い‥
C
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <iconv.h> #include <limits.h> #include <errno.h> #define BUF_SIZE 1024 int main(int argc, char** argv) { if (argc != 2) { fprintf(stderr, "Usage: %s dirname\n", argv[0]); return 1; } /* ファイルのオープン */ char inFile[PATH_MAX]; sprintf(inFile, "%s/in.txt", argv[1]); char outFile[PATH_MAX]; sprintf(outFile, "%s/out.txt", argv[1]); FILE* inFp = fopen(inFile, "r"); if (!inFp) { perror(inFile); return 1; } FILE* outFp = fopen(outFile, "w"); if (!outFp) { perror(outFile); fclose(inFp); return 1; } /* 文字エンコーディング変換の準備 */ iconv_t iconvHandler = iconv_open("CP932", "EUC-JP"); /* 入力を読み込んで文字エンコーディング変換して出力 */ char inBuf[BUF_SIZE]; size_t inBufLeft = 0; char outBuf[BUF_SIZE]; int len; while ((len = fread(inBuf + inBufLeft, sizeof(char), sizeof(inBuf) - inBufLeft, inFp)) + inBufLeft > 0) { inBufLeft += len; char* inPtr = inBuf; char* outPtr = outBuf; size_t outBufLeft = sizeof(outBuf); int rc = iconv(iconvHandler, &inPtr, &inBufLeft, &outPtr, &outBufLeft); if (rc == -1 && (errno == EILSEQ || errno == E2BIG)) { perror("iconv"); break; } fwrite(outBuf, sizeof(char), sizeof(outBuf) - outBufLeft, outFp); if (inBufLeft > 0) { strncpy(inBuf, inPtr, inBufLeft); } } iconv_close(iconvHandler); /* ファイルのクローズ */ fclose(inFp); fclose(outFp); return 0; }
iconvの挙動を理解するのにすごい苦労した。というか、ネット上に転がっていたサンプルをいくつか試してみたが、入力データにちょっと細工したりするとすぐにテストケースでNGが出たりして使えないということになり、結局manページを熟読して理解したという・・・
C++
C言語で書いたプログラムのうち、ファイル入出力の部分をC++版にしてみたもの。
#include <fstream> #include <iostream> #include <string> #include <cstring> #include <iconv.h> #include <limits.h> #include <errno.h> #define BUF_SIZE 1024 using namespace std; int main(int argc, char** argv) { if (argc != 2) { fprintf(stderr, "Usage: %s dirname\n", argv[0]); return EXIT_FAILURE; } /* ファイルのオープン */ char inFile[PATH_MAX]; sprintf(inFile, "%s/in.txt", argv[1]); char outFile[PATH_MAX]; sprintf(outFile, "%s/out.txt", argv[1]); ifstream inFs(inFile, ios::binary); if (inFs.fail()) { perror(inFile); return EXIT_FAILURE; } ofstream outFs(outFile, ios::binary); if (outFs.fail()) { perror(outFile); return EXIT_FAILURE; } /* 文字エンコーディング変換の準備 */ iconv_t iconvHandler = iconv_open("CP932", "EUC-JP"); /* 入力を読み込んで文字エンコーディング変換して出力 */ char inBuf[BUF_SIZE / 2]; size_t inBufLeft = 0; char outBuf[BUF_SIZE * 2]; streamsize len; while ((len = inFs.readsome(inBuf + inBufLeft, sizeof(inBuf) - inBufLeft)) + inBufLeft > 0) { inBufLeft += len; char* inPtr = inBuf; char* outPtr = outBuf; size_t outBufLeft = sizeof(outBuf); int rc = iconv(iconvHandler, &inPtr, &inBufLeft, &outPtr, &outBufLeft); if (rc == -1 && (errno == EILSEQ || errno == E2BIG)) { perror("iconv"); break; } outFs.write(outBuf, sizeof(outBuf) - outBufLeft); if (inBufLeft > 0) { strncpy(inBuf, inPtr, inBufLeft); } } iconv_close(iconvHandler); return EXIT_SUCCESS; }
PHP
2パターン思いついたので、それぞれ書いてみた。
- パターン1
- ファイルI/Oは、fopen / fgets / fputs / fclose
- 文字エンコーディング変換は、mb_convert_encoding
- パターン2
- ファイルI/Oは、file_get_contents / file_put_contents
- 文字エンコーディング変換は、iconv
パターン1
<?php if (count($argv) < 2) { file_put_contents('php://stderr', "Usage: php {$argv[0]} dirname" . PHP_EOL); exit(1); } $dir = $argv[1]; if (!file_exists($dir) || !is_dir($dir)) { file_put_contents('php://stderr', "{$dir}: No such directory." . PHP_EOL); exit(1); } $inFile = "{$dir}/in.txt"; $outFile = "{$dir}/out.txt"; $inFp = fopen($inFile, 'rb'); if (!$inFp) { file_put_contents('php://stderr', "{$inFile}: Cannot open file." . PHP_EOL); exit(1); } $outFp = fopen($outFile, 'wb'); if (!$outFp) { file_put_contents('php://stderr', "{$outFile}: Cannot open file." . PHP_EOL); fclose($inFp); exit(1); } while (($line = fgets($inFp))) { $line = mb_convert_encoding($line, 'SJIS-win', 'eucJP-win'); fputs($outFp, $line); } fclose($inFp); fclose($outFp);
パターン2
<?php if (count($argv) < 2) { file_put_contents('php://stderr', "Usage: php {$argv[0]} dirname" . PHP_EOL); exit(1); } $dir = $argv[1]; if (!is_dir($dir)) { file_put_contents('php://stderr', "{$dir}: No such directory." . PHP_EOL); exit(1); } $inFile = "{$dir}/in.txt"; $outFile = "{$dir}/out.txt"; if (!file_exists($inFile)) { file_put_contents('php://stderr', "{$inFile}: No such file." . PHP_EOL); exit(1); } $str = file_get_contents($inFile); $str = iconv('eucJP-win', 'SJIS-win', $str); file_put_contents($outFile, $str);
Python 2
import sys import os import codecs if len(sys.argv) < 2: sys.stderr.write("Usage: " + sys.argv[0] + " dirname\n") exit(1) dirname = sys.argv[1] inFile = dirname + "/in.txt" outFile = dirname + "/out.txt" if not os.path.isfile(inFile): sys.stderr.write(inFile + ": No such file\n") exit(1) inFp = codecs.open(inFile, 'rb', 'EUC-JP') outFp = codecs.open(outFile, 'wb', 'CP932') for line in inFp: outFp.write(line) inFp.close() outFp.close()
Python 3
import sys import os import codecs if len(sys.argv) < 2: sys.stderr.write("Usage: " + sys.argv[0] + " dirname\n") exit(1) dirname = sys.argv[1] inFile = dirname + "/in.txt" outFile = dirname + "/out.txt" if not os.path.isfile(inFile): sys.stderr.write(inFile + ": No such file\n") exit(1) inFp = codecs.open(inFile, 'rb', 'EUC-JP') outFp = codecs.open(outFile, 'wb', 'CP932') for line in inFp: outFp.write(line) inFp.close() outFp.close()
Python 2と何ら変わりはない。
Ruby
if ARGV.length != 1 STDERR.puts('Usage: ' + __FILE__ + ' dirname') exit 1 end dir = ARGV[0] inFile = dir + '/in.txt' outFile = dir + '/out.txt' if !File.file?(inFile) STDERR.puts(inFile + ': No such file') exit 1 end inFp = File.open(inFile, mode = 'rb') outFp = File.open(outFile, mode = 'wb') inFp.each_line{|line| line.encode!('CP932', 'EUC-JP') outFp.puts(line) } inFp.close() outFp.close()
Perl
パターン1
if (@ARGV < 1) { die("Usage: " . __FILE__ . " dirname"); } my $dir = $ARGV[0]; my $inFile = $dir . "/in.txt"; my $outFile = $dir . "/out.txt"; open(inFp, "<:encoding(EUC-JP)", $inFile) or die($inFile . ": $!"); open(outFp, ">:encoding(CP932)", $outFile) or die($outFile . ": $!"); while (my $line = <inFp>) { print outFp $line; } close(inFp); close(outFp);
パターン2
use Encode; if (@ARGV < 1) { die("Usage: " . __FILE__ . " dirname"); } my $dir = $ARGV[0]; my $inFile = $dir . "/in.txt"; my $outFile = $dir . "/out.txt"; open(inFp, "<", $inFile) or die($inFile . ": $!"); open(outFp, ">", $outFile) or die($outFile . ": $!"); while (my $line = <inFp>) { $line = encode('CP932', decode('EUC-JP', $line)); print outFp $line; } close(inFp); close(outFp);
Go
package main import ( "fmt" "os" "io" "bufio" "golang.org/x/text/encoding/japanese" "golang.org/x/text/transform" ) func main() { if len(os.Args) < 2 { fmt.Fprintln(os.Stderr, "Usage: " + os.Args[0] + " dirname") os.Exit(1) } dir := os.Args[1] inFile := dir + "/in.txt" outFile := dir + "/out.txt" inFp, err := os.Open(inFile) if err != nil { panic(err) } outFp, err := os.Create(outFile) if err != nil { inFp.Close() panic(err) } in := bufio.NewReader(transform.NewReader(inFp, japanese.EUCJP.NewDecoder())) out := bufio.NewWriter(transform.NewWriter(outFp, japanese.ShiftJIS.NewEncoder())) b := make([]byte, 1024) for { n, err := in.Read(b) if err == io.EOF { break } else if err != nil { inFp.Close() outFp.Close() panic(err) } out.Write(b[0:n]) } out.Flush() inFp.Close() outFp.Close() }
これをやるには「GOPATH=$(pwd) go get golang.org/x/text/encoding/japanese」しておく必要がある。更に実行時に「GOPATH=$(pwd) go run Main.go」することも忘れずに。
bash
#! /bin/bash dir="${1}" if [ -z "${dir}" ]; then echo "Usage: $0 dirname" >> /dev/stderr exit 1 fi if [ ! -d "${dir}" ]; then echo "${dir}: No such directory." >> /dev/stderr exit 1 fi iconv -f EUC-JP -t CP932 < "${dir}/in.txt" > "${dir}/out.txt"