- ファイル内容は同じなのにdiffで違いが出る
- Web上でソースレビューする時に同じ内容の行なのに差分が出て紛らわしい
これらの原因はWindows、Mac、Linuxが入り混じった、改行コードが統一されていない開発環境にあるかも知れません。
find ./ -type f | xargs -I{} -t awk '{if($0 ~ /\r$/) {print NR, $0}}' {}
を実行すれば、意図せず全行がCRLFに置換されてしまった、或いはCRLFが混じってしまったファイルを行番号付きで特定できます。
環境
- CentOS 7.6
OSによる改行コードの違い
テキストファイルでEnterを押して行が次に行くのは、行末に以下の制御コードが入る為。(OSによって改行コードは異なる)
OS | char | 数値 |
Windows | CRLF(\r\n) | 0x0D(13) 0x0A(10) |
Linux、MacOS X | LF(\n) | 0x0A(10) |
MacOS 9以前(10以降はLF) | CR(\r) | 0x0D(13) |
MacOS 9はなくなって久しく、現代の改行コードはCRLF、LFの違いのみです。
ASCIIコード128バイトの内、数字の0~9(0x30~0x39)、アルファベットのa~z(0x61~0x7A)のようなテキストファイルに入力出来るASCIIコード以外は制御コードとして存在します。
以下はman asciiの結果。0x0D(13)はCR(復帰)、0x0A(10)LF(改行)であることが分かります。
Oct Dec Hex Char Oct Dec Hex Char
qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq
000 0 00 NUL '\0' 100 64 40 @
001 1 01 SOH (ヘッダ開始) 101 65 41 A
002 2 02 STX (テキスト開始) 102 66 42 B
003 3 03 ETX (テキスト終了) 103 67 43 C
004 4 04 EOT (転送終了) 104 68 44 D
005 5 05 ENQ (問い合わせ) 105 69 45 E
006 6 06 ACK (肯定応答) 106 70 46 F
007 7 07 BEL '\a' (ベル) 107 71 47 G
010 8 08 BS '\b' (バックスペース) 110 72 48 H
011 9 09 HT '\t' (水平タブ) 111 73 49 I
012 10 0A LF '\n' (改行) 112 74 4A J
013 11 0B VT '\v' (垂直タブ) 113 75 4B K
014 12 0C FF '\f' (改ページ) 114 76 4C L
015 13 0D CR '\r' (復帰) 115 77 4D M
016 14 0E SO (シフトアウト) 116 78 4E N
017 15 0F SI (シフトイン) 117 79 4F O
020 16 10 DLE (伝送制御拡張) 120 80 50 P
021 17 11 DC1 (装置制御1) 121 81 51 Q
022 18 12 DC2 (装置制御2) 122 82 52 R
023 19 13 DC3 (装置制御3) 123 83 53 S
024 20 14 DC4 (装置制御4) 124 84 54 T
025 21 15 NAK (否定応答) 125 85 55 U
026 22 16 SYN (同期) 126 86 56 V
027 23 17 ETB (転送ブロック終了) 127 87 57 W
030 24 18 CAN (キャンセル) 130 88 58 X
031 25 19 EM (メディア終了) 131 89 59 Y
032 26 1A SUB (置換) 132 90 5A Z
033 27 1B ESC (エスケープ) 133 91 5B [
034 28 1C FS (ファイル区切り) 134 92 5C \ '\\'
035 29 1D GS (グループ区切り) 135 93 5D ]
036 30 1E RS (レコード区切り) 136 94 5E ^
037 31 1F US (ユニット区切り) 137 95 5F _
040 32 20 SPACE 140 96 60 `
041 33 21 ! 141 97 61 a
042 34 22 " 142 98 62 b
043 35 23 # 143 99 63 c
044 36 24 $ 144 100 64 d
045 37 25 % 145 101 65 e
046 38 26 & 146 102 66 f
047 39 27 ´ 147 103 67 g
050 40 28 ( 150 104 68 h
051 41 29 ) 151 105 69 i
052 42 2A * 152 106 6A j
053 43 2B + 153 107 6B k
054 44 2C , 154 108 6C l
055 45 2D - 155 109 6D m
056 46 2E . 156 110 6E n
057 47 2F / 157 111 6F o
060 48 30 0 160 112 70 p
061 49 31 1 161 113 71 q
062 50 32 2 162 114 72 r
063 51 33 3 163 115 73 s
064 52 34 4 164 116 74 t
065 53 35 5 165 117 75 u
066 54 36 6 166 118 76 v
067 55 37 7 167 119 77 w
070 56 38 8 170 120 78 x
071 57 39 9 171 121 79 y
072 58 3A : 172 122 7A z
073 59 3B ; 173 123 7B {
074 60 3C < 174 124 7C |
075 61 3D = 175 125 7D }
076 62 3E > 176 126 7E ~
077 63 3F ? 177 127 7F DEL
3パターンのファイルを作成してdiffしてみる
- 改行がCRLFのみのファイル(crlf.txt、windowsのterapadで作成)
- 改行がLFのみのファイル(lf.txt、viで作成)
- 改行がCRLF、LFが混じったファイル(crlf-and-lf.txt、echo >> dddd crlf.txt)
改行が見えるようにcatで内容を確認。(-eは行末(LF)を”$”で置き換え)
$ cat -e crlf.txt
aaaa^M$ <-- CRLF
bbbb^M$
cccc^M$
dddd^M$
$ cat -e lf.txt
aaaa$ <-- LF
bbbb$
cccc$
dddd$
$ cat -e crlf-and-lf.txt
aaaa^M$ <-- CRLF
bbbb^M$
cccc^M$
dddd$ <-- LF
ファイル内容が同じでも改行コードが違えば差分が出る。
$ diff crlf.txt lf.txt
1,4c1,4
< aaaa
< bbbb
< cccc
< dddd
---
> aaaa
> bbbb
> cccc
> dddd
ありがちなのが開発チームメンバーのPCがWindowsとMacOSXで混在しており、CRLFとLFが混じってしまうケース。
$ diff crlf.txt crlf-and-lf.txt
4c4
< dddd
---
> dddd
同じ内容の行なのに差分が出て煩わしいですね。
CRLF行を持つファイルがあるか再帰的に調べる
コマンドで改行コードの違いを見つけられるようにしておきます。
- findで見つけたカレント配下のファイルをxargsで一ファイルずつawkに渡す
- awkはxargs引数{}で渡されたファイルを一行ずつ舐める(その際LFはawkが削除済み)
- (LFは削除されているので)CRで終わる行を探し行番号(NR)と行全体($0)をコンソールに表示
$ find ./ -name "*.txt" \
| xargs -I{} -t awk '{if($0 ~ /\r$/) {print NR, $0}}' {}
awk {if($0 ~ /\r$/) {print NR, $0}} ./crlf.txt
1 aaaa
2 bbbb
3 cccc
4 dddd
awk {if($0 ~ /\r$/) {print NR, $0}} ./lf.txt
awk {if($0 ~ /\r$/) {print NR, $0}} ./crlf-and-lf.txt
1 aaaa
2 bbbb
3 cccc
CRLFが改行コードになっているファイルが行番号付きで表示出来ました。
逆にLFで終わっているファイル行を出したい場合、awkの条件式を論理反転させれば期待通りの結果になります。
$ find ./ -type f \
| xargs -I{} -t awk '{if($0 !~ /\r$/) {print NR, $0}}' {}
awk {if($0 !~ /\r$/) {print NR, $0}} ./crlf.txt
awk {if($0 !~ /\r$/) {print NR, $0}} ./lf.txt
1 aaaa
2 bbbb
3 cccc
4 dddd
awk {if($0 !~ /\r$/) {print NR, $0}} ./crlf-and-lf.txt
4 dddd
見つけた後は統一されるように変更すればいいですね。手作業で変換する場合は
sed -i 's/\r//g' CRLFが含まれたファイル
などでCRを削除、LFのみに変更します。