简体   繁体   English

如何为交互式程序编写 python 包装脚本?

[英]How to write python wrapper script for the interactive programs?

After some error and trial I came up with the following solution ( popen.py ):经过一些错误和试验后,我想出了以下解决方案( popen.py ):

#! /usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import pty
import sys
from subprocess import Popen
from shlex import join
import errno

master_fd, slave_fd = pty.openpty()
cmd = join(sys.argv[1:])
print(">>", cmd)
try:
    p = Popen(
        cmd,
        shell=True,
        stdout=slave_fd,
        stderr=slave_fd,
        stdin=slave_fd,
        close_fds=True,
        universal_newlines=True,
    )
    os.close(slave_fd)
    while p.returncode is None:
        buffer = os.read(master_fd, 512)
        if buffer:
            os.write(1, buffer)
        else:
            break
except OSError as err:
    if err.errno != errno.EIO:
        raise
except KeyboardInterrupt:
    print(f"\n## Err: Terminating the PID: {p.pid}")
    p.terminate()

This works well in most of the cases:这在大多数情况下都很有效:

> ./popen.py date
>> date
Wed 13 May 19:10:54 BST 2020

> ./popen.py date +'%F_%T'
>> date +%F_%T
2020-05-13_19:10:56

> ./popen.py bash -c 'while :; do echo -n .; sleep .5; done;'
>> bash -c 'while :; do echo -n .; sleep .5; done;'
.......^C
## Err: Terminating the PID: 840102

However it seems that my script is not capable to read the stdin :但是,我的脚本似乎无法读取stdin

> ./popen.py bash -c 'read -p "Enter something: " x; echo $x'
>> bash -c 'read -p "Enter something: " x; echo $x'
Enter something: bla bla



Come on... read it!!!
^C
## Err: Terminating the PID: 841583

> ./popen.py docker exec -it 9ab85463e3c1 sh
>> docker exec -it 9ab85463e3c1 sh
/opt/work_dir # ^[[20;19R
sfsdf
sdfsdf

^C
## Err: Terminating the PID: 847172

I've also tried to skip the os.close(slave_fd) step, but with exactly the same results:-/我也尝试跳过os.close(slave_fd)步骤,但结果完全相同:-/


My goal is to replicate bash script similar to the following ( bash.sh ):我的目标是复制类似于以下的bash脚本( bash.sh ):

#! /bin/bash --

ACT="${1:?## Err: Sorry, what should I do?}"
DID="${2:?## Err: Oh please.. Where\'s ID?}"

CMD=( docker exec -it )

case "${ACT}" in
  (run)
    shift 2
    echo ">> ${CMD[@]}" "${DID}" "$@"
    "${CMD[@]}" "${DID}" "$@"
  ;;
  (*)
    echo "## Err: Something went wrong..."
    exit 1
  ;;
esac

Example:例子:

> ./bash.sh run 9ab85463e3c1 sh
>> docker exec -it 9ab85463e3c1 sh
/opt/work_dir # date
Wed May 13 19:08:05 UTC 2020
/opt/work_dir # ^C

> ./bash.sh run 9ab85463e3c1 date +"%F_%T"
>> docker exec -it 9ab85463e3c1 date +%F_%T
2020-05-13_19:35:09

For my use case I eventually came up with the following script ( wrapper.py ):对于我的用例,我最终想出了以下脚本( wrapper.py ):

#! /usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import pty
import fire
from shlex import split
from os.path import realpath, expanduser


def proc(cmd, cd=None):
    """Spawn a process, and connect its controlling terminal with the
    current process's standard io."""
    print("## PID: {}".format(os.getpid()))

    def m_read(fd):
        """Master read function."""
        return os.read(fd, 1024)

    def i_read(fd):
        """Standard input read function."""
        return os.read(fd, 1024)

    cmd = cmd if isinstance(cmd, (list, tuple)) else split(cmd)

    try:
        cwd = os.getcwd()
        if cd is not None:
            print("## Changing directory: {}".format(realpath(cd)))
            os.chdir(expanduser(os.fsdecode(cd)))
        ex_msg = "\n## Exit Status Indicator: {}"
        print(ex_msg.format(pty.spawn(cmd, m_read, i_read)))
    except Exception as err:
        print("## Err: {}".format(str(err)))
    finally:
        os.chdir(cwd)


if __name__ == "__main__":
    fire.Fire({"run": proc})

It now can be used as a regular wrapper script:它现在可以用作常规包装脚本:

> ./wrapper.py run -cd .. 'docker exec -it 9ab85463e3c1 sh'
## PID: 1679972
## Changing directory: /home
/opt/work_dir # date
Sat May 16 15:18:46 UTC 2020
/opt/work_dir # cat /etc/os-release 
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.11.6
PRETTY_NAME="Alpine Linux v3.11"
HOME_URL="https://alpinelinux.org/"
BUG_REPORT_URL="https://bugs.alpinelinux.org/"
/opt/work_dir # 
## Exit Status Indicator: 0

I hope that someone will find this helpful.我希望有人会觉得这很有帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM