【愚公系列】2021年12月 Python教学课程 26-系统编程
前言
如果我们要操作文件、目录,可以在命令行下面输入操作系统提供的各种命令来完成。比如 dir,cd 等命令。如果要在 Python 程序中执行这些目录和文件的操作怎么办?其实操作系统提供的命令只是简单地调用了操作系统提供的接口函数,Python 内置的 os模块也可以直接调用操作系统提供的接口函数。
os 模块是 Python 标准库中的一个用于访问操作系统相关功能的模块。
os 模块的主要功能:系统相关、目录及文件操作、执行命令和管理进程
Ps:其中的进程管理功能主要是 Linux 相关的,本节不做讨论。
使用 os 模块:
import os
在使用 os 模块的时候,如果出现了问题,会抛出 OSError 异常,表明无效的路径名或
文件名,或者路径名(文件名)无法访问,或者当前操作系统不支持该操作等。
>>> import os
>>> os.chdir("d:\11")
Traceback (most recent call last):
File "<pyshell#2>", line 1, in <module>
os.chdir("d:\11")
OSError: [WinError 123] 文件名、目录名或卷标语法不正确。: 'd:\11'
1. 系统相关
os 模块提供了一些操作系统相关的变量,可以在跨平台的时候提供支持,便于编写移植性高,可用性好的代码。所以在涉及操作系统相关的操作时,请尽量使用本模块提供的方法,而不要使用当前平台特定的用法或格式,否则一旦移植到其他平台,可能会造成难以解决的困扰。
下面以表格的形式,列举 os 模块中常用的方法和变量,及其用途解释。
方法和变量 | 用途 |
---|---|
os.name | 查看当前操作系统的名称。windows 平台下返回‘nt’,Linux 则返回‘posix’。 |
os.environ | 获取系统环境变量 |
os.sep | 当前平台的路径分隔符。在 windows 下,为‘\’,在 POSIX 系统中,为‘/’。 |
os.altsep | 可替代的路径分隔符,在 Windows 中为‘/’。 |
os.extsep | 文件名和文件扩展名之间分隔的符号,在 Windows 下为‘.’。 |
os.pathsep | PATH 环境变量中的分隔符,在 POSIX 系统中为‘:’,在 Windows 中为‘;’。 |
os.linesep | 行结束符。在不同的系统中行尾的结束符是不同的,例如在 Windows 下为‘\r\n’。 |
os.devnull | 在不同的系统上 null 设备的路径,在 Windows 下为‘nul’,在 POSIX 下为‘/dev/null’。 |
os.defpath | 当使用 exec 函数族的时候,如果没有指定 PATH 环境变量,则默认会查找 os.defpath 中的值作为子进程 PATH 的值。 |
使用范例:
2. 文件和目录操作
os 模块中包含了一系列文件操作相关的函数,其中有一部分是 Linux 平台专用方法。Linux 是用 C 写的,底层的 libc 库和系统调用的接口都是 C API,Python 的 os 模块中包括了对这些接口的 Python 实现,通过 Python 的 os 模块,可以调用 Linux 系统的一些底层功能,进行系统编程。关于 Linux 的相关方法,可根据需要自行查阅官方文档,这里只介绍一些常用的,各平台通用的方法。
方法和变量 | 用途 |
---|---|
os.getcwd() | 获取当前工作目录,即当前 python 脚本工作的目录路径 |
os.chdir(“dirname”) | 改变当前脚本工作目录;相当于 shell 下 cd |
os.curdir | 返回当前目录: (’.’) |
os.pardir | 获取当前目录的父目录字符串名:(’…’) |
os.makedirs(‘dir1/dir2’) | 可生成多层递归目录 |
os.removedirs(‘dirname1’) | 递归删除空目录(要小心) |
os.mkdir(‘dirname’) | 生成单级目录 |
os.rmdir(‘dirname’) | 删除单级空目录,若目录不为空则无法删除并报错 |
os.listdir(‘dirname’) | 列出指定目录下的所有文件和子目录,包括隐藏文件 |
os.remove(‘filename’) | 删除一个文件 |
os.rename(“oldname”,“new”) | 重命名文件/目录 |
os.stat(‘path/filename’) | 获取文件/目录信息 |
os.path.abspath(path) | 返回 path 规范化的绝对路径 |
os.path.split(path) | 将 path 分割成目录和文件名二元组返回 |
os.path.dirname(path) | 返回 path 的目录。其实就是 os.path.split(path)的第一个元素 |
os.path.basename(path) | 返回 path 最后的文件名。如果 path 以/或\结尾,那么就会返回空值。 |
os.path.exists(path 或者 file) | 如果 path 存在,返回 True;如果 path 不存在,返回 False |
os.path.isabs(path) | 如果 path 是绝对路径,返回 True |
os.path.isfile(path) | 如果 path 是一个存在的文件,返回 True。否则返回 False |
os.path.isdir(path) | 如果 path 是一个存在的目录,则返回 True。否则返回 False |
os.path.join(path1[, path2[, …]]) | 将多个路径组合后返回,第一个绝对路径之前的参数将被忽略 |
os.path.getatime(path) | 返回 path 所指向的文件或者目录的最后存取时间 |
os.path.getmtime(path) | 返回 path 所指向的文件或者目录的最后修改时间 |
os.path.getsize(filename) | 返回文件包含的字符数量 |
在 Python 中,使用 windows 的文件路径时一定要小心,比如你要引用 d 盘下的 1.txt文件,那么路径要以字符串的形式写成’d:\1.txt’或者 r’d:\1.txt’。前面的方式是使用windwos 的双斜杠作为路径分隔符,后者是使用原生字符串的形式,以 r 开始的字符串都被认为是原始字符串,表示字符串里所有的特殊符号都以本色出演,不进行转义,此时可以使用普通 windows 下的路径表示方式。这两种方法使用哪种都可以,但不可混用。
下面是一些使用的例子,建议大家都跟着做一遍(其中有一些是错误示范,让你更清楚它的用法)。
>>> os.getcwd() #获取当前目录
'C:\\Python36'
>>> os.chdir("d:") #进入 D:目录
>>> os.getcwd()
'D:\\'
>>> os.curdir #返回当前目录
'.'
>>> os.pardir #返回父目录
'..'
>>> os.makedirs("1\\2") #创建目录
>>> os.removedirs("1\\2") 删除目录
>>> os.listdir()
['$360Section', '$RECYCLE.BIN', '1.txt', 'MobileFile', 'pymysql_test.py', 'System
Volume Information', '用户目录']
>>> os.mkdir("1")
>>> os.listdir()
['$360Section', '$RECYCLE.BIN', '1', '1.txt', 'MobileFile', 'pymysql_test.py', 'System
Volume Information', '用户目录']
>>> os.rmdir("1")
>>> os.rename('1.txt','2.txt')
>>> os.listdir()
['$360Section', '$RECYCLE.BIN', '2.txt', 'MobileFile', 'pymysql_test.py', 'System
Volume Information', '用户目录']
>>> os.remove('1.txt')
Traceback (most recent call last):
File "<pyshell#22>", line 1, in <module>
os.remove('1.txt')
FileNotFoundError: [WinError 2] 系统找不到指定的文件。: '1.txt'
>>> os.remove('2.txt')
>>> os.stat()
Traceback (most recent call last):
File "<pyshell#24>", line 1, in <module>
os.stat()
TypeError: Required argument 'path' (pos 1) not found
>>> os.stat(os.getcwd())
os.stat_result(st_mode=16895, st_ino=1407374883553285, st_dev=2431137650,
st_nlink=1, st_uid=0, st_gid=0, st_size=32768, st_atime=1505824872,
st_mtime=1505824872, st_ctime=1445187376)
>>> os.path.abspath(os.getcwd()) #获取绝对路径
'D:\\test'
>>> os.path.split(os.getcwd()) #分割成目录和文件
('D:\\', 'test')
>>> cp = os.getcwd()
>>> os.path.dirname(cp)
'D:\\'
>>> os.path.basename(cp)
'test'
>>> os.path.exists(cp)
True
>>> os.path.exists("d:\\123\123") #判断路径是否存在
False
>>> os.path.isabs(cp)
True
>>> os.path.isabs("11\\1.py")
False
>>> os.path.isfile(cp)
False
>>> os.path.isfile("d:\\1.txt")
False
>>> os.path.isdir(cp)
True
>>> os.path.join(cp, "test.py")
'D:\\test\\test.py'
>>> os.path.getatime(cp) #最后存储时间
1505825113.4970243
>>> os.path.getmtime(cp) #最后修改时间
1505825113.4970243
>>> os.path.getsize(cp)
0
os.walk(top, topdown=True, onerror=None, followlinks=False)
walk 方法是 os 模块中非常重要和强大的一个方法。可以帮助我们非常便捷地以递归方式自顶向下或者自底向上的方式遍历目录树,对每一个目录都返回一个三元元组(dirpath, dirnames, filenames)。
其中:
dirpath - 遍历所在目录树的位置,是一个字符串对象
dirnames - 目录树中的子目录组成的列表,不包括(".“和”…")
filenames - 目录树中的文件组成的列表
如果可选参数 topdown = True 或者没有指定,则采用自顶向下的方式进行目录遍历,也就是从父目录向子目录逐步深入遍历,如果 topdown = False,则采用自底向上的方式遍历目录,也就是先打印子目录再打印父目录的方式。
如果可选参数 onerror 被指定,则 onerror 必须是一个函数,该函数有一个 OSError 实例的参数,这样可以允许在运行的时候即使出现错误的时候不会打断 os.walk()的执行,或者抛出一个异常并终止 os.walk()的运行。通俗的讲,就是定义这个参数用于指定当发生了错误时的处理方法。
默认情况下,os.walk()遍历的时候不会进入符号链接,如果设置了可选参数followlinks = True,则会进入符号链接。注意,这可能会出现遍历死循环,因为符号链接可能会出现自己链接自己的情况,而 os.walk()没有那么高的智商,无法发现这一点。
下面的例子会将 c:\python36 目录中的所有文件和子目录打印出来。
import os
try:
for root, dirs, files in os.walk(r"c:\python36"):
print("directory", root)
for directory in dirs:
print("<DIR> %s" % directory)
for file in files:
print("\t\t%s" % file)
except OSError as ex:
print(ex)
下面的例子会统计 c:/python36/Lib/email 目录下所有子目录的大小,但是 CVS 目录除外。
import os
from os.path import join, getsize
for root, dirs, files in os.walk('c:/python36/Lib/email'):
print(root, "consumes", end=" ")
print(sum(getsize(join(root, name)) for name in files), end=" ")
print("bytes in", len(files), "non-directory files")
if 'CVS' in dirs:
dirs.remove('CVS') # 不遍历 CVS 目录
运行结果:
C:\Python36\python.exe F:/Python/pycharm/201705/1.py -
c:/python36/Lib/email consumes 377849 bytes in 21 non-directory files
c:/python36/Lib/email\mime consumes 12205 bytes in 9 non-directory files
c:/python36/Lib/email\mime\__pycache__ consumes 30289 bytes in 27 nondirectory files
c:/python36/Lib/email\__pycache__ consumes 741924 bytes in 60 non-directory files
下面的例子会递归删除目录的所有内容,危险,请勿随意尝试!
import os
for root, dirs, files in os.walk(top, topdown=False):
for name in files:
os.remove(os.path.join(root, name))
for name in dirs:
os.rmdir(os.path.join(root, name))
3. 执行命令
我们几乎可以在任何操作系统上通过命令行指令与操作系统进行交互。那么我们如何通过 Python 来完成这些命令行指令的执行呢?首先,我们应该知道的是命令行指令的执行通常有两个我们比较关注的结果:
- 命令执行的状态码–表示命令执行是否成功
- 命令执行的输出结果–命令执行成功后的输出
早期的 Python 版本中,我们主要是通过 os.system()、os.popen().read()等函数来执行命令行指令的,另外还有一个很少使用的 commands 模块。但是从 Python 2.4 开始官方文档中建议使用的是 subprocess 模块,所以 os 模块和 commands 模块的相关函数在这里只提供一个简单的使用示例,我们重要要介绍的是 subprocess 模块。
os.system(command)
运行操作系统命令,直接显示结果。但返回值是 0 或-1,不能获得显示在屏幕上的数据。 command 是要执行的命令字符串。
如果我们是在 windows 环境下使用 IDLE 运行 os.system(‘ipconfig /all’),你会发现命令终端界面一闪而过,根本啥都来不及看。这时候,你最好进入 cmd 环境使用 python命令进入交互式界面才可以看到屏幕上的信息。
由于使用该函数经常会莫名其妙地出现错误,但是直接执行命令并没有问题,所以一般建议不要使用。
os.popen(command, [mode, [bufsize]])
开启一个子进程执行 command 参数指定的命令,在父进程和子进程之间建立一个管道 pipe,用于在父子进程间通信。该方法返回一个文件对象,可以对这个文件对象进行读或写,取决于参数 mode,如果 mode 指定了只读,那么只能对文件对象进行读,如果 mode 参数指定了只写,那么只能对文件对象进行写操作。
简而言之,popen 也可以运行操作系统命令,并通过 read()方法将命令的结果返回,不像 system 只能看不能存,这个能存!
>>> os.popen('ipconfig')
<os._wrap_close object at 0x0000000002BB8EF0>
>>> ret = os.popen('ipconfig')
>>> ret.read()
'\nWindows IP 配置\n\n\n 以太网适配器 Bluetooth 网络连接 2:\n\n 媒体状
态 . . . . . . . . . . . . : 媒体已断开\n 连接特定的 DNS 后缀 . . . . . . . : \n\n 无线局域
网适配器 无线网络连接 2:\n\n 媒体状态 . . . . . . . . . . . . : 媒体已断开\n 连接特
定的 DNS 后缀 . . . . . . . : \n\n 无线局域网适配器 无线网络连接:\n\n 连接特定的
DNS 后缀......
subprocess 模块
subprocess 模块主要用于创建子进程,并连接它们的输入、输出和错误管道,获取它们的返回状态。通俗地说就是通过这个模块,你可以在 Python 的代码里执行操作系统级别的命令,比如“ipconfig”等等。
subprocess 模块中的常用函数
大多数情况下,推荐使用 run()方法调用子进程,执行操作系统命令。
subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, shell=False, timeout=None, check=False, encoding=None, errors=None)
功能:执行 args 参数所表示的命令,等待命令结束,并返回一个CompletedProcess 类型对象。
注意,run()方法返回的不是我们想要的执行结果或相关信息,而是一个CompletedProcess 类型的对象。
上面参数表里展示的只是一些常用的,真实情况还有很多。
args:表示要执行的命令。必须是一个字符串,字符串参数列表
stdin、stdout 和 stderr:子进程的标准输入、输出和错误。其值可以是subprocess.PIPE、subprocess.DEVNULL、一个已经存在的文件描述符、已经打开的文件对象或者 None。subprocess.PIPE 表示为子进程创建新的管道。
subprocess.DEVNULL 表示使用 os.devnull。默认使用的是 None,表示什么都不做。另外,stderr 可以合并到 stdout 里一起输出。
timeout:设置命令超时时间。如果命令执行时间超时,子进程将被杀死,并弹出TimeoutExpired 异常。
check:如果该参数设置为 True,并且进程退出状态码不是 0,则弹出CalledProcessError 异常。
encoding:如果指定了该参数,则 stdin、stdout 和 stderr 可以接收字符串数据,并以该编码方式编码。否则只接收 bytes 类型的数据。
shell:如果该参数为 True,将通过操作系统的 shell 执行指定的命令。
run()方法的返回值,表示一个进程结束了。
CompletedProcess 类有下面这些属性:
- args 启动进程的参数,通常是个列表或字符串。
- returncode 进程结束状态返回码。0 表示成功状态。
- stdout 获取子进程的 stdout。通常为 bytes 类型序列,None 表示没有捕获值。如果你在调用run()方法时,设置了参数 stderr=subprocess.STDOUT,则错误信息会和 stdout 一起输出,此时 stderr
的值是 None。 - stderr 获取子进程的错误信息。通常为 bytes 类型序列,None 表示没有捕获值。
- check_returncode() 用于检查返回码。如果返回状态码不为零,弹出CalledProcessError 异常。
subprocess.DEVNULL
一个特殊值,用于传递给 stdout、stdin 和 stderr 参数。表示使用 os.devnull 作为参数
值。
subprocess.PIPE
管道,可传递给 stdout、stdin 和 stderr 参数。
subprocess.STDOUT
特殊值,可传递给 stderr 参数,表示 stdout 和 stderr 合并输出。
args 与 shell 参数
args 参数可以接收一个类似’dir d:\'的字符串,也可以传递一个类似[‘dir’, ‘d:\’]的字符串分割列表。shell 参数默认为 False,设置为 True 的时候表示使用操作系统的 shell 执行命令。下面我们来看一下两者的组合结果。
到 windows 系统中测试一下,分别独立执行下面的语句:
ret = subprocess.run('dir d:\\')
ret = subprocess.run('dir d:\\', shell=True)
ret = subprocess.run(['dir', 'd:\\'])
ret = subprocess.run(['dir', 'd:\\'], shell=True)
ret = subprocess.run('ipconfig /all')
ret = subprocess.run('ipconfig /all', shell=True)
ret = subprocess.run(['ipconfig', '/all'])
ret = subprocess.run(['ipconfig', '/all'], shell=True)
结果表明,在 windows 中,args 和 shell 参数组合比较复杂,根据命令的不同有不同的情况。建议 shell 设置为 True。
获取执行结果
run()方法返回的是一个 CompletedProcess 类型对象,不能直接获取我们通常想要的结果。要获取命令执行的结果或者信息,在调用 run()方法的时候,请指定
stdout=subprocess.PIPE。
>>> ret = subprocess.run('dir', shell=True)
>>> ret
CompletedProcess(args='dir', returncode=0)
>>> ret = subprocess.run('dir', shell=True, stdout=subprocess.PIPE)
>>> ret
CompletedProcess(args='dir', returncode=0, stdout=b' \xc7\xfd\xb6\xaf\xc6\xf7 ......')
>>> ret.stdout
b' \xc7\xfd\xb6\xaf\xc6\xf7 C \xd6\xd0\xb5\xc4\xbe\xed\xca\xc7 ......'
>>> ret.stdout.decode('gbk')
' 驱动器 C 中的卷是 系统\r\n 卷的序列号是 C038-3181\r\n\r\n C:\\Python36 的目录
\r\n\r\n2017/08/11 10:14 ...... 15,275,020,288 可用字节\r\n'
从例子中我们可以看到,如果不设置 stdout=subprocess.PIPE,那么在返回值CompletedProcess(args=‘dir’, returncode=0)中不会包含 stdout 属性。反之,则会将结果以 bytes 类型保存在 ret.stdout 属性中。注意: 中文 windows 系统使用 GBK 编码,需要 decode(‘gbk’)才可以看见熟悉的中文。
交互式输入
并不是所有的操作系统命令都像‘dir’或者‘ipconfig’那样单纯地返回执行结果,还有很多像‘python’这种交互式的命令,你要输入点什么,然后它返回执行的结果。使用 run()方法怎么向 stdin 里输入?
参考下面的使用方法, 在一个 1.txt 文件中写入 print(‘i like Python’),然后:
import subprocess
fd = open("d:\\1.txt")
ret = subprocess.run("python", stdin=fd, stdout=subprocess.PIPE,shell=True)
print(ret.stdout)
fd.close()
这样做,虽然可以达到目的,但是很不方便,也不是以代码驱动的方式。这个时候,我们可以使用 Popen 类。
subprocess.Popen()类
用法和参数与 run()方法基本类同,但是它的返回值是一个 Popen 对象,而不是CompletedProcess 对象。
>>> ret = subprocess.Popen("dir", shell=True)
>>> type(ret)
<class 'subprocess.Popen'>
>>> ret
<subprocess.Popen object at 0x0000000002B17668>
Popen 对象的 stdin、stdout 和 stderr 是三个文件句柄,可以像文件那样进行读写操作。
>>>s = subprocess.Popen("ipconfig", stdout=subprocess.PIPE, shell=True)
>>>print(s.stdout.read().decode("GBK"))
要实现前面的‘python’命令功能,可以按下面的例子操作:
import subprocess
s = subprocess.Popen("python", stdout=subprocess.PIPE, stdin=subprocess.PIPE,
shell=True)
s.stdin.write(b"import os\n")
s.stdin.write(b"print(os.environ)")
s.stdin.close()
out = s.stdout.read().decode("GBK")
s.stdout.close()
print(out)
通过 s.stdin.write()可以输入数据,而 s.stdout.read()则能输出数据。
- 点赞
- 收藏
- 关注作者
评论(0)