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

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 を使う人はプログラマーだからこんな改造はみんなできるよね。

GNOME 3.34 modal dialog bug

Fedora 31 の GNOME 3.34 にて。
Gedit でファイルの編集中にする。
F11 でフルスクリーンにする。
Ctrl+W でファイルを閉じようとして modal なアラートを出す。

何じゃこりゃ。

逆にフルスクリーンから戻すともっと悲惨。

自作アプリの Comipoli でファイル切り替えの動作が変だった。
モーダルダイアログを出すと一瞬だけ先頭ページが表示される現象に悩まされた。
これが原因じゃねーか、三日も悩んで損した。

GNOME だから 3.36 になるまでこのままなんだろうな。
しかたがないので Comipoli はしばらく GtkInfoBar に切り替えることに。

モーダルにできないのでキーボード処理は表示時のみ専用ハンドラに逃がして。
何故か use_markup が使えない、文字列だけにするしかないみたい。
gtk_info_bar_set_default_response が何故かエラーになる、困った。
スペースキーをガシガシするだけの特徴は残したいけど自前処理しか手段が無いな。
困った時の g_idle_add だ、この関数マジ便利。

#!/usr/bin/env python3

from gi.repository import GLib, Gtk

class ComipoliInfoBar (Gtk.InfoBar):
    def __init__(self):
        Gtk.InfoBar.__init__(self, no_show_all=True, valign=Gtk.Align.START)
        #
        self.uri = ''
        self.label = Gtk.Label(visible=True)
        area = self.get_content_area()
        area.add(self.label)
        self.ok = self.add_button('_OK', Gtk.ResponseType.OK)
        self.add_button('_Cancel', Gtk.ResponseType.CANCEL)

    def show_message(self, text, uri):
        self.uri = uri
        self.label.set_text(f'Next\n{text}\n\nEsc Cancel')
        GLib.idle_add(self._show_message)

    def _show_message(self):
        self.show()
        self.ok.props.has_focus = True
        return False

を GtkOverlay に乗せて。

class ComipoliWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        # InfoBar
        self.infobar = ComipoliInfoBar()
        self.infobar.connect('response', self.on_next_info)
        #
        # etc...
        #
        overlay = Gtk.Overlay()
        infolay = Gtk.Overlay()
        overlay.add_overlay(self.upperbar)
        overlay.add(infolay)
        infolay.add_overlay(self.infobar)
        infolay.add(self.canvas)
        self.add(overlay)

    def on_next_info(self, widget, res_id):
        if res_id == Gtk.ResponseType.OK:
            self.set_uri(widget.uri)
        widget.hide()

    def change_pixbuf(self, nex):
		#
		# etc...
		#
        next_index = cbzs.index(etc) + 1
        if l > next_index:
            esc = html.escape(cbzs[next_index])
            uri = GLib.filename_to_uri(GLib.build_filenamev([path, cbzs[next_index]]), None)
            self.infobar.show_message(esc, uri)
            ''' GNOME 3.34 bug
            dlg = Gtk.MessageDialog(
                transient_for=self,
                buttons=Gtk.ButtonsType.OK_CANCEL,
                #message_type=Gtk.MessageType.QUESTION,
                text=f'<span bgcolor="red">Next:</span> {esc}',
                use_markup=True,
                secondary_text='ESC Cancel')
            dlg.set_default_response(Gtk.ResponseType.OK)
            if dlg.run() == Gtk.ResponseType.OK:
                uri = GLib.filename_to_uri(GLib.build_filenamev([path, cbzs[next_index]]), None)
                self.set_uri(uri)
            dlg.destroy()
            '''

で。

なんとかなった。
もう少し debug してから更新します。

zsh Expansion

凝りもせず macOS で zsh を色々試しているんだけど。
zsh: 14 Expansion
一部を抜き出すと。

#!/bin/zsh

arr='IFS 区切りのまま for'
for s in ${=arr}; do
    echo $s
done

val='カンマ,区切りを,ループ'
for j in ${(ps.,.)val}; do
    echo $j
done

aa=(配列を 改行 区切りに合体)
echo ${(F)aa}

えっと。
理解できたのは Python 屋が使うモンじゃないってことだった。
だって覚えたところで永久に使わなさそうだもん、こんな書き方。
覚書ページへの追記は基本的なものだけにしておこう。