Python」タグアーカイブ

os.forkpty

あけましておめでとうございます。
元旦といえば、そうプログラミングです。

ということで、Python で sshpass を再現の話。
os.forkpty というものがあるようだ。

python – Using os.forkpty() to create a pseudo-terminal to ssh to a remote server and communicate with it – Stack Overflow

これだよコレ、似たようなことを考える人っているもんだ。
がんばって sshpass と完全に同じコードにしなくてもよかった。
というか ptmx では上手くいかない、擬似端末ならこっちでもいいはず。

Python3 に書き換えしなきゃね。
色々問題はあるけどなんとかなったコードをとりあえず。

#!/usr/bin/env python3

import os, sys, signal, time

# var
HOSTNAME = 'username@hostname'
PASSWORD = '********'

# Ctrl+C
signal.signal(signal.SIGINT, signal.SIG_DFL)

pid, fd = os.forkpty()

def cmd(s):
    '''
        長い出力の場合分割される場合がある
        os.read はこの場合ループにすると値を戻さずフリーズする
        しかたがないので空打ちの場合は残りの読み込みにしている
    '''
    if s:
        os.write(fd, f'{s}\n'.encode('utf-8'))
        time.sleep(1)
        res = os.read(fd, 1024).decode()
        # 一行目を取り除いて表示
        sys.stdout.write(res[res.find('\n')+1 : ])
    else:
        res = os.read(fd, 1024).decode()
        sys.stdout.write(res)
    sys.stdout.flush()

if pid == 0:
    #os.setsid() # Error
    os.execvp('ssh',['ssh', HOSTNAME])
    # 親プロセスに切り替わるので以下は実行されない
    print('execvp Error!')

# レスポンスが遅いと表示されないけどログインは可能
output = os.read(fd, 1024)
sys.stdout.write(output.decode())
sys.stdout.flush()
# パスいワード
os.write(fd, f'{PASSWORD}\n'.encode('utf-8'))
# 只の時間稼ぎ
time.sleep(1)
res = os.read(fd, 1024).decode()
# 最初のプロンプトが表示できないので仮プロンプト
print(f'{res}First Command > ', end='')
# exit で終了
while True:
    s = input()
    if s == 'exit':
        break
    cmd(s)
os.write(fd, 'exit\n'.encode('utf-8'))
print('__DONE__')

よしこれで sshpass 不要でパスワード入力ができる。
しかし time.sleep を駆使するしかないのかな?
今のところこんな感じ。

調子こいて pysshpass みたいなものを作ろうと思ったけど…
os.forkpty では setsid できないし ptmx だと暴走するしetc…
我が Macbook Air の CPU ファン全開音なんて初めて聞いたよ。
ま、必要ないか。

しかし思っていたより Fedora と macOS では使える関数が違う。
os.fsync は macOS で問題ないけど Fedora は何をやってもエラー。
fcntl でノンブロッキングI/O は macOS ではエラー出まくり。
他の Linux や BSD では、なんて考えたくないな。

ptmx

sftp アプリを PyObjC で作るって話。
やってます、上手くいかないだけです。
とりあえず今やっていること。

sshpass/main.c at master ? bauruine/sshpass ? GitHub

sshpass のソースってたったコレだけなんだよね。
これなら Python3 の標準モジュールだけで模写できそうだと考えた。
UNIX の中身の勉強にもなるし一石二鳥。

/dev/ptmx って何だ?

Ubuntu Manpage: ptmx, pts – 擬似端末のマスタとスレーブ

たしかに sudo や ssh は stdin やパイプからの入力はできない。
/dev/ptmx を利用すればなんとかなるってことね。

#!/usr/bin/env python3

import os, signal, fcntl

# Ctrl+C
signal.signal(signal.SIGINT, signal.SIG_DFL)

# ptmx
masterpt = os.open('/dev/ptmx', os.O_RDWR)

# not macOS
#fcntl.fcntl(masterpt, fcntl.F_SETFL, os.O_NONBLOCK)
#name = os.ttyname(masterpt)

childpid = os.fork()
if childpid == 0:
    os.setsid()
    #os.execvp('ssh', ['ssh', 'sasakima-nao@mba2.local'])
    os.execvp('python3', ['python3'])
    # Error
    print(f'{__file__}: Failed to run command')

#slavept = os.open(name, os.O_RDWR|os.O_NOCTTY)

os.close(masterpt)
#os.close(slavept)
print(f'{__file__}: EOL')

とりあえず fork して execvp することはできた。
呼び出し側のプロセスを終了して子プロセスに置き換える。
ほぼ os モジュールだけでなんとかなるのようで。

Man page of EXEC

