明らかに、同じシェルが同じサーバーへの複数のSSH接続を開始すると、指定されたコマンドを実行した後に返されずにStopped (tty input)
永久に停止します()。表示するには:
#!/bin/bash
ssh localhost sleep 2
echo "$$ DONE!"
上記のスクリプトをバックグラウンドで複数回実行すると、決して終了しません。
$ for i in {1..3}; do foo.sh & done
[1] 28695
[2] 28696
[3] 28697
$ ## Hit enter
[1] Stopped foo.sh
[2]- Stopped foo.sh
[3]+ Stopped foo.sh
$ ## Hit enter again
$ jobs -l
[1] 28695 Stopped (tty input) foo.sh
[2]- 28696 Stopped (tty input) foo.sh
[3]+ 28697 Stopped (tty input) foo.sh
詳細
- Perlスクリプトでsshを介してコマンドを実行していたので、これを見つけました。 Perlを使用して
system()
launchを呼び出すときも同じ動作が発生しますssh
。 - 代わりにPerlモジュールを使用してみまし
system()
たNet::SSH::Perl
。Net:SSH2
Net::OpenSSH
- 異なるシェルで複数のsshコマンドを実行すると(複数の端末を開く)、期待どおりに機能します。
SSH接続デバッグ情報には明らかに有用なものはありません。
OpenSSH_7.5p1, OpenSSL 1.1.0f 25 May 2017 debug1: Reading configuration data /home/terdon/.ssh/config debug1: Reading configuration data /etc/ssh/ssh_config debug2: resolving "localhost" port 22 debug2: ssh_connect_direct: needpriv 0 debug1: Connecting to localhost [::1] port 22. debug1: Connection established. debug1: identity file /home/terdon/.ssh/id_rsa type 1 debug1: key_load_public: No such file or directory debug1: identity file /home/terdon/.ssh/id_rsa-cert type -1 debug1: key_load_public: No such file or directory debug1: identity file /home/terdon/.ssh/id_dsa type -1 debug1: key_load_public: No such file or directory debug1: identity file /home/terdon/.ssh/id_dsa-cert type -1 debug1: key_load_public: No such file or directory debug1: identity file /home/terdon/.ssh/id_ecdsa type -1 debug1: key_load_public: No such file or directory debug1: identity file /home/terdon/.ssh/id_ecdsa-cert type -1 debug1: key_load_public: No such file or directory debug1: identity file /home/terdon/.ssh/id_ed25519 type -1 debug1: key_load_public: No such file or directory debug1: identity file /home/terdon/.ssh/id_ed25519-cert type -1 debug1: Enabling compatibility mode for protocol 2.0 debug1: Local version string SSH-2.0-OpenSSH_7.5 debug1: Remote protocol version 2.0, remote software version OpenSSH_7.5 debug1: match: OpenSSH_7.5 pat OpenSSH* compat 0x04000000 debug2: fd 3 setting O_NONBLOCK debug1: Authenticating to localhost:22 as 'terdon' debug3: hostkeys_foreach: reading file "/home/terdon/.ssh/known_hosts" debug3: record_hostkey: found key type ECDSA in file /home/terdon/.ssh/known_hosts:47 debug3: load_hostkeys: loaded 1 keys from localhost debug3: order_hostkeyalgs: prefer hostkeyalgs: [email protected],[email protected],[email protected],ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521 debug3: send packet: type 20 debug1: SSH2_MSG_KEXINIT sent debug3: receive packet: type 20 debug1: SSH2_MSG_KEXINIT received debug2: local client KEXINIT proposal debug2: KEX algorithms: curve25519-sha256,[email protected],ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1,ext-info-c debug2: host key algorithms: [email protected],[email protected],[email protected],ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,[email protected],[email protected],ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa debug2: ciphers ctos: [email protected],aes128-ctr,aes192-ctr,aes256-ctr,[email protected],[email protected],aes128-cbc,aes192-cbc,aes256-cbc debug2: ciphers stoc: [email protected],aes128-ctr,aes192-ctr,aes256-ctr,[email protected],[email protected],aes128-cbc,aes192-cbc,aes256-cbc debug2: MACs ctos: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],hmac-sha2-256,hmac-sha2-512,hmac-sha1 debug2: MACs stoc: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],hmac-sha2-256,hmac-sha2-512,hmac-sha1 debug2: compression ctos: none,[email protected],zlib debug2: compression stoc: none,[email protected],zlib debug2: languages ctos: debug2: languages stoc: debug2: first_kex_follows 0 debug2: reserved 0 debug2: peer server KEXINIT proposal debug2: KEX algorithms: curve25519-sha256,[email protected],ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1 debug2: host key algorithms: ssh-rsa,rsa-sha2-512,rsa-sha2-256,ecdsa-sha2-nistp256,ssh-ed25519 debug2: ciphers ctos: [email protected],aes128-ctr,aes192-ctr,aes256-ctr,[email protected],[email protected] debug2: ciphers stoc: [email protected],aes128-ctr,aes192-ctr,aes256-ctr,[email protected],[email protected] debug2: MACs ctos: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],hmac-sha2-256,hmac-sha2-512,hmac-sha1 debug2: MACs stoc: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],hmac-sha2-256,hmac-sha2-512,hmac-sha1 debug2: compression ctos: none,[email protected] debug2: compression stoc: none,[email protected] debug2: languages ctos: debug2: languages stoc: debug2: first_kex_follows 0 debug2: reserved 0 debug1: kex: algorithm: curve25519-sha256 debug1: kex: host key algorithm: ecdsa-sha2-nistp256 debug1: kex: server->client cipher: [email protected] MAC: <implicit> compression: none debug1: kex: client->server cipher: [email protected] MAC: <implicit> compression: none debug3: send packet: type 30 debug1: expecting SSH2_MSG_KEX_ECDH_REPLY debug3: receive packet: type 31 debug1: Server host key: ecdsa-sha2-nistp256 SHA256:uxhkh+gGPiCJQPaP024WXHth382h3BTs7QdGMokB9VM debug3: hostkeys_foreach: reading file "/home/terdon/.ssh/known_hosts" debug3: record_hostkey: found key type ECDSA in file /home/terdon/.ssh/known_hosts:47 debug3: load_hostkeys: loaded 1 keys from localhost debug1: Host 'localhost' is known and matches the ECDSA host key. debug1: Found key in /home/terdon/.ssh/known_hosts:47 debug3: send packet: type 21 debug2: set_newkeys: mode 1 debug1: rekey after 134217728 blocks debug1: SSH2_MSG_NEWKEYS sent debug1: expecting SSH2_MSG_NEWKEYS debug3: receive packet: type 21 debug1: SSH2_MSG_NEWKEYS received debug2: set_newkeys: mode 0 debug1: rekey after 134217728 blocks debug2: key: /home/terdon/.ssh/id_rsa (0x555a5e4b5060) debug2: key: /home/terdon/.ssh/id_dsa ((nil)) debug2: key: /home/terdon/.ssh/id_ecdsa ((nil)) debug2: key: /home/terdon/.ssh/id_ed25519 ((nil)) debug3: send packet: type 5 debug3: receive packet: type 7 debug1: SSH2_MSG_EXT_INFO received debug1: kex_input_ext_info: server-sig-algs=<ssh-ed25519,ssh-rsa,rsa-sha2-256,rsa-sha2-512,ssh-dss,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521> debug3: receive packet: type 6 debug2: service_accept: ssh-userauth debug1: SSH2_MSG_SERVICE_ACCEPT received debug3: send packet: type 50 debug3: receive packet: type 51 debug1: Authentications that can continue: publickey,password debug3: start over, passed a different list publickey,password debug3: preferred publickey,keyboard-interactive,password debug3: authmethod_lookup publickey debug3: remaining preferred: keyboard-interactive,password debug3: authmethod_is_enabled publickey debug1: Next authentication method: publickey debug1: Offering RSA public key: /home/terdon/.ssh/id_rsa debug3: send_pubkey_test debug3: send packet: type 50 debug2: we sent a publickey packet, wait for reply debug3: receive packet: type 60 debug1: Server accepts key: pkalg rsa-sha2-512 blen 279 debug2: input_userauth_pk_ok: fp SHA256:OGvtyUIFJw426w/FK/RvIhsykeP8kIEAtAeZwYBIzok debug3: sign_and_send_pubkey: RSA SHA256:OGvtyUIFJw426w/FK/RvIhsykeP8kIEAtAeZwYBIzok debug3: send packet: type 50 debug3: receive packet: type 52 debug1: Authentication succeeded (publickey). Authenticated to localhost ([::1]:22). debug2: fd 6 setting O_NONBLOCK debug1: channel 0: new [client-session] debug3: ssh_session2_open: channel_new: 0 debug2: channel 0: send open debug3: send packet: type 90 debug1: Requesting [email protected] debug3: send packet: type 80 debug1: Entering interactive session. debug1: pledge: network debug3: receive packet: type 80 debug1: client_input_global_request: rtype [email protected] want_reply 0 debug3: receive packet: type 91 debug2: callback start debug2: fd 3 setting TCP_NODELAY debug3: ssh_packet_set_tos: set IPV6_TCLASS 0x08 debug2: client_session2_setup: id 0 debug1: Sending command: sleep 2 debug2: channel 0: request exec confirm 1 debug3: send packet: type 98 debug2: callback done debug2: channel 0: open confirm rwindow 0 rmax 32768 debug2: channel 0: rcvd adjust 2097152 debug3: receive packet: type 99 debug2: channel_input_status_confirm: type 99 id 0 debug2: exec request accepted on channel 0
これは私の設定には依存しません
~/.ssh/config
。ファイル名を変更すると、何も変更されません。- これは複数のコンピュータで発生します。最新のUbuntuとArchディストリビューションを実行している4〜5台の異なるコンピュータを試してみました。
- コマンド(
sleep
ダミーの例ではより複雑ですが、実際の生活ではより複雑です)が正常に終了し、予想される操作を実行します。これは実行中のコマンドに依存せず、SSHの問題です。 - そのうち最悪は次のとおりです。一貫性がない。場合によっては、インスタンスの1つがシャットダウンされ、コントロールが親スクリプトに返されます。しかし、必ずしもそうではなく、どのパターンも識別できません。
- 名前を変更しても
~/.bashrc
違いはありません。また、Ubuntu(デフォルトのログインシェルdash
)とArch(デフォルトのログインシェルbash
と呼ばれる)を実行しているsh
システムでもこれを実行しました。 - Enter興味深いことに、問題はループの開始後に最初のスクリプトが終了する前に任意のキーを押すと発生します(たとえば、キーが機能しているようです)。端末をそのままにすると、期待どおりに完了します。
どうなりますか?これはsshのバグですか?オプションを設定する必要がありますか?同じシェルでSSHを介してコマンドを実行するスクリプトの複数のインスタンスをどのように起動できますか?
答え1
フォアグラウンドプロセスと端末アクセス制御
何が起こっているのかを理解するには、共有端末について少し知っておく必要があります。両方のプログラムが同時に同じ端末で読み取ろうとするとどうなりますか?各入力バイトはプログラムの一つにランダムに入ります。 (カーネルでRNGを使用して決定されたとおり、ランダムではなく実際には予測できないため、ランダムです。)2つのプログラムがパイプまたは別のファイルタイプから読み取ると(ある場所でバイトストリームを移動する)、何が起こりますか?すべてのバイトを複数回読み取ることができるバイト配列(通常のファイル、ブロックデバイス)とは異なり、他のデバイス(ソケット、文字デバイスなど)で発生します。たとえば、端末でシェルを実行するには、端末名を見つけて実行しますcat
。
$ tty
/dev/pts/18
$ cat
その後、別の端末で実行しますcat /dev/pts/18
。端末に入力し、時にはcat
プロセスの1つに移動し、時には別のプロセスに移動する行を確認します。ターミナルがベーキングモードの場合、ラインは完全に予約されます。端末をネイティブモードに切り替えると、各バイトは独立して転送されます。
厄介です。確かに、あるプログラムが端末を取得し、他のプログラムがそうでないことを決定するメカニズムが必要です。まあ!一般的な状況では発生しますが、上記で設定したシナリオでは発生しません。この状況はcat /dev/pts/18
最初から始まらないため、一般的ではありません/dev/pts/18
。端末内で実行されていないプログラムから端末に接続することは一般的ではありません。一般的な状況では、端末でシェルを実行し、そのシェルでプログラムを実行します。これにより、フォアグラウンドのプログラムは端末を取得しますが、バックグラウンドのプログラムは端末を取得できないのがルールです。これは…ターミナル出入管理。仕組みは次のとおりです。
- すべてのプロセスに制御端子(または通常、端末で開かれたファイル記述子がないため、そうではありません。)
- プロセスが制御端末にアクセスしようとしたときにプロセスが前景にない場合、カーネルはそれをブロックします。 (制限事項が適用されます。他の端末へのアクセスは制限されません。)
- シェルは、誰がフォアグラウンドプロセスかを決定します。 (実際にはフォアグラウンドプロセスグループです。)
tcsetpgrp
誰が前景にあるべきかをカーネルに伝えます。
これは一般的な場合に機能します。シェルでプログラムを実行すると、そのプログラムはフォアグラウンドプロセスになります。バックグラウンドでプログラムを実行すると(使用&
)、プログラムはフォアグラウンドで実行されません。シェルがプロンプトを表示すると、シェルは自分を前景にインポートします。一時停止したジョブを再開すると、ジョブはfg
フォアグラウンドになります。のbg
場合はそうではありません。
バックグラウンドプロセスが端末からデータを読み取ろうとすると、カーネルはSIGTTIN信号を送信します。この信号の基本的な作業は、プロセスを一時停止することです(例:SIGSTOP)。プロセスの親は呼び出しによってこれを学ぶことができます。waitpid
フラグを使用すると、WSTOPPED
子プロセスが停止信号を受信すると、waitpid
親プロセスの呼び出しが返され、親プロセスにシグナルが何であるかが通知されます。これは、シェルが「停止(tty入力)」を印刷する方法を知る方法です。 SIGTTIN によりジョブが中断されたことを通知します。
プロセスが一時停止しているため、再開または終了するまで何も起こりません(プロセスにシグナルハンドラが設定されている場合、プロセスは一時停止してから実行されないため、プロセスはシグナルをキャプチャしません)。 SIGCONTを送信してプロセスを再開できますが、プロセスが端末からデータを読み取る場合は効果がなく、すぐに別のSIGTTINを受け取ります。再開プロセスを使用すると、fg
フォアグラウンドに移動して読み取りが成功します。
cat
これで、バックグラウンドで実行すると何が起こるかがわかります。
$ cat &
$
[1] + Stopped (tty input) cat
$
SSHの場合
それではSSHを使って同じことをしましょう。
$ ssh localhost sleep 999999 &
$
$
$
[1] + Stopped (tty input) ssh localhost sleep 999999
$
キーを押すと、Enter時にはシェル(前景)に移動し、時にはSSHプロセスに移動します(この場合はSIGTTINによって中止されます)。なぜ?端末から読む場合は、ssh
すぐにSIGTTINを受信する必要があります。そうでない場合、SIGTTINを受信するのはなぜですか?
何が起こるかは、SSHプロセスが呼び出されることです。select
関心のあるファイルへの入力がいつ利用可能か(または出力ファイルがより多くのデータを受信する準備ができたか)を知るためのシステムコール。入力ソースには、少なくとも端末とネットワークソケットが含まれます。とは異なり、read
バックグラウンドselect
プロセスは無効にされず、ssh
呼び出し時にSIGTTINを受信しませんselect
。目標select
は、何も破壊せずにデータが利用可能であることを確認することです。理想的にはselect
システムの状態はまったく変わりませんが、実際にはこれはまったく真実ではありません。select
端末ファイル記述子で入力が利用可能であることをSSHプロセスに通知すると、プロセスが後で入力を呼び出すと、read
カーネルは入力転送をコミットする必要があります。 (そうでない場合、プロセスが呼び出されると、その時点read
で使用可能な入力がない可能性があるため、戻り値はselect
嘘になります。)したがって、カーネルが一部の入力をSSHプロセスにルーティングすることを決定した場合、システムがいつ実行されるかが決まります。電話が戻りますselect
。次にSSHが呼び出されread
、この時点でカーネルはバックグラウンドプロセスが端末からデータを読み取ろうとし、SIGTTINでそれを一時停止します。
同じサーバーに対して複数の接続を開始する必要はありません。一つで十分です。複数の接続によって問題が発生する可能性が高くなります。
解決策:端末から読まないでください。
端末からデータを読み取るためにSSHセッションが必要な場合は、フォアグラウンドで実行してください。
端末からデータを読み取るためにSSHセッションが必要ない場合は、入力が端末から出ていないことを確認してください。これを行う方法は2つあります。
入力をリダイレクトできます。
ssh … </dev/null
-n
端末接続を転送しないようにSSHを使用または指示できます-f
。 (-n
と同じです</dev/null
。SSH-f
自体が端末から読み取ることができます(パスワードを読むなど)。ただし、コマンド自体は端末を開けません。)ssh -n …
端末とSSHの間の切断は、クライアント側で行わなければなりません。サーバーで実行されているプロセスはsleep
端末からデータを読み取ることはありませんが、SSHはこれを知りません。クライアントが標準入力から入力を受け取ったら、それをサーバーに渡す必要があります。これにより、アプリケーションがそれを読み取りに決定した場合、バッファーでデータを使用できるようになります。アプリケーションが を呼び出すと、select
データが利用できることを知らせます。 )。
答え2
マンページでヘルプを見つけることができます。
-n Redirects stdin from /dev/null (actually, prevents reading from stdin). This must be used when ssh is run in the
background. A common trick is to use this to run X11 programs on a remote machine. For example, ssh -n
shadows.cs.hut.fi emacs & will start an emacs on shadows.cs.hut.fi, and the X11 connection will be automatically
forwarded over an encrypted channel. The ssh program will be put in the background. (This does not work if ssh
needs to ask for a password or passphrase; see also the -f option.)
-T
それでも問題が解決しない場合は、即興(仮想tty割り当てを無効にする)を試してみましょう。
答え3
明らかに、同じシェルが同じサーバーへの複数のSSH接続を開始すると、指定されたコマンドを実行した後に返されず、永久に停止します(中止(tty入力))。
これは、TTYへの同時アクセスの一般的な動作です。プロセス全体がすでにバックグラウンドで実行されており、出力を書き込もうとすると、TTYへのアクセスが許可されておらず、プロセスが捕捉できSIGTTOU
ない信号()を受け取るため、基本操作()が実行されます。bash
Stop
この-n
オプションについては後述する。その他の回答あるいは、IOをいくつかのファイルにリダイレクトすると便利です。もっと説明する内容があるかどうかはわかりませんが、あれば説明してください。