手元にある各言語で、「1行」を読み込んだ時の文字列の違い、とりわけ改行コードの扱いについて調べてみたメモ。
環境
手元にあるものということで、環境は以下のものに限定する。
- CentOS 7
- Java (openjdk version "1.8.0_151")
- C (gcc (GCC) 4.8.5)
-std=gnu11
でコンパイル
- C++ (g++ (GCC) 4.8.5)
-std=gnu++1y
でコンパイル
- PHP (PHP 5.4.16 (cli))
- Python 2 (Python 2.7.5)
- Python 3 (Python 3.6.3)
- ソースからビルドしたもの
- Ruby (ruby 2.0.0p648)
- Perl (v5.16.3)
- Go (go version go1.8.3 linux/amd64)
- bash (4.2.46(1)-release)
- Awk (GNU Awk 4.0.2)
- sed (sed (GNU sed) 4.2.2)
繰り返すが、環境は「CentOS 7」。ということで、改行コードは「LF」(一般に"\n"
で表されることが多い)が標準となっている環境。
入力ファイル
od -c
した結果で示す。
0000000 h o g e \r \n f o o \r b a r \n e n 0000020 d 0000021
改行コードとして一般的に知られている3つのパターンを含み、かつファイルの末尾には改行コードが無いというファイルである。
- "hoge"の後には、Windows系のOSで標準とされる改行コード「CR LF」
- "foo"の後には、昔の(?)MacOS系のOSで標準とされる改行コード「CR」
- "bar"の後には、UNIX/Linux系のOSで標準とされる改行コード「LF」
- "end"の後には、いきなりEOF
プログラムでの処理
各言語で書くプログラムは、入力の末端に到達したと判定されるまで「1行」を読み込み、先頭にスラッシュ('/')を付加して「1行」の区切りが分かるようにしたうえでそのまま出力。その際に追加の改行コードが出力されないような方法で出力する。
出力パターン
答えを先に言ってしまう形になるが、試したケースで計6パターンの出力が得られた。 以下のような感じである。
- パターン1:いわゆる改行コードはすべて認識し、得られる文字列からは取り除かれる
0000000 / h o g e / f o o / b a r / e n 0000020 d 0000021
- パターン2:「LF」が改行コードと認識され、かつ得られる文字列に改行コードが含まれる
0000000 / h o g e \r \n / f o o \r b a r \n 0000020 / e n d 0000024
- パターン3:「LF」が改行コードと認識され、得られる文字列からは取り除かれる
0000000 / h o g e \r / f o o \r b a r / e 0000020 n d 0000022
- パターン4:「CR LF」と「LF」が改行コードと認識され、得られる文字列からは取り除かれる
0000000 / h o g e / f o o \r b a r / e n 0000020 d 0000021
- パターン5:「LF」が改行コードと認識され、得られる文字列からは取り除かれ、さらに改行コードの前にEOFが来ると末端と認識されてしまう
0000000 / h o g e \r / f o o \r b a r 0000016
- パターン6:「CR LF」と「LF」が改行コードと認識され、得られる文字列からは取り除かれ、さらに改行コードの前にEOFが来ると末端と認識されてしまう
0000000 / h o g e / f o o \r b a r 0000015
Java
念のため、BufferedReader版とScanner版の両方を試した。
BufferedReader版
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; public class Main { public static void main(String[] args) { try (BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); PrintWriter out = new PrintWriter(System.out) ) { String buf; while ((buf = in.readLine()) != null) { out.print('/'); out.print(buf); } } catch (IOException e) { e.printStackTrace(); } } }
Scanner版
import java.io.PrintWriter; import java.util.Scanner; public class Main { public static void main(String[] args) { try (Scanner in = new Scanner(System.in); PrintWriter out = new PrintWriter(System.out) ) { while (in.hasNextLine()) { String buf = in.nextLine(); out.print('/'); out.print(buf); } } } }
C
#include <stdio.h> int main(int argc, char** argv) { char buf[1024]; while (fgets(buf, sizeof(buf), stdin)) { printf("/"); printf("%s", buf); } return 0; }
C++
念のため、cin.getline
とstringヘッダのgetline
の両方を試した。
cin.getline
版
#include <iostream> using namespace std; int main(int argc, char** argv) { char buf[1024]; while (cin.getline(buf, sizeof(buf))) { cout << '/'; cout << buf; } return EXIT_SUCCESS; }
stringヘッダのgetline
版
#include <iostream> #include <string> using namespace std; int main(int argc, char** argv) { string buf; while (getline(cin, buf)) { cout << '/'; cout << buf; } return EXIT_SUCCESS; }
PHP
<?php $lines = file('php://stdin'); foreach ($lines as $line) { echo '/' . $line; }
Python 2
import sys while True: line = sys.stdin.readline() if line == '': break sys.stdout.write('/') sys.stdout.write(line)
print文を使うと、末尾に勝手に改行コードが挿入されるので、sys.stdout.writeを使う。
Python 3
import sys while True: line = sys.stdin.readline() if line == '': break sys.stdout.write('/') sys.stdout.write(line)
print文を使うと、末尾に勝手に改行コードが挿入されるので、sys.stdout.writeを使う。
Ruby
while line = STDIN.gets print '/' print line end
Perl
while (my $line = readline(STDIN)) { print '/'; print $line; }
Go
package main import ( "bufio" "fmt" "io" "os" ) func ReadLine(reader *bufio.Reader) (str string, err error) { prefix := false buf := make([]byte, 0, 1024) line := make([]byte, 0, 1) for { line, prefix, err = reader.ReadLine() if err == io.EOF { return } buf = append(buf, line...) if prefix { continue } str = string(buf) return } } func main() { stdin := bufio.NewReader(os.Stdin) for { line, err := ReadLine(stdin) if err == io.EOF { break } fmt.Print("/") fmt.Print(line) } }
bash
環境変数IFSの値によって挙動が変わるだろうということで、3パターン試した。
IFS=$'\n'
版
#! /bin/bash while IFS=$'\n' read line ; do echo -n / echo -n ${line} done
IFS=$'\r'
版
#! /bin/bash while IFS=$'\r' read line ; do echo -n / echo -n ${line} done
IFS=
版
#! /bin/bash while IFS= read line ; do echo -n / echo -n ${line} done
Awk
{ printf("/"); printf("%s", $0); }
sed
言語なのかという突っ込みがありそうだが、行処理なら任せろというツールの一つであるので外せないということで。
s|^|/|
行頭にスラッシュを挿入するという単純な正規表現。
まとめると‥
- パターン1:いわゆる改行コードはすべて認識し、得られる文字列からは取り除かれる
- パターン2:「LF」が改行コードと認識され、かつ得られる文字列に改行コードが含まれる
- パターン3:「LF」が改行コードと認識され、得られる文字列からは取り除かれる
- パターン4:「CR LF」と「LF」が改行コードと認識され、得られる文字列からは取り除かれる
- パターン5:「LF」が改行コードと認識され、得られる文字列からは取り除かれ、さらに改行コードの前にEOFが来ると末端と認識されてしまう
- パターン6:「CR LF」と「LF」が改行コードと認識され、得られる文字列からは取り除かれ、さらに改行コードの前にEOFが来ると末端と認識されてしまう
言語 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
Java(BufferedReader) | O | |||||
Java(Scanner) | O | |||||
C | O | |||||
C++(cin.getline) | O | |||||
C++(getline) | O | |||||
PHP | O | |||||
Python 2 | O | |||||
Python 3 | O | |||||
Ruby | O | |||||
Perl | O | |||||
Go | O | |||||
bash(IFS=$'\n') | O | |||||
bash(IFS=$'\r') | O | |||||
bash(IFS=) | O | |||||
Awk | O | |||||
sed | O |
bashで「end」が出力されないというのは予想外だった。
bashを除くと、Go言語がちょっと特殊。