sshと疑似端末

リモートのホストでコンパイル(make)やテストを実行して、その出力(ログ)をファイルに保存してみると、出力が期待通りでないことに気付く。標準出力と標準エラー出力のタイミングが、端末で見たときのそれと異なっている。

sshでエディタのような対話式のコマンドをリモート実行するとき、-tオプションを使用する。このsshの-tオプション(リモート側の疑似端末の割り当て)と、ローカル側の標準入力が端末かどうかで、どのように振る舞いが変わるかを調べ、期待通りの結果を得る。


実験

次のようなシェルスクリプトを用意する。

$ cat ~/command.sh
#!/bin/sh
echo "STDOUT 1"
echo "STDERR 1" >&2
echo "STDOUT 2"
echo "STDERR 2" >&2
echo "STDOUT 3"
echo "STDERR 3" >&2
$

sshでリモートのホストにログインしてスクリプトを実行し、次のような期待通りの結果が得られるか確認する(リモートのホストには以降の実験のためにパスワードを入力せずにログインできるようにしておく必要がある。また、ホストはlocalhostでなくてもよい)。

$ ssh localhost
...
$ ~/command.sh
STDOUT 1
STDERR 1
STDOUT 2
STDERR 2
STDOUT 3
STDERR 3
$ exit
$

今度はsshにコマンドを与えて実行させてみる。次のような結果が得られるか確認する。

$ ssh localhost ~/command.sh
STDOUT 1
STDOUT 2
STDOUT 3
STDERR 1
STDERR 2
STDERR 3
$

期待通りの結果が得られないことがわかる。この場合、リモート側で疑似端末が割り当てられない。また、ローカル側でも標準出力と標準エラー出力は別々に出力されるので、次のようにリダイレクトすることもできる。

$ ssh localhost ~/command.sh > /dev/null
STDERR 1
STDERR 2
STDERR 3
$

今度は-tオプションを追加して実行してみる(-qも指定しているが、ここでは説明を省略する)。

$ ssh -qt localhost ~/command.sh
STDOUT 1
STDERR 1
STDOUT 2
STDERR 2
STDOUT 3
STDERR 3
$

期待通りの結果が得られた。リモート側で疑似端末が割り当てられれば、リモート側の標準出力と標準エラー出力のタイミングがずれることはなくなる。ただし、次のように出力はすべてローカルの標準出力にまとめられていることに注意。

$ ssh -qt localhost ~/command.sh > /dev/null
$

今まではローカルの標準入力(ファイルディスクリプタ0)が端末であった。もし、標準入力がファイルやパイプだったらどうなるのか。次のようにして出力を確認してみよう。

$ ssh -qt localhost ~/command.sh < /dev/null
Pseudo-terminal will not be allocated because stdin is not a terminal.
STDOUT 1
STDOUT 2
STDOUT 3
STDERR 1
STDERR 2
STDERR 3
$

期待通りの結果が得られない(先頭の警告メッセージはローカルの標準エラー出力に出力されている)。sshのマニュアルを読むと、-tオプションを重ねれば、このような場合でも強制的に疑似端末を割り当ててくれるそうである。次のようにsshを実行して結果を確認してみる。

$ ssh -qtt localhost ~/command.sh < /dev/null
tcgetattr: Operation not supported by device
STDOUT 1
STDERR 1
STDOUT 2
STDERR 2
STDOUT 3
STDERR 3
$

警告が出るが、期待通りの結果が得られた(先頭の警告メッセージはやはりローカルの標準エラー出力に出力されている)。

標準入力に疑似端末を割り当ててからsshを実行すれば警告は出ない。BSDの場合、script(1)の引数にコマンドを指定できるので、script(1)にsshを実行させれば、次のように期待通りの結果が得られる。

$ script -q /dev/null ssh -qt localhost sh ~/test.sh < /dev/null
STDOUT 1
STDERR 1
STDOUT 2
STDERR 2
STDOUT 3
STDERR 3
$

結論

sshのリモート実行の出力結果を取得する場合、標準出力と標準エラー出力のタイミングを正しくするには、sshに-tまたは-ttを指定する。

次のような手段で、標準入力が端末でないプロセスが子プロセスでsshを実行させる場合、sshに-tオプションが必要。

次のような手段で、標準入力が端末でないプロセスが子プロセスでsshを実行する場合、sshに-ttオプションが必要(ただし、先頭に警告が出てしまう)。