Programming」カテゴリーアーカイブ

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

Atom: Script to atom-runner

macOS で Atom を使おうとしたら Script パッケージのアップデートが。
適用したらエラーで動かなくなった、なんじゃそりゃ。
バグ報告は上がっているようなので修正を待つとするか。

いや、ぶっちゃけ気に入らない所も多かったし別の手段も考えよう。
せっかくなので自作、も考えたけどアウトプットパネルはどうしよう?
Gedit みたいに標準では付いていないみたいだし、うーん。
もっとシンプルなパッケージを探して参考にしようかなと。

オススメのatomパッケージ7選 – Qiita

atom-runner というのを試しに入れてみた。
これは別 Pane を使って出力するようだ、なるほど。
そのおかげかパッケージもシンプルで素敵。

てゆーか、コイツは shebang どおりにスクリプトを実行してくれるヤン!
対応言語なんて shebang を書くなら設定不要、拡張子無しもイケる。
何だよ自分で作らなくてもいいジャン、こいつを今度から使うことにする。

ただデフォルトの状態だと何か表示が変、無駄な余白がある。
下側ピッチリに表示させたいんだけど。

Atom-Runner, Moving output pane to right side – support – Atom Discussion

こんなのを見つけた。
試してみると -4 に指定すれば下に出るようになるな。

#pane = panes[panes.length - 1][dirfunc](view)
pane = panes[panes.length - 4][dirfunc](view)

それとカレントディレクトリが全部ルート (/) になるんだが。
ファイルの場所を指定するには、新しいペインを表示する前に記憶すりゃいいかも。

  run: (selection) ->
    # add
    path = atom.workspace.getActivePaneItem()?.buffer?.file?.path
    @cwd = p.dirname(path)

##################################

    try
      '''dir = atom.project.getPaths()[0] || '.'
      try
        if not fs.statSync(dir).isDirectory()
          throw new Error("Bad dir")
      catch
        dir = p.dirname(dir)
      @child = spawn(cmd, args, cwd: dir)'''
      @child = spawn(cmd, args, {cwd: @cwd})

それと esc を押したら Pane も削除してほしいな。
https://flight-manual.atom.io/api/v1.41.0/Pane/
Pane.destroy() でペインは削除できるようだ。

  stopAndClose: ->
    {pane, view} = @runnerView()
    pane?.removeItem(view)
    pane?.destroy() # add
    @stop(view)

よし後は run:file を F5 に割り付けして。

理想どおりになったけどかなり改造してしまった。
Atom を使う人はプログラマーだからこんな改造はみんなできるよね。