スポンサーリンク
Windows上のJupyter Notebook(Python 3)から、リモートサーバーにSSH接続してコマンドを実行する際に、いろいろ手間取ったので、ポイントをメモしておきます。
またおまけとして、リモートサーバー上にあるCSVファイルをSSH経由で読み込んで、DataFrameにする方法も書いておきます。
目次
スポンサーリンク
paramikoパッケージを利用
今回SSHを利用するために、paramikoパッケージをAnaconda Navigatorを使ってインストールしておきました(手順は、こちらのPyMySQLのインストール手順と同様です)。
公開鍵用のパスワードを毎回入力する
パスワードをNotebookに直接記載するのは気が引けます。パスワードを利用する場合、環境変数を使う方法をよく見かけるのですが、今回はgetpassパッケージを使うことにしました。
「import getpass」してから「password = getpass.getpass("Password: "」と記述すると、セルの下に入力欄が表示されて、入力内容がpassword変数に入ります。
事前のknown_hostsファイルを作っておく
ssh接続する前に、あらかじめ空ファイルでいいので、「known_hosts」ファイルを作っておく必要がありました。
標準的には、「C:\User\(ユーザー名)\.ssh\known_hosts」にファイルを作ります。.sshフォルダがなければ、sshフォルダごと作成しておきます。
これを作っておかないと、この後の手順で「FileNotFoundError: [Errno 2] No such file or directory:」になってしまいました。
公開鍵を使ってSSH接続してコマンドの実行結果を出力する
あまりに簡単なサンプルだと、さっぱり公開鍵認証を使ってくれなかったりするのですが、もちろん公開鍵認証を使いたいので、結果こんなコードになりました:
import os import getpass import paramiko password = getpass.getpass("Password: ") username = 'ユーザー名' ip_addr = 'IPアドレス' port = 22 # 22はやめておくのが吉 path_to_public_key = 'C:\\path\\to\\public_key\\filename' client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.load_host_keys(os.path.expanduser('~\\.ssh\\known_hosts')) client.connect(ip_addr, username=username, password=password, port=port, key_filename=path_to_public_key) stdin, stdout, stderr = client.exec_command('ls -l') for line in stdout: print(line, end='')
ポイントは色々あるので以下に:
「SSHException: Server '[1.2.3.4]:22' not found in known_hosts」エラー
一番さっぱり分からなかったのがこの「SSHException: Server '[160.16.214.220]:19880' not found in known_hosts」というエラーでした。
このエラーは要するに、普通のSSHクライアントなら初回接続時にknown_hostsに追加するかを聞いてくるところを、「known_hostsに無いからダメ、おわり」と切断されてしまう状態でした。
これを回避しているのが、「client.set_missing_host_key_policy(paramiko.AutoAddPolicy())」というコードです。
参考:paramiko does not automatically add unknown hosts · Issue #11 · onyxfish/relay
stdout.read()は微妙
exec_commandは、実行結果を「stdin, stdout, stderr」で返してくれます。それは良いのですが、このstdoutという変数から、どう結果文字列を取り出すかが問題 です。
read()を使えば、結果を丸ごと取り出せるようだったのですが、str型ではないため、strのように扱うと「TypeError: a bytes-like object is required, not 'str'」になってしまいます。
このstdoutオブジェクトの型はtype関数で調べるとparamiko.channel.ChannelFileです。呼び出せるメソッドはどうやら「Buffered files — Paramiko documentation」の通りで、strで返してくれる他のメソッドとしては、readlineやreadlinesがありました。
ただし、「__iter__」が実装されているので、「for line in stdout:」と1行ずつループさせることができて、この方法が使い勝手が良さそうでした。
改行文字が付く
ひとつ厄介なのが、1行ずつ取り出した時に、改行文字(\n)が付いてくることです。なので、「print(line)」としてしまうと、print関数自体の改行も加わって、空行が1行ごとに挟まってしまいます。
それを回避するために、print関数が改行しないように、「print(line, end='')」という記述を使っています。
closeする
closeするように、tryで囲っておくと良いです。ドキュメントのClientのページにwithや__enter__ / __exit__ について言及がなくwithに対応していないのかとも思ったのですが、ソースを見てみるとSSHClientクラスがClosingContextManagerクラスを継承しており、そちらで__enter__ / __exit__が実装されていました。なので、withを使って(closeを手で書かずに)次のように書けば良さそうです。
import os import getpass import paramiko password = getpass.getpass("Password: ") username = 'ユーザー名' ip_addr = 'IPアドレス' port = 22 # 22はやめておくのが吉 path_to_public_key = 'C:\\path\\to\\public_key\\filename' with paramiko.SSHClient() as client client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.load_host_keys(os.path.expanduser('~\\.ssh\\known_hosts')) client.connect(ip_addr, username=username, password=password, port=port, key_filename=path_to_public_key) stdin, stdout, stderr = client.exec_command('ls -l') for line in stdout: print(line, end='')
ファイルに出力する
コマンド実行時に、「client.exec_command('ls -l > output')」のようにすれば、「output」というファイルにコマンド実行結果をリダイレクトすることももちろんできます。
CSVファイルをSSH経由で読み込む
「stdin, stdout, stderr = client.exec_command('cat posts-2016.csv')」のようにしてcsvファイルを標準出力&SSH経由でstdoutバッファに向けた状態で
import pandas as pd pd.read_csv(stdout)
のようにすることで、csvファイルをDataFrameとして読み込むことができます。
この.csvファイルは、「【MySQL】SELECTの結果をCSVファイルに出力してExcelで編集したい」の記事中で、MySQLからCSV出力したものです。
ただこれについて今となっては、Jupyter NotebookからMySQLに直接接続する方法がおすすめです。
スポンサーリンク
スポンサーリンク