文章目录
- 操作目录和文件
- 简述
- 环境变量
- 操作目录和文件
- 小结
- 练习
- 习题1
- 习题2
操作目录和文件
简述
在命令行下,我们可以通过输入操作系统提供的各种命令,比如dir、cp等,来操作目录和文件。这些命令的本质其实就是简单地调用了操作系统提供的接口函数。
那如果想在Python程序中操作目录和文件该怎么办呢?Python内置的 os
模块同样给与我们调用操作系统提供的接口函数的能力。
打开Python交互式命令行,首先看看如何使用 os
模块的基本功能:
>>> import os
>>> os.name
'posix'
Linux、Unix和Mac OS X系统返回的是 posix
,Windows系统返回的则是 nt
。
要获取详细的系统信息,可以调用 uname()
函数:
>>> os.uname()
posix.uname_result(sysname='Darwin', nodename='MichaelMacPro.local', release='14.3.0', version='Darwin Kernel Version 14.3.0: Mon Mar 23 11:59:05 PDT 2015; root:xnu-2782.20.48~5/RELEASE_X86_64', machine='x86_64')
注意,uname()
函数在Windows系统上不提供,也就是说,os
模块的能否使用某些函数取决于使用者的操作系统。
环境变量
在操作系统中定义的环境变量,全部保存在 os.environ
变量中。我们可以直接查看操作系统的所有环境变量:
>>> os.environ
environ({'VERSIONER_PYTHON_PREFER_32_BIT': 'no', 'TERM_PROGRAM_VERSION': '326', 'LOGNAME': 'michael', 'USER': 'michael', 'PATH': '/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin:/usr/local/mysql/bin', ...})
要获取某个环境变量的值,可以调用使用 os.environ.get('key')
的方式:
>>> os.environ.get('PATH')
'/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin:/usr/local/mysql/bin'
>>> os.environ.get('x', 'default')
'default'
传入某个环境变量的名称,得到对应的路径。除此之外还可以传入一个字符串作为默认路径(没有可返回的路径时会返回默认路径)。
操作目录和文件
除了前面的 os
模块中,操作目录和文件的函数还有一部分放在 os.path
模块中。比方说用于生成绝对路径的 abspath()
函数:
>>> os.path.abspath('.') # 点符代表当前工作路径
'F:\\Python35'
>>> os.path.abspath('Tools\\demos')
'F:\\Python35\\Tools\\demos'
在05模块中归纳过文件搜索路径的一些知识,当我们在程序中需要用到某个文件时,可以使用两种方式来让程序查找到这个文件:
- 一是使用绝对路径,也即完整的路径;
- 二是使用相对路径(相对当前工作路径而言的路径),并且可以使用点符
.
来替代当前工作路径。
注意,使用相对路径时是可以不使用点符的,所以上面代码中,为 Tools\\demos
生成绝对路径也同样可行。
接下来我们试试创建目录和删除目录:
# 在某个目录下创建一个新目录,首先生成新目录的完整路径:
>>> os.path.join('/Users/michael', 'testdir')
'/Users/michael/testdir'
# 然后创建一个目录:
>>> os.mkdir('/Users/michael/testdir')
# 删掉一个目录:
>>> os.rmdir('/Users/michael/testdir')
把两个路径合成一个时,不要直接拼字符串,而要通过 os.path.join()
函数,这样可以正确处理不同操作系统的路径分隔符。在Linux/Unix/Mac下,os.path.join('part1','part2')
返回这样的字符串:
part-1/part-2
而Windows下会返回这样的字符串:
part-1\part-2
同样的道理,要拆分路径时,也不要直接去拆字符串,而要通过 os.path.split()
函数,这样可以把一个路径拆分为两部分,后一部分总是最后级别的目录或文件名:
>>> os.path.split('/Users/michael/testdir/file.txt')
('/Users/michael/testdir', 'file.txt')
os.path.splitext()
函数可以用来获取文件扩展名,很多时候非常方便:
>>> os.path.splitext('/path/to/file.txt')
('/path/to/file', '.txt')
这些合并、拆分路径的函数并不要求目录和文件真实存在,它们只是对字符串进行操作。
文件操作使用下面的函数。假定当前目录下有一个 test.txt
文件:
# 对文件重命名:
>>> os.rename('test.txt', 'test.py')
# 删掉文件:
>>> os.remove('test.py')
但是 os
模块中不存在复制文件的函数!原因是复制文件并非是由操作系统提供的系统调用。理论上讲,我们通过上一节的读写文件可以完成文件复制,只不过要多写很多代码。
幸运的是 shutil
模块提供了 copyfile()
的函数,你还可以在 shutil
模块中找到很多实用函数,它们可以看做是对 os
模块的补充。
最后看看如何利用Python的特性来过滤文件。比如我们要列出当前目录下的所有目录,只需要一行代码:
>>> [x for x in os.listdir('.') if os.path.isdir(x)]
['.lein', '.local', '.m2', '.npm', '.ssh', '.Trash', '.vim', 'Applications', 'Desktop', ...]
要列出所有的 .py
文件,也只需一行代码:
>>> [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']
['apis.py', 'config.py', 'models.py', 'pymonitor.py', 'test_db.py', 'urls.py', 'wsgiapp.py']
是不是非常简洁?
小结
Python的 os
模块封装了操作系统的目录和文件操作,要注意这些函数有的在 os
模块中,有的在 os.path
模块中。
练习
习题1
利用
os
模块编写一个能实现ls -l
输出的程序。
先看看 ls -l
做的是什么:
ubuntu@ubuntu:~/HumanFaceRecognitionWithNN$ ls -l
total 344
-rw-rw-r-- 1 ubuntu ubuntu 10301 Dec 14 22:28 face_recognition.py
drwxrwxr-x 3 ubuntu ubuntu 4096 Dec 21 15:50 test
-rw-rw-r-- 1 ubuntu ubuntu 328506 Dec 10 15:39 yaleB_face_dataset.mat
注意这是在Linux上执行的,我们想查看当前路径下有什么文件和文件夹可以使用 ls
或者 dir
命令,而如果我们想了解更详细的信息则可以用 ls -l
或者dir -l
命令。
这里稍微解析一下返回的信息吧,以下面这一条为例:
field1 | field2 | field3 | field4 | field5 | field6 | field7 | field8 | field9 | field10 |
- | rw- | rw- | r– | 1 | ubuntu | ubuntu | 10301 | Dec 14 22:28 | face_recognition.py |
- field1 是
File type flag
,标识文件类型,如果是-
则表明是普通文件,是d
则表明是一个目录; - field2、field3、field4 依次是拥有者、拥有者所在的用户组以及其他用户对该文件/文件夹的操作权限,
r
表示可读,w
表示可写,x
表示可执行。 - field5 是所含链接数,如果该项是一个文件,则链接数为1;如果该项是一个目录,则一般为该目录下子文件夹数+2。为什么呢?因为当前目录(该项的父目录)有一条指向该项的链接,而对文件夹来说,除了父目录的链接之外,它本身还有一条
.
链接指向自身,并且它的子目录都有一条..
链接指向它; - field6 是拥有者的名字;
- field7 是拥有者所在的用户组;
- field8 是该项的大小(多少bytes);
- field9 是最后修改该项的日期和时间;
- field10 是该项的名字。
我们注意到,除了每一项的详细信息之外,最前面还有一行输出 total 344
,这个344是什么呢?它指的是当前目录所有文件和文件夹所使用的块(block)的数目,块是一个操作系统的概念,这里不详细展开。如果我们想知道当前目录下每一项所使用的块的数目,可以使用 ls -s
命令:
ubuntu@VM-173-69-ubuntu:~/HumanFaceRecognitionWithNN$ ls -s
total 344
12 face_recognition.py 4 test 328 yaleB_face_dataset.mat
加起来总数正是 344
。
以上内容参考了以下几个链接:
- What is that “total” in the very first line after ls -l?
- command ls -l output explained
- What do the fields in ls -al output mean?
- What does each part of the ls -la
题目要求实现Python版的 ls -l
,理论上应该是可行的,但上面的内容只涉及到 os
和 os.path
模块中很少的函数,其他的还有待发掘。我暂时没有时间去琢磨,所以先略过这一题。
习题2
编写一个程序,能在当前目录以及当前目录的所有子目录下查找文件名包含指定字符串的文件,并打印出相对路径。
import os
def search(s, path=os.getcwd()):
filelst = [x for x in os.listdir(path)]
for filename in filelst:
filepath = os.path.join(path, filename)
# print('Searching: ', path, '\nWith: ', filepath)
if os.path.isfile(filepath):
if s in filename:
print(os.path.relpath(filepath))
elif os.path.isdir(filepath):
search(s, filepath)
if __name__ == '__main__':
s = input('Enter the string: ')
search(s)
这题还是挺有意思的,用户自己定义搜索的字符串,我们不仅要找出当前目录下包含该字符串的文件,还要搜索所有的子目录。我们可以把搜索一个目录的过程封装为 search
函数,并采用递归的方式来实现其子目录的搜索。思路如下:
- 获取当前目录的所有文件&目录名,使用
os.listdir()
函数可以实现,把这些名称放在一个列表里保存; - 接下来逐个遍历并判断列表中的元素是文件还是目录,可以使用
os.path.isfile()
和os.path.isdir()
函数;
- 如果当前遍历到的元素是文件,则使用
os.path.relpath()
函数输出文件的相对路径; - 如果当前遍历到的元素是目录,则将该目录的路径传入
search
函数。
特别地,我们要注意这些函数应该输入什么和会输出什么。os.path.relpath()
函数接收一条完整的绝对路径,并输出相对于当前工作路径(在命令行中执行该Python文件时所处的路径)的相对路径,所以我们要先构造出正确的绝对路径,才能获取正确的相对路径。
os.path.isdir()
可以接收相对路径也可以接收绝对路径,因为我们使用 os.listdir()
只能获得文件或目录的名称,在搜索子目录时,这些名称并不是相对于当前工作路径的相对路径,所以不能直接传入 os.listdir()
中,必须先构造绝对路径,然后再判断。
为什么不使用 os.path.abspath()
函数来生成绝对路径呢?因为传入 os.path.abspath()
函数的必须是一条正确的相对路径,才会得到正确的相对路径。举个例子,当前工作路径是 C:\Users\Administrator\Desktop
,其子目录 test1
中有一个文件 test2.py
,如果我们使用 os.path.abspath('test2.py')
,那么得到的绝对路径就变成了 C:\Users\Administrator\Desktop\test2.py
,显然是不对的。