起動すると EOL が表示され Python3 インタプリタが始まる。
Ctrl+D すると bash はもう破棄されているので gnome-terminal が終了する。
macOS では「プロセスが完了しました」と出る。

で、ssh のほうの現状。

こんなになってしまう。
sshpass.c をトレースしているつもりだけどまだ何か間違っているっぽい。
上手くいったらまた今度。

subprocess run Popen

そういえば最近このブログは subprocess.Popen を使っているけど。
subprocess.run で全部まかなえるようになったんじゃなかったっけ?
Popen を使う理由は何だろう。

subprocessでPythonからLinuxコマンド実行

あぁ戻り値を得ないならば非同期実行になるってことね。
しかしなんともなサンプルコードだ、多分 Windows なんだろうけど。
UNIX 系ならこんなに単純なサンプルコードにできる。

#!/usr/bin/env python3

import subprocess

subprocess.Popen(['sh', '-c', 'sleep 3; echo First'])
subprocess.Popen(['sh', '-c', 'echo Second'])

print('__done__')

__done__ が一番最初に表示されるね、なるほど。
ただし with as を使うと戻り値を使うのと同様になるので順番どおりになる。

#!/usr/bin/env python3

import subprocess

with subprocess.Popen(['sh', '-c', 'sleep 3; echo First']) as p:
    pass
with subprocess.Popen(['sh', '-c', 'echo Second']) as q:
    pass

print('__done__')

この特性を上手く利用すれば色々便利になりそう。

subprocess readline loop

sftp を subprocess で関連は色々詰まって全然進まない。
特に stdout.readline は検索すると同じ事で詰まっている人ばかり見つかるってどうよ。
そうです、readline でループすると値が戻らず止まってしまうんです。
俺だけじゃなかった!なんて安心してもしょーがないんですけど。

python – catching stdout in realtime from subprocess – Stack Overflow

上記で上手くいっている人と駄目な人がいるみたいだけーが。
exe ってことは Windows なら問題ないってことなのかも、知らんけど。

Pythonでサブプロセスと対話する – 西尾泰和のはてなダイアリー

上記でやっとなんとかなった。
サンプルコード、bash のエミュレーター。

#!/usr/bin/env python3

import sys, subprocess, os

with subprocess.Popen(['/bin/bash'], encoding='UTF-8', stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as p:
    while True:
        try:
            s = input('test > ')
        except EOFError:
            # Ctrl+D
            break
        p.stdin.write(f'{s}\n')
        p.stdin.flush()
        if s == '':
            continue
        if s == 'exit':
            break
        r = os.read(p.stdout.fileno(), 1024)
        sys.stdout.write(r.decode())
        sys.stdout.flush()
        # not UNIX ?
        #for line in iter(p.stdout.readline, b''):
        #    print(line.rstrip())

print('__done__')

os.read はファイルオブジェクトに使わないでって書いているけど動くようだ。
os — 雑多なオペレーティングシステムインタフェース ? Python 3.8.0 ドキュメント
ls の出力が改行されてしまう理由は解らない。
subprocess ごときにこんなに苦戦するとは思わなかったぞ。

subprocess sftp

sftp コマンドをラッピングしてアプリを自作するのもアリかも。
と前回書いたし自分で作ろうかなって。
Python なら subprocess であっさり作れそう。
後は PyObjC あたりで GUI を作ればいい。

まず問題なのがパスワード入力をどうするかってこと。
すぐに見つかったけど sshpass コマンドを使えば簡単にできるようだ。
Fedora には最初からあるけど macOS には無いんだなぁ。
まあいい後回し、今回は Fedora から接続で試す。

次は接続されるのを待つ必要があるな。

Python の subprocess – Qiita

この一番下みたいに開始の出力を得ればなんとかなりそう。
色々試したけど Connected to … は stderr 出力だ、なんだそれ。

Pythonでデッドロックを回避しながらサブプロセスの標準出力を1行ずつ読み込む – 物理の駅 by onsanai

デッドロックしまくったけどコレをみつけてなんとかなった。
色々遠回りしたけどなんとかなったコード。

#!/usr/bin/env python3

import shlex, subprocess

args = shlex.split('sshpass -p PASSWORD sftp USERNAME@HOSTNAME')

with subprocess.Popen(args, encoding='UTF-8', stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as p:
    # wait Connect
    while True:
        line = p.stdout.readline()
        print(line)
        if 'Connected' in line:
            break
    # Start
    s = input('test > ')
    outs, errs = p.communicate(input=s, timeout=15)
    print(outs)

communicate ではプロセスが終わってしまうんだよな。
bye を送るまで while ループさせる所で今つまっている。

Paramiko 使えってのは無しにして、それじゃつまらないし勉強にもならない。
外部モジュールで満足する人はフリーソフトのインストールで満足しちゃう人だと思う。