现今,Python如此火爆,也不需进行多余的介绍(如果身为一个程序员,还不知道这个语言,那就要有危机感了),直入正题。本人也是Python新手一名,作为一名新手,当然选择新版本的Python啦,也就是Python3系列。刚开始,也不知道写点啥,不过光看文档也没啥用,所以就写了这个小程序(程序总代码也才70行,包括注释
)。程序功能也比较简单,就是在Python中运行Shell命令,简单地说就是接收用户输入shell命令,然后运行shell命令,并将shell命令的输出结果显示给用户。说起简单,做起来也不容易,身为菜鸟的我也折腾了将近1天。“麻雀虽小,五脏俱全”,程序虽然挺小,但也包含了几个有意思的小技术:子进程(Python3中新加入的功能)、多线程以及线程间通信等等。下面先介绍下相关基础知识(大神们如果无意间看到这篇文章,就直接略过基础知识吧,或者有时间的大神可以看看,有错的话希望能帮我这个菜鸟指出来,我也多学学
)。
基础知识
对于Python的语法知识,这里就不进行赘述了,主要讲一些关键技术,包括:子进程(Popen)、多线程(Thread)以及线程通信(Queue)
子进程(Popen)
class subprocess.Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=True, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=()) 这个构造函数包含的参数颇多,所以解释清楚还需要多费些口舌。
Popen(['/bin/sh', '-c', args[0], args[1], ...])
cwd:设置子进程的当前路径。需要注意的是,这个路径不影响可执行程序的路径,即不能将可执行的路径写为相对此路径。
env:子进程的环境变量,需为非None(不明白啥意思,使用的时候没有设置,也没有出错)。
多线程
Thread(group=None, target=None, name=None, args=(), kwargs={}, verbose=None, *, daemon=None)
group:这个参数是为了将来实现ThreadGroup后使用的,现在应该设置为None。
主要成员方法:
线程通信
class queue.Queue(maxsize=0)
class queue.LifoQueue(maxsize=0)
class queue.PriorityQueue(maxsize=0)
这三种队列都支持下面的方法:
程序模块
输入模块
#input method
def pwd_input():
chars = []
while True:
try:
newChar = msvcrt.getch().decode(encoding="utf-8")
except:
return input()
if newChar in '\r\n': # 如果是换行,则输入结束
break
elif newChar == '\b': # 如果是退格,则删除密码末尾一位并且删除一个星号
if chars:
del chars[-1]
msvcrt.putch('\b'.encode(encoding='utf-8')) # 光标回退一格
msvcrt.putch( ' '.encode(encoding='utf-8')) # 输出一个空格覆盖原来的星号
msvcrt.putch('\b'.encode(encoding='utf-8')) # 光标回退一格准备接受新的输入
else:
chars.append(newChar)
msvcrt.putch(newChar.encode(encoding='utf-8'))
return (''.join(chars) )代码中,使用了Python中提供的msvcrt方法,每次读取一个字符,然后如果字符是换行则直接返回;如果是回车则删除一个字符并将光标绘图,其他字符则直接显示(在密码输入的时候,用'*'代替newChar进行输出就行)。需要说明的是,之所以一开始需要捕获输入时的异常,是因为当在Python shell中执行,而不是在控制台执行时,会出现异常。这时候,直接用内置input()函数代替此函数。
子进程输出读取模块
#function read data from of io object
def __reader(stream,q):
while True:
byte=stream.read(64)
q.put(byte)此函数包括两个参数,stream代表了需要读取的IO对象(本文中就是stdout和stderr),q代表了读取数据后放入的队列。
主模块
def cmd():
cmdStack=[]# command stack
#sharing queue
q=Queue()
#create shell subprocess
p=Popen("cmd",stdin=PIPE,stdout=PIPE,stderr=PIPE,shell=True,cwd=os.environ['homepath'])
#start reader
outThread=Thread(target=__reader,kwargs={'stream':p.stdout,"q":q})
errThread=Thread(target=__reader,kwargs={'stream':p.stderr,"q":q})
outThread.setDaemon(True)
errThread.setDaemon(True)
outThread.start()
errThread.start()
#main loop
while True:
time.sleep(0.1)
if p.poll()!=None:
break
#read messages
output=b''
while(q.qsize()>0):
output+=q.get(False)
output=output.decode('gb2312')
#skip the command
if len(cmdStack)>0:
cmd=cmdStack[-1]
start=output.find(cmd[0:-1])
#print('<'+output+'><'+'<'+cmd[0:-1]+'>')
if start!=-1:
output=output[start+len(cmd):len(output)]
print(output,end="")
#read input
cmd=pwd_input()
cmdStack=[]
cmdStack.append(cmd)
cmd=cmd+'\n'
p.stdin.write(cmd.encode(encoding='utf-8'))首先创建了一个命令栈来存储执行过的命令,然后创建了一个公共队列,再然后创建了一个控制台子进程(用来执行SHELL命令)、两个控制台输出子线程,下面是循环读取SHELL输出的内容显示出来,然后再提示用户输入,并将输入的命令发送给SEHLL子进程。需要解释的主要代码包括以下几个部分:
p=Popen("cmd",stdin=PIPE,stdout=PIPE,stderr=PIPE,shell=True,cwd=os.environ['homepath'])上述语句创建了一个shell子进程,并将子进程的输入输出都设置为PIPE,也就是重定向为标准输入输出流。然后设置了子进程的执行路径为默认用户文件夹(在unix系统中使用'home'代替'homepath')。
#start reader
outThread=Thread(target=__reader,kwargs={'stream':p.stdout,"q":q})
errThread=Thread(target=__reader,kwargs={'stream':p.stderr,"q":q})
outThread.setDaemon(True)
errThread.setDaemon(True)
outThread.start()
errThread.start()上面创建了两个进程,分别来读取标准输出流和标准错误流,然后并且设置为后台线程。之所以设置为后台线程,是因为这两个线程在读取输出的时候会被堵塞,即使子线程已经结束,此时如果不设置为后台线程,会导致主线程无法退出。设为后台线程后,主线程退出时会直接强制终止这两个线程。
#read messages
output=b''
while(q.qsize()>0):
output+=q.get(False)
output=output.decode('gb2312')
#skip the command
if len(cmdStack)>0:
cmd=cmdStack[-1]
start=output.find(cmd[0:-1])
#print('<'+output+'><'+'<'+cmd[0:-1]+'>')
if start!=-1:
output=output[start+len(cmd):len(output)]
print(output,end="")由于队列中的输出内容是字节数组的形式存储的,所以先进行字节数组的拼接,然后再进行解码,解码为字符串(不同平台采用不同字符集进行解码,这里针对Windows平台采用了gb2312)。由于命令在输入的时候已经显示了出来,所以应该跳过子进程输出中的命令,因此下面要查找命令,然后将此命令删除。最后将输出内容打印出来。
#read input
cmd=pwd_input()
cmdStack=[]
cmdStack.append(cmd)
cmd=cmd+'\n'
p.stdin.write(cmd.encode(encoding='utf-8'))上面读取用户的输入,并将输入,并在输入后面给放入一个换行符,通过标准输入流发送给子进程进行执行。这里,如果不加入一个换行符,则命令就不会执行。
运行效果
总结
主要参考文章
完整程序
from subprocess import *
import os
from queue import Queue
from threading import Thread
import time
import msvcrt
#input method
def pwd_input():
chars = []
while True:
try:
newChar = msvcrt.getch().decode(encoding="utf-8")
except:
return input()
if newChar in '\r\n': # 如果是换行,则输入结束
break
elif newChar == '\b': # 如果是退格,则删除密码末尾一位并且删除一个星号
if chars:
del chars[-1]
msvcrt.putch('\b'.encode(encoding='utf-8')) # 光标回退一格
msvcrt.putch( ' '.encode(encoding='utf-8')) # 输出一个空格覆盖原来的星号
msvcrt.putch('\b'.encode(encoding='utf-8')) # 光标回退一格准备接受新的输入
elif newChar not in '\t':
chars.append(newChar)
msvcrt.putch(newChar.encode(encoding='utf-8'))
return (''.join(chars) )
#function read data from of io object
def __reader(stream,q):
while True:
byte=stream.read(64)
q.put(byte)
def cmd():
cmdStack=[]# command stack
#sharing queue
q=Queue()
#create shell subprocess
p=Popen("cmd",stdin=PIPE,stdout=PIPE,stderr=PIPE,shell=True,cwd=os.environ['homepath'])
#start reader
outThread=Thread(target=__reader,kwargs={'stream':p.stdout,"q":q})
errThread=Thread(target=__reader,kwargs={'stream':p.stderr,"q":q})
outThread.setDaemon(True)
errThread.setDaemon(True)
outThread.start()
errThread.start()
#main loop
while True:
time.sleep(0.1)
if p.poll()!=None:
break
#read messages
output=b''
while(q.qsize()>0):
output+=q.get(False)
output=output.decode('gb2312')
#skip the command
if len(cmdStack)>0:
cmd=cmdStack[-1]
start=output.find(cmd[0:-1])
#print('<'+output+'><'+'<'+cmd[0:-1]+'>')
if start!=-1:
output=output[start+len(cmd):len(output)]
print(output,end="")
#read input
cmd=pwd_input()
cmdStack=[]
cmdStack.append(cmd)
cmd=cmd+'\n'
p.stdin.write(cmd.encode(encoding='utf-8'))
if __name__=='__main__':
cmd()
print("\npython exitted")
本文介绍了一个使用Python编写的简易控制台模拟器。该程序利用Python的子进程、多线程及线程间通信技术,实现了接收并执行用户输入的Shell命令,并显示命令输出结果的功能。
214

被折叠的 条评论
为什么被折叠?



