在Python3中,commands模块被移除掉,commands模块执行shell命令的相关模块和函数的功能,在subprocess模块总均可实现,并且提供了更加丰富的功能
Python Version: 3.5+
call 执行命令,返回状态码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 >>> ret = subprocess.call(["df" ,"-h" ], shell=False )Filesystem Size Used Avail Use% Mounted on /dev/disk1 233 G 107 G 127 G 46 % / /dev/disk2s1 58 G 36 G 22 G 63 % /Volumes/SanDisk >>> print(ret)0 ------------ >>> ret = subprocess.call("df -h" , shell=True )Filesystem Size Used Avail Use% Mounted on /dev/disk1 233 G 107 G 127 G 46 % / /dev/disk2s1 58 G 36 G 22 G 63 % /Volumes/SanDisk >>> print(ret)0
check_call 检查执行命令后的返回值,如果是0则返回,如果不是则抛出异常
1 2 3 4 5 6 >>> ret = subprocess.check_call("exit 1" , shell=True )Traceback (most recent call last): File "<stdin>" , line 1 , in <module> File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/subprocess.py" , line 584 , in check_call raise CalledProcessError(retcode, cmd) subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1
check_output 检查执行命令后的返回值,如果是0则返回执行结果,如果不是则抛出异常
1 2 3 >>> ret = subprocess.check_output("df -h" , shell=True )>>> print(ret)b'Filesystem Size Used Avail Use% Mounted on\n/dev/disk1 233G 107G 127G 46% /\n/dev/disk2s1 58G 36G 22G 63% /Volumes/SanDisk\n'
Popen 用于执行复杂的系统命令
参数:
args:shell命令,可以是字符串或者序列类型(如:list,元组)
bufsize:指定缓冲。0 无缓冲,1 行缓冲,其他 缓冲区大小,负值 系统缓冲
stdin, stdout, stderr:分别表示程序的标准输入、输出、错误句柄
preexec_fn:只在Unix平台下有效,用于指定一个可执行对象(callable object),它将在子进程运行之前被调用
close_sfs:在windows平台下,如果close_fds被设置为True,则新创建的子进程将不会继承父进程的输入、输出、错误管道。 所以不能将close_fds设置为True同时重定向子进程的标准输入、输出与错误(stdin, stdout, stderr)。
shell:同上
cwd:用于设置子进程的当前目录
env:用于指定子进程的环境变量。如果env = None,子进程的环境变量将从父进程中继承。
universal_newlines:不同系统的换行符不同,True -> 同意使用 \n
startupinfo与createionflags只在windows下有效 将被传递给底层的CreateProcess()函数,用于设置子进程的一些属性,如:主窗口的外观,进程的优先级等等
1 2 3 import subprocessret1 = subprocess.Popen(["mkdir" ,"t1" ]) ret2 = subprocess.Popen("mkdir t2" , shell=True )
非交互模式的命令 1 2 3 4 5 6 7 8 9 10 11 import subprocessobj = subprocess.Popen("df -h" , shell=True ) print("polarsnow" ) ------------ polarsnow Filesystem Size Used Avail Use% Mounted on /dev/disk1 233 G 107 G 127 G 46 % / /dev/disk2s1 58 G 36 G 22 G 63 % /Volumes/SanDisk
从上面的例子🌰看到,父进程在开启子进程之后并没有等待子进程结束,而是直接运行了print
1 2 3 4 5 6 7 8 9 10 11 import subprocessobj = subprocess.Popen("df -h" , shell=True ) obj.wait() print("polarsnow" ) ------------ Filesystem Size Used Avail Use% Mounted on /dev/disk1 233 G 107 G 127 G 46 % / /dev/disk2s1 58 G 36 G 22 G 63 % /Volumes/SanDisk polarsnow
加入了wait之后,可以看到,父进程会等待子进程运行结束后再执行print
此外,你还可以在父进程中对子进程进行如下其他的操作:
obj.poll() 检查子进程的状态
obj.kill() 终止子进程
obj.sent_signal() 向子进程发送信号
obj.terminate() 终止子进程
obj.pid 查看子进程的pid
obj.args 查看shell命令
子进程的文本流控制
obj.stdin 标准输入
obj.stdout 标准输出
obj.stderr 标准错误
可以在Popen建立子进程的时候改变子进程的标准输入、标准输出和标准错误。并可以利用subprocess.PIPE将多个子进程的输入输出连接在一起,构成管道
1 2 3 4 import subprocessobj = subprocess.Popen("df -h" , shell=True , stdout=subprocess.PIPE) print(obj.stdout.read())
使用了subprocess提供的管道,子进程执行命令后的输出不会再默认输出到屏幕上了,而是放到了管道里,等待用户提取管道中的数据
1 2 3 4 5 6 7 import subprocessobj = subprocess.Popen("df -h" , shell=True , stdout=subprocess.PIPE) print(obj.communicate()) ------------ (b'Filesystem Size Used Avail Use% Mounted on\n/dev/disk1 233G 107G 127G 46% /\n/dev/disk2s1 58G 36G 22G 63% /Volumes/SanDisk\n' , None )
communicate()会返回执行命令的结果和标准错误,但是上面的例子中,我只是将子进程的标准输出放到的subprocess的管道中,并没有对标准错误应用subprocess的管道,所以,不管有没有报错,这里的返回值返回的标准错误永远是None。下面来验证一下
1 2 3 4 5 6 7 8 9 import subprocessobj = subprocess.Popen("df -y" , shell=True , stdout=subprocess.PIPE) print(obj.communicate()) ------------ df: invalid option -- 'y' Try 'df --help' for more information. (b'' , None )
果然,报错默认打在了屏幕上,而communicate的返回值中的标准错误依然是None
实现Linux中 | 管道的效果
1 2 3 4 5 6 7 8 9 import subprocessobj1 = subprocess.Popen("cat /etc/passwd" , shell=True , stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) obj2 = subprocess.Popen("grep root" , shell=True , stdin=obj1.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out = obj2.communicate() print(out) ------------ (b'root:*:0:0:System Administrator:/var/root:/bin/sh\ndaemon:*:1:1:System Services:/var/root:/usr/bin/false\n_cvmsroot:*:212:212:CVMS Root:/var/empty:/usr/bin/false\n' , b'' )
第3行:创建了一个子进程去执行命令,并将标准输入输出和错误放到了subprocess提供的管道中。
第4行:创建了另一个子进程去执行命令,命令的标准输入是上一条命令的标准输出,所以把obj1中的标准输出从subprocess的管道中取出当做标准输入传递给第二个子进程
你可能会想,在之前的例子中,如果不加wait的话,父进程不管子进程有没有执行完都会往下执行,那么假如第一条命令执行时间比较长,父进程如果继续向下走,执行第二条命令时,还没有拿到第一条的标准输出,会不会报错呢?首先,这是个好问题!下面详细分析一下
程序会继续向下执行,但是会分两种情况,一种情况是第二条命令是阻塞的命令,第二种情况是非阻塞的命令。
假如第二条是阻塞的命令,进程会一直卡住,等待接收输入,这种情况下,程序会一直等下去,等到第一条命令执行结束,把标准输出放到管道为止。
假如第二条命令是非阻塞的命令,父进程会立即向下执行,虽然第一条命令还没有把标准输出放到管道中,第二条命令会认为你要给我传的就是空,所以第二条命令会被迅速执行完毕,而上面的例子中,调用了第二条命令对象的communicate方法,只会等待第二条命令执行完毕,一旦第二条命令执行完毕,不管第一条有没有执行完都会立即退出
subprocess.PIPE实际上为文本流提供一个缓存区。child1的stdout将文本输出到缓存区,随后child2的stdin从该PIPE中将文本读取走。child2的输出文本也被存放在PIPE中,直到communicate()方法从PIPE中读取出PIPE中的文本。 注意:communicate()是Popen对象的一个方法,该方法会阻塞父进程,直到子进程完成
实现类似pexpect的交互式命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import subprocessobj = subprocess.Popen("passwd ps" , shell=True , stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True ) obj.stdin.write("123\n" ) obj.stdin.write("123\n" ) obj.stdin.close() cmd_out = obj.stdout.read() obj.stdout.close() cmd_error = obj.stderr.read() obj.stderr.close() print(cmd_out) print(cmd_error) ------------ Changing password for user ps. passwd: all authentication tokens updated successfully. New password: BAD PASSWORD: it is WAY too short BAD PASSWORD: is too simple Retype new password:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import subprocessobj = subprocess.Popen("passwd ps" , shell=True , stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True ) obj.stdin.write("123\n" ) obj.stdin.write("123\n" ) cmd_out, cmd_error = obj.communicate() print(cmd_out) print(cmd_error) ------------ Changing password for user ps. passwd: all authentication tokens updated successfully. New password: BAD PASSWORD: it is WAY too short BAD PASSWORD: is too simple Retype new password:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import subprocessobj = subprocess.Popen("passwd ps" , shell=True , stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True ) cmd_out, cmd_error = obj.communicate('123\n123\n' ) print(cmd_out) print(cmd_error) ------------ Changing password for user ps. passwd: all authentication tokens updated successfully. New password: BAD PASSWORD: it is WAY too short BAD PASSWORD: is too simple Retype new password: