[英]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.