ようへいの日々精進XP

よかろうもん

Docker や docker-compose で TTY を有効にした場合, 標準出力の改行コードが CR+LF になる件

tl;dr

ギョームで Docker (docker-compose) を使ったちょっとしたコマンドラインツールを作った時に, 以下のように実行して標準出力をファイルに出力すると, 何故か ^M という改行コード (Carriage Return) が残ってしまってなんでやろうと思って調べたら Docker なんかよりももっといにしへの TTY の話まで掘り下がってしまって, すごく勉強になったのでメモっておきます.

docker-compose run foo > foo.txt

foo.txt の中身は以下のような感じになってしまいます.

foo1@example.com^M
foo2@example.com^M
foo3@example.com^M

なぜ, こんなことになるのか

まさに, 以下の Issue に記載されていました.

github.com

このコメントに対する返答を引用します.

Moral of the story, don't use -t unless you want a TTY. TTY line endings are CRLF, this is not Docker's doing.

TTY を有効にした場合, 行末には CRLF が付与されるとのこと. そして, これは Docker の仕業ではなく, TTY の仕業であるとのこと. CRLF が付与されることを避けたければ, TTY を無効 (Docker ではデフォルトが無効だが, docker-compose では有効になっている) にすれば良いとのこと.

ほうほう.

一応, 確認してみます. 上記の Issue コメントに書かれている方法を参考にして od コマンドを利用して確認してみます. 尚, 確認で利用している環境は以下の通りです.

$ docker version
Client: Docker Engine - Community
 Version:           18.09.0
 API version:       1.39
 Go version:        go1.10.4
 Git commit:        4d60db4
 Built:             Wed Nov  7 00:47:43 2018
 OS/Arch:           darwin/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          18.09.0
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.10.4
  Git commit:       4d60db4
  Built:            Wed Nov  7 00:55:00 2018
  OS/Arch:          linux/amd64
  Experimental:     true

$ docker-compose version
docker-compose version 1.23.2, build 1110ad01
docker-py version: 3.6.0
CPython version: 3.6.6
OpenSSL version: OpenSSL 1.1.0h  27 Mar 2018

以下, 実行結果です.

# Docker コマンドで
# TTY 無効
$ docker run ruby:2.5-alpine sh -c 'echo "test"' | od -c
0000000    t   e   s   t  \n
0000005
# TTY 有効
$ docker run -t ruby:2.5-alpine sh -c 'echo "test"' | od -c
0000000    t   e   s   t  \r  \n
0000006

# docker-compose コマンドで
# TTY 無効 (-T で TTY を無効にする)
$ docker-compose run -T test sh -c 'echo "test"' | od -c
0000000    t   e   s   t  \n
0000005
# TTY 有効 (docker-compose はデフォルトで TTY が有効になっている)
$ docker-compose run test sh -c 'echo "test"' | od -c
0000000    t   e   s   t  \r  \n
0000006

確かに, TTY を無効にしている場合, \n のみで \r が付与されていないことが判ります.

なぜ, TTY の行末には CRLF が付与されているのか

なぜ, TTY の行末には CRLF が付与されているのか, 詳しい文献を見つけることが出来なかったのですが, 以下のような「screen コマンドの出力はなんで CRLF なんすか?」という記事を見つけたので転載しておきます.

unix.stackexchange.com

以下, グーグル翻訳した内容を一部意訳もつけて転載させて頂きます.

The deep reason for the discrepancy between program output and a captured tty stream (e.g. typescript) is that tty's used to be printers.

プログラム出力とキャプチャされた tty ストリームとの違いは, tty がプリンタであったことに由来する.

Before unix, text always had a CRLF at the end of a line, not because it was considered to be the logical representation of a line termination, but because the characters individually had real physical meaning: move the print head all the way to the left, and advance the paper.

Unix の前には, 行終端には常に CRLF が付いていた. 行終端の論理的な表現とみなされたわけではありませんでしたが, 実際には紙を前進させるという, 物理的な意味があります.

Unix took a radical new approach: it treats text files on disk as a useful object in their own right (not just instructions for a printer), and lines as logical entities. The two-character line terminator is unnecessarily complicated in the unix worldview.

UNIX は, ディスク上のテキストファイルをプリンタのための指示だけでなく, 論理的なエンティティとして有用なオブジェクトとして扱う. 2 文字の行終端文字 (CRLF) は, Unix の世界観では不必要に複雑である.

But they had to work with existing hardware - printers and CRT dumb terminals that didn't recognize a single "end of line" character, but only a CR to do half the job and an LF to do the other half. So a translation had to be done, and it was done at the closest possible place to that hardware - in the tty driver.

しかし, 既存のハードウェアプリンタやCRTダム端末では,「行末」文字は認識されませんでしたが, ジョブの半分を実行するCRと残りの半分を実行するLFだけが必要でした. 翻訳が完了しなければならなかったので, それは tty ドライバの中でそのハードウェアに最も近い可能な場所で行われました.

Since then, it's all been backward compatibility. So you have a terminal emulator that insists on CRLF, and a tty driver that supplies it when a program outputs a newline.

それ以来, それはすべて下位互換性があります. したがって, CRLFを主張する端末エミュレータと, プログラムが改行を出力するときにそれを供給する tty ドライバがあります.

どうやら, 歴史的な経緯の中で下位互換の維持の為, TTY の行末には CRLF が付与されるのではないかと読み取りました. Linuxソースコードとか読めば (読めれば), もう少し確証を持てる情報が得られるのではないかと考えていますが, ここまでの情報にたどり着くまでに TTY や CR, LF について参考になる記事を読むことが出来ました.

ありがとうございました.

参考文献

github.com

github.com

tty(4)

テレタイプ端末 - Wikipedia

developer.mozilla.org

www.granfairs.com