18.4. os — 便捷地访问操作系统专属功能 | python 运行时服务 |《python 3 标准库实例教程》| python 技术论坛-江南app体育官方入口
目的:为操作系统相关的特性提供便捷且可移植的操作。
os
模块提供了平台相关模块的封装,常见的平台有 posix
、nt
和 mac
。在所有平台上可用函数的 api 应该都是相同的,这样使用 os
模块为程序提供了一些可移植性。但并非所有的函数在每个平台上都可用, 比如后文中提到的一些进程管理函数在 windows 上就不可用。
python 文档中 os
模块的的副标题是「各种各样的操作系统接口」。模块包含的大部分函数用于创建和管理进程或文件系统,例如:目录与文件,此外还有一些其他函数。
查看文件系统的内容
要查看文件系统上某个目录的内容,请使用 listdir()
。
os_listdir.py
import os
import sys
print(sorted(os.listdir(sys.argv[1])))
返回值包含了指定目录中未排序的成员名列表。但你不能从返回值中分辨它们是文件、目录还是符号连接。
$ python3 os_listdir.py .
['index.rst', 'os_access.py', 'os_cwd_example.py',
'os_directories.py', 'os_environ_example.py',
'os_exec_example.py', 'os_fork_example.py',
'os_kill_example.py', 'os_listdir.py', 'os_listdir.py~',
'os_process_id_example.py', 'os_process_user_example.py',
'os_rename_replace.py', 'os_rename_replace.py~',
'os_scandir.py', 'os_scandir.py~', 'os_spawn_example.py',
'os_stat.py', 'os_stat_chmod.py', 'os_stat_chmod_example.txt',
'os_strerror.py', 'os_strerror.py~', 'os_symlinks.py',
'os_system_background.py', 'os_system_example.py',
'os_system_shell.py', 'os_wait_example.py',
'os_waitpid_example.py', 'os_walk.py']
walk()
函数经过一个目录时,会递归的访问它的子目录,并产生一个 tuple
,其中包含了目录路径、该路径下任何直接子目录和指定目录中所有文件名的列表。
os_walk.py
import os
import sys
# if we are not given a path to list, use /tmp
if len(sys.argv) == 1:
root = '/tmp'
else:
root = sys.argv[1]
for dir_name, sub_dirs, files in os.walk(root):
print(dir_name)
# make the subdirectory names stand out with /
sub_dirs = [n '/' for n in sub_dirs]
# mix the directory contents together
contents = sub_dirs files
contents.sort()
# show the contents
for c in contents:
print(' {}'.format(c))
print()
这个例子展示了目录的递归输出。
$ python3 os_walk.py ../zipimport
../zipimport
__init__.py
example_package/
index.rst
zipimport_example.zip
zipimport_find_module.py
zipimport_get_code.py
zipimport_get_data.py
zipimport_get_data_nozip.py
zipimport_get_data_zip.py
zipimport_get_source.py
zipimport_is_package.py
zipimport_load_module.py
zipimport_make_example.py
../zipimport/example_package
readme.txt
__init__.py
如果你想要比文件名更多的信息,使用 scandir()
会比用 listdir()
更有效率。因为用它扫描目录时,它能在一次系统调用中返回更多的信息。
os_scandir.py
import os
import sys
for entry in os.scandir(sys.argv[1]):
if entry.is_dir():
typ = 'dir'
elif entry.is_file():
typ = 'file'
elif entry.is_symlink():
typ = 'link'
else:
typ = 'unknown'
print('{name} {typ}'.format(
name=entry.name,
typ=typ,
))
scandir()
返回目录中每一个项目 direntry
实例的序列。这种对象有几种属性和方法,可以用于访问文件的元数据。
$ python3 os_scandir.py .
index.rst file
os_access.py file
os_cwd_example.py file
os_directories.py file
os_environ_example.py file
os_exec_example.py file
os_fork_example.py file
os_kill_example.py file
os_listdir.py file
os_listdir.py~ file
os_process_id_example.py file
os_process_user_example.py file
os_rename_replace.py file
os_rename_replace.py~ file
os_scandir.py file
os_scandir.py~ file
os_spawn_example.py file
os_stat.py file
os_stat_chmod.py file
os_stat_chmod_example.txt file
os_strerror.py file
os_strerror.py~ file
os_symlinks.py file
os_system_background.py file
os_system_example.py file
os_system_shell.py file
os_wait_example.py file
os_waitpid_example.py file
os_walk.py file
文件系统权限管理
一个文件的详细信息可以通过 stat()
和 lstat()
函数查看,后者常用于检查疑似符号链接文件的状态。
os_stat.py
import os
import sys
import time
if len(sys.argv) == 1:
filename = __file__
else:
filename = sys.argv[1]
stat_info = os.stat(filename)
print('os.stat({}):'.format(filename))
print(' size:', stat_info.st_size)
print(' permissions:', oct(stat_info.st_mode))
print(' owner:', stat_info.st_uid)
print(' device:', stat_info.st_dev)
print(' created :', time.ctime(stat_info.st_ctime))
print(' last modified:', time.ctime(stat_info.st_mtime))
print(' last accessed:', time.ctime(stat_info.st_atime))
程序的执行方式不同时,程序的输出变化很大。你可以通过命令行尝试传递不同的参数给 os_stat.py
,并观察输出。
$ python3 os_stat.py
os.stat(os_stat.py):
size: 593
permissions: 0o100644
owner: 527
device: 16777218
created : sat dec 17 12:09:51 2016
last modified: sat dec 17 12:09:51 2016
last accessed: sat dec 31 12:33:19 2016
$ python3 os_stat.py index.rst
os.stat(index.rst):
size: 26878
permissions: 0o100644
owner: 527
device: 16777218
created : sat dec 31 12:33:10 2016
last modified: sat dec 31 12:33:10 2016
last accessed: sat dec 31 12:33:19 2016
在类 unix 系统上,你可以通过 chmod()
改变文件的权限。文件的权限可以用一个整数表示,你可以通过 stat
模块的常量来构造文件权限值。下面这个例子改变了文件的用户执行权限位。
os_stat_chmod.py
import os
import stat
filename = 'os_stat_chmod_example.txt'
if os.path.exists(filename):
os.unlink(filename)
with open(filename, 'wt') as f:
f.write('contents')
# 使用 stat 函数判断文件当前的权限
existing_permissions = stat.s_imode(os.stat(filename).st_mode)
if not os.access(filename, os.x_ok):
print('adding execute permission')
new_permissions = existing_permissions | stat.s_ixusr
else:
print('removing execute permission')
# 使用 xor 异或清除用户的执行权限
new_permissions = existing_permissions ^ stat.s_ixusr
os.chmod(filename, new_permissions)
以上脚本假定运行时,它有足够的权限用于改变文件的权限。
$ python3 os_stat_chmod.py
adding execute permission
access()
函数用于检测文件或进程的访问权限。
os_access.py
import os
print('testing:', __file__)
print('exists:', os.access(__file__, os.f_ok))
print('readable:', os.access(__file__, os.r_ok))
print('writable:', os.access(__file__, os.w_ok))
print('executable:', os.access(__file__, os.x_ok))
程序的执行方式不同,输出结果将有很大的变化。但输出都类似于这样:
$ python3 os_access.py
testing: os_access.py
exists: true
readable: true
writable: true
executable: false
access()
函数的库文档中有两个特殊的警告。第一个,没有必要在用 open()
打开文件前使用 access()
检查是否有打开文件的权限。原因基于一个小事实:在两次函数调用的时间窗口之间,文件权限可能发生了变化。第二个警告与扩展了 posix 含义的网络文件系统有有关。有些文件系统类型可能会响应 posix 系统调用,并返回进程有权限访问文件。之后使用 open()
打开文件时,又因为一些 posix 调用没检测到的原因导致打开操作失败。总的来说,打开文件时直接使用 open()
函数再带上相应的模式就好,问题出现时就捕获 ioerror
,没必要在打开前先检查权限。
创建或删除文件夹
os
模块也提供了一些处理文件夹的函数,使用这些函数,你可以创建、删除或列出目录。
os_directories.py
import os
dir_name = 'os_directories_example'
print('creating', dir_name)
os.makedirs(dir_name)
file_name = os.path.join(dir_name, 'example.txt')
print('creating', file_name)
with open(file_name, 'wt') as f:
f.write('example file')
print('cleaning up')
os.unlink(file_name)
os.rmdir(dir_name)
有两类函数用于创建或删除文件夹。使用 mkdir()
创建目录时,目录的所有父目录必须存在;同样的,使用 rmdir()
删除目录时,你只能删除子叶目录,即一条路径的最后一个目录,它不再包含其他目录了。与之不同的是,当你使用 makedirs()
与 removedirs()
函数来操作目录时,makedirs()
会创建完整的路径,父目录不存在时,它也会创建父目录。类似的,只要目录是空的,不包含除子目录外的其他文件了。用 removedirs()
删除目录时,会删除整条目录,包括父目录与子目录。
$ python3 os_directories.py
creating os_directories_example
creating os_directories_example/example.txt
cleaning up
处理符号链接
在支持符号链接的平台与系统上,你可以用这些函数处理符号链接。
os_symlinks.py
import os
link_name = '/tmp/' os.path.basename(__file__)
print('creating link {} -> {}'.format(link_name, __file__))
os.symlink(__file__, link_name)
stat_info = os.lstat(link_name)
print('permissions:', oct(stat_info.st_mode))
print('points to:', os.readlink(link_name))
# 清理工作
os.unlink(link_name)
symlink()
可以创建一个符号链接。readlink()
可以读取符号链接,用于判断链接指向的源文件。lstat()
类似 stat()
函数,只不过用于处理符号链接,它可以查看符号链接的一些属性。
$ python3 os_symlinks.py
creating link /tmp/os_symlinks.py -> os_symlinks.py
permissions: 0o120755
points to: os_symlinks.py
安全的替换现有文件
替换或重命名现有文件的操作不是幂等的,也就是说它们可能有一些副作用。导致同一个操作重复多次时,结果有可能并不相同。所以使用这两个操作可能会让程序暴露在竞态条件之下,导致一些意料之外的错误。rename()
和 replace()
函数都使用了安全的算法实现,在 posix 兼容的系统上,它们会尽可能的使用原子操作以避免错误。
os_rename_replace.py
import glob
import os
with open('rename_start.txt', 'w') as f:
f.write('starting as rename_start.txt')
print('starting:', glob.glob('rename*.txt'))
os.rename('rename_start.txt', 'rename_finish.txt')
print('after rename:', glob.glob('rename*.txt'))
with open('rename_finish.txt', 'r') as f:
print('contents:', repr(f.read()))
with open('rename_new_contents.txt', 'w') as f:
f.write('ending with contents of rename_new_contents.txt')
os.replace('rename_new_contents.txt', 'rename_finish.txt')
with open('rename_finish.txt', 'r') as f:
print('after replace:', repr(f.read()))
for name in glob.glob('rename*.txt'):
os.unlink(name)
大多数情况下,rename()
与 replace()
函数能跨文件系统工作。重命名文件时,如果源文件已经被移动一个文件到新文件系统上或目标文件已存在,则操作可能会失败。
$ python3 os_rename_replace.py
starting: ['rename_start.txt']
after rename: ['rename_finish.txt']
contents: 'starting as rename_start.txt'
after replace: 'ending with contents of rename_new_contents.txt'
检测和更改进程所有者
os
提供的下一组函数用于确定和更改进程所有者 id 。 这些是守护进程或特殊系统程序的作者最常使用的,它们需要更改权限级别而不是以 root
身份运行。 本节不会尝试解释 unix 安全性以及进程所有者等的复杂细节。更多有关详细信息,请参阅本节末尾的参考列表。
以下示例显示进程的真实有效用户和组信息,然后更改有效值。 这类似于守护进程在系统引导期间以 root 身份启动时需要执行的操作,以降低权限级别并以其他用户身份运行。
注意
在运行示例之前,更改
test_gid
和test_uid
值以匹配系统上定义的真实用户。
os_process_user_example.py
import os
test_gid = 502
test_uid = 502
def show_user_info():
print('user (actual/effective) : {} / {}'.format(
os.getuid(), os.geteuid()))
print('group (actual/effective) : {} / {}'.format(
os.getgid(), os.getegid()))
print('actual groups :', os.getgroups())
print('before change:')
show_user_info()
print()
try:
os.setegid(test_gid)
except oserror:
print('error: could not change effective group. '
'rerun as root.')
else:
print('change group:')
show_user_info()
print()
try:
os.seteuid(test_uid)
except oserror:
print('error: could not change effective user. '
'rerun as root.')
else:
print('change user:')
show_user_info()
print()
当在 os x 上以 id 为 502 和用户组为 502 的用户身份运行时,将生成以下输出:
$ python3 os_process_user_example.py
before change:
user (actual/effective) : 527 / 527
group (actual/effective) : 501 / 501
actual groups : [501, 701, 402, 702, 500, 12, 61, 80, 98, 398,
399, 33, 100, 204, 395]
error: could not change effective group. rerun as root.
error: could not change effective user. rerun as root.
值没有更改,因为当它不以 root 身份运行时,进程无法更改其有效所有者值。 任何尝试将有效用户 id 或组 id 设置为除当前用户之外的任何内容都会导致 oserror
。 使用 sudo
运行相同的脚本以便以 root 权限启动则是另外一种情况。
$ sudo python3 os_process_user_example.py
before change:
user (actual/effective) : 0 / 0
group (actual/effective) : 0 / 0
actual groups : [0, 1, 2, 3, 4, 5, 8, 9, 12, 20, 29, 61, 80,
702, 33, 98, 100, 204, 395, 398, 399, 701]
change group:
user (actual/effective) : 0 / 0
group (actual/effective) : 0 / 502
actual groups : [0, 1, 2, 3, 4, 5, 8, 9, 12, 20, 29, 61, 80,
702, 33, 98, 100, 204, 395, 398, 399, 701]
change user:
user (actual/effective) : 0 / 502
group (actual/effective) : 0 / 502
actual groups : [0, 1, 2, 3, 4, 5, 8, 9, 12, 20, 29, 61, 80,
702, 33, 98, 100, 204, 395, 398, 399, 701]
在这种情况下,由于它以 root 身份启动,因此脚本可以更改进程的有效用户和组。 更改有效 uid 后,该过程仅限于该用户的权限。 由于非 root 用户无法更改其有效组,因此程序需要在更改用户之前更改组。
管理进程环境
操作系统通过 os
模块暴露给程序的另一个特性是环境。在环境中设置的变量字符串能通过 os.environ
或 getenv()
读取。环境变量通常用于配置值,例如搜索路径、文件位置、和调试标识。这个例子演示如何获取一个环境变量,并给子进程传递一个值。
os_environ_example.py
import os
print('initial value:', os.environ.get('testvar', none))
print('child process:')
os.system('echo $testvar')
os.environ['testvar'] = 'this value was changed'
print()
print('changed value:', os.environ['testvar'])
print('child process:')
os.system('echo $testvar')
del os.environ['testvar']
print()
print('removed value:', os.environ.get('testvar', none))
print('child process:')
os.system('echo $testvar')
os.environ
对象遵循标准 python 映射 api 来获取与设置值。对 os.environ
的改动会输出给子进程。
$ python3 -u os_environ_example.py
initial value: none
child process:
changed value: this value was changed
child process:
this value was changed
removed value: none
child process:
管理进程工作目录
具备层级文件系统的操作系统有 当前工作目录 这个概念 -- 进程在使用相对路径存取文件时,将这个文件系统中的目录当作起始位置。用 getcwd()
获取当前工作目录,用 chdir()
改变当前工作目录。
os_cwd_example.py
import os
print('starting:', os.getcwd())
print('moving up one:', os.pardir)
os.chdir(os.pardir)
print('after move:', os.getcwd())
可移植的使用方式是用 os.curdir
引用当前目录,用 os.pardir
引用父目录。
$ python3 os_cwd_example.py
starting: .../pymotw-3/source/os
moving up one: ..
after move: .../pymotw-3/source
运行外部函数
警告
许多用于进程的函数可移植性有限。更一致的方式是以平台独立的方式来使用进程,请参考 模块。
system()
是运行一个单独命令的最基本方式,完全不需要与之交互 。它只接受一个字符串参数,该字符串是 shell 子进程执行的命令行。
os_system_example.py
import os
# simple command
os.system('pwd')
system()
的返回值是 shell 运行程序后退出状态码,它封装为16比特数值,高字节为退出状态,低字节为导致退出的信号值或0。
$ python3 -u os_system_example.py
.../pymotw-3/source/os
因为命令直接传递给 shell 处理,所以它可以含有 shell 语法,例如通配符或环境变量。
os_system_shell.py
import os
# command with shell expansion
os.system('echo $tmpdir')
当 shell 运行命令行时,这个字符串中的环境变量 $tmpdir
被展开。
$ python3 -u os_system_shell.py
/var/folders/5q/8gk0wq888xlggz008k8dr7180000hg/t/
除非显式在后台运行该命令,否则 system()
的调用将阻塞,直到它完成。子进程的标准输入、输出和错误默认被绑定到调用者拥有的流,但可以被 shell 语法重定向。
os_system_background.py
import os
import time
print('calling...')
os.system('date; (sleep 3; date) &')
print('sleeping...')
time.sleep(5)
不过这容易掉进 shell 的陷阱,有更好的方式完成同样的事。
$ python3 -u os_system_background.py
calling...
sat dec 31 12:33:20 est 2016
sleeping...
sat dec 31 12:33:23 est 2016
使用 os.fork() 创建进程
通过 os
模块可使用 posix 函数 fork()
和 exec()
(在 mac os x,linux,和其他 unix 衍生系统下可以用)。关于如何可靠地使用这些函数能写好几本书,请去图书馆或书店了解这里提到的详细信息。
使用 fork()
创建一个当前进程的克隆进程:
os_fork_example.py
import os
pid = os.fork()
if pid:
print('child process id:', pid)
else:
print('i am the child')
每次运行例子时,输出内容会随着系统状态变化,但看起来会是这样:
$ python3 -u os_fork_example.py
child process id: 29190
i am the child
fork 之后,这两个进程运行同样的代码。程序要搞清楚运行在哪个进程里,需要检查 fork()
返回值。如果返回值为 0
,程序正运行在子进程里。如果返回值非 0
,程序运行在父进程里,返回值是子进程的进程id。
os_kill_example.py
import os
import signal
import time
def signal_usr1(signum, frame):
"callback invoked when a signal is received"
pid = os.getpid()
print('received usr1 in process {}'.format(pid))
print('forking...')
child_pid = os.fork()
if child_pid:
print('parent: pausing before sending signal...')
time.sleep(1)
print('parent: signaling {}'.format(child_pid))
os.kill(child_pid, signal.sigusr1)
else:
print('child: setting up signal handler')
signal.signal(signal.sigusr1, signal_usr1)
print('child: pausing to wait for signal')
time.sleep(5)
父进程可以使用 kill()
和 模块给子进程发送信号。首先,定义一个信号处理函数,子进程收到信号后该函数会被调用。接着, fork()
,并在父进程中用 kill()
发送 usr1
信号之前暂停一会儿。这个例子利用这个暂停时间让子进程设置信号处理函数。真正的应用不会想用 sleep()
。子进程设置信号处理函数,然后 sleep 一会儿,给父进程留出时间发送信号。
$ python3 -u os_kill_example.py
forking...
parent: pausing before sending signal...
child: setting up signal handler
child: pausing to wait for signal
parent: signaling 29193
received usr1 in process 29193
在子进程中处理单独行为,一种简单方法是检查 fork()
的返回值,然后执行分支。比起简单分支,更复杂的情况需要更多的分离代码。在其他情况下,可能需要包装现有程序。对于这两种情况,exe*()
系列函数可用来运行另一个程序。
os_exec_example.py
import os
child_pid = os.fork()
if child_pid:
os.waitpid(child_pid, 0)
else:
os.execlp('pwd', 'pwd', '-p')
当 exec()
运行一个程序,该程序代码替换掉现有进程的代码。
$ python3 os_exec_example.py
.../pymotw-3/source/os
exec()
有很多变体,取决于可用参数的形式、父进程的路径和环境是否复制到子进程等。对于所有变体,第一个参数是路径或文件名,剩余的参数控制程序如何运行。它们要么作为命令行参数传递,要么覆盖进程「环境」(请看 os.environ
和 os.getenv
)。全部细节请参阅库文档。
生成新进程
作为一种便利手段, spawn()
函数簇在一条语句中处理 fork()
和 exec()
:
os_spawn_example.py
import os
os.spawnlp(os.p_wait, 'pwd', 'pwd', '-p')
第一个参数是模式,它指示在返回之前是否等待进程完成。本例子是等待。使用 p_nowait
让其他进程启动,然后在当前进程中恢复。
$ python3 os_spawn_example.py
.../pymotw-3/source/os
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 cc 协议,如果我们的工作有侵犯到您的权益,请及时联系江南app体育官方入口。