パイプではまらないためにdockerコマンド実行時の「-it」オプションの挙動を確認してみた

Docker

はじめに

docker run コマンドや docker exec コマンドを実行する際に -i オプションと -t オプションを付与することが多いですが、
理解をしていないとパイプとつなぐ際に思わぬ挙動をするので整理しました。

検証環境

$ bash -version | head -n 1
GNU bash, バージョン 5.0.7(1)-release (x86_64-apple-darwin18.5.0)

$ docker --version
Docker version 18.09.2, build 6247962
Bash

各種オプションの有無での挙動の違い

-i オプション有り、 -t オプション有り

まずは -it オプションつきでDockerコンテナを起動し bash を起動してみます。
接続したコンテナ内でLinuxコマンドを実行でき、結果をターミナルで確認できます。

$ docker run -it ubuntu:19.10 bash

root@5ba28223ab82:/# ls
bin   dev  home  lib32  libx32  mnt  proc  run   srv  tmp  var
boot  etc  lib   lib64  media   opt  root  sbin  sys  usr
root@5ba28223ab82:/# echo test
test
Bash

-i オプション無し、 -t オプション無し

両方のオプションを外した場合はどうでしょう?

$ docker run ubuntu:19.10 bash
Bash

コンテナは直ちに終了してしまいます。

-i オプション無し、 -t オプション有り

今度は -t オプションだけ付与して実行してみます。

$ docker run -t ubuntu:19.10 bash
root@58ad0e36e274:/# ls
Bash

先ほどと異なり、インタラクティブなBashが起動できました。
-t オプションについてヘルプを見てみます。

$ docker run --help | grep '\-t'
      --add-host list                  Add a custom host-to-IP mapping (host:ip)
      --cpu-rt-period int              Limit CPU real-time period in microseconds
      --cpu-rt-runtime int             Limit CPU real-time runtime in microseconds
      --disable-content-trust          Skip image verification (default true)
      --health-timeout duration        Maximum time to allow one check to run (ms|s|m|h) (default 0s)
      --stop-timeout int               Timeout (in seconds) to stop a container
      --tmpfs list                     Mount a tmpfs directory
  -t, --tty                            Allocate a pseudo-TTY
Bash

-t はtty(擬似端末)をコンテナに割り当てるためのオプションです。
これによりBashがインタラクティブモードで起動することとなります。プロンプトも表示されていますね。

ここで ls と入力し、Enterキーを押してみます。
ls の結果が何も出力されないはずです。
exit もできなくなりました。
こうなると CTRL+C で抜けるしかありません。

-i オプション有り、 -t オプション無し

今度は -i オプション付きだけを付与してみます。

$ docker run -i ubuntu:19.10 bash
Bash

何も表示されませんが、ここで ls と入力してみます。

ls
bin
boot
dev
etc
home
lib
lib32
lib64
libx32
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
Bash

プロンプトがないのでわかりにくいですが、一番上の ls は自分で入力したコマンドです。
-i オプションがあって初めて、コマンドの入力が可能となります。
逆に -i オプションだけでコンテナに対する操作はできます(-tは必要ない)。

ちなみに、以下のように vim をインストールして起動することもできました。

$ apt-get update -y && apt-get install vim -y

$ vim
Bash

パイプと組み合わせてみる(標準入力)

単体でのコンテナ起動の挙動は確認できました。
今度はパイプと組み合わせた場合の挙動について確認してみます。

echo ls | ... というコマンド形式で、コンテナ起動時に標準入力を受け取るようにしてみます。

-i オプション有り、 -t オプション有り

両方のオプションを付与した場合は、正常に動作しません。

$ echo ls | docker run -it ubuntu:19.10 bash

the input device is not a TTY
Bash

入力デバイスがTTYでないので -t を付与してはいけないようです。

-i オプション無し、 -t オプション無し

以下のコマンドでは何も出力されません。
コンテナが標準入力を受け付けないためです。

$ echo ls | docker run ubuntu:19.10 bash
Bash

以下のコマンドを実行したのと同じです。

$ echo ls; docker run ubuntu:19.10 bash
Bash

-i オプション無し、 -t オプション有り

インタラクティブシェルが起動しますが、標準入力を受け取るようにしていないため何もできません。

$ echo ls | docker run -t ubuntu:19.10 bash

root@b60730972a07:/# 
Bash

以下のコマンドを実行したのと同じです。

$ echo ls; docker run -t ubuntu:19.10 bash
Bash

-i オプション有り、 -t オプション無し

正しく標準入力を受け取り、 ls コマンドが実行されます。
実行後、コンテナは終了します。

$ echo ls | docker run -i ubuntu:19.10 bash

bin
boot
dev
etc
home
lib
lib32
lib64
libx32
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
Bash

パイプと組み合わせてみる(標準出力)

-i オプション有り、 -t オプション有り

何故か、おかしなインデントが出力されてしまいます。

$ docker run -it ubuntu:19.10 echo '{ "x": 1 }' | jq .
{
   "x": 1
         }
Bash

-i オプション無し、 -t オプション無し

正常に動作。

$ docker run ubuntu:19.10 echo '{ "x": 1 }' | jq .
{
  "x": 1
}
Bash

-i オプション無し、 -t オプション有り

正常に動作。

$ docker run -i ubuntu:19.10 echo '{ "x": 1 }' | jq .
{
  "x": 1
}
Bash

-i オプション有り、 -t オプション無し

正常に動作。

$ docker run -t ubuntu:19.10 echo '{ "x": 1 }' | jq .
{
  "x": 1
}
Bash

フィルダリングを行うようなコンテナ起動を行う場合

標準入力を受け取り、標準出力を送り込むコンテナ起動用のコマンドは以下のようになります。

$ echo 123 | docker run -i ubuntu:19.10 sed 's/2/---/' | sed 's/3/+++/'
1---+++
Bash

ひとこと

コンテナにフィルダリング処理をさせるという発想はどうなんでしょうね。
問題があるのかないのか。

Docker