页面导航:
前言:
本篇文章由网友“小黑”投稿发布。
这篇文章是接着上一篇的内容来写的。
相关英文教程的下载链接,以及Python 2.7.8的C源代码的下载链接,请参考之前
"Python基本的I/O操作 (一)"的内容。
文章中的脚本代码主要是通过Slackware Linux系统中的python 2.7.8的版本来进行测试的。部分代码是通过Mac OS X中的Python 2.6.1的版本以及win7中的Python 2.7.9的版本来测试的。
文章中的大部分链接,可能都需要通过代理才能正常访问。
os模块的pathconf与fpathconf方法:
pathconf与fpathconf方法可以在Unix系统中,获取与文件或终端相关的一些配置值,这些配置值也可以看作是Unix对文件或终端的限制值。例如,这两个方法可以获取到最多可以为某个文件创建多少个hard link(硬链接),还可以获取到文件名所支持的最大长度等。
这两个方法的语法格式如下(只存在于Unix系统中):
os.pathconf(path, name) -> integer
os.fpathconf(fd, name) -> integer |
os.pathconf会使用path路径名来作为第一个参数,而os.fpathconf则会使用fd文件描述符来作为第一个参数。它们的第二个参数name的含义都是一样的,用于指定需要获取的配置的名称。配置值则会以整数的形式作为结果返回。
os.pathconf方法最终会通过底层的pathconf系统调用去完成具体的操作,os.fpathconf则会通过底层的fpathconf系统调用去完成操作。这些系统调用可以通过man命令来查看其详情:
black@slack:~/test$ man pathconf
FPATHCONF(3) Linux Programmer's Manual FPATHCONF(3)
NAME
fpathconf, pathconf - get configuration values for files
SYNOPSIS
#include <unistd.h>
long fpathconf(int fd, int name);
long pathconf(char *path, int name);
DESCRIPTION
fpathconf() gets a value for the configuration option name for the open
file descriptor fd.
pathconf() gets a value for configuration option name for the filename
path.
.............................................
Setting name equal to one of the following constants returns the fol-
lowing configuration options:
_PC_LINK_MAX
returns the maximum number of links to the file. If fd or path
refer to a directory, then the value applies to the whole direc-
tory. The corresponding macro is _POSIX_LINK_MAX.
_PC_MAX_CANON
returns the maximum length of a formatted input line, where fd
or path must refer to a terminal. The corresponding macro is
_POSIX_MAX_CANON.
_PC_MAX_INPUT
returns the maximum length of an input line, where fd or path
must refer to a terminal. The corresponding macro is
_POSIX_MAX_INPUT.
_PC_NAME_MAX
returns the maximum length of a filename in the directory path
or fd that the process is allowed to create. The corresponding
macro is _POSIX_NAME_MAX.
_PC_PATH_MAX
returns the maximum length of a relative pathname when path or
fd is the current working directory. The corresponding macro is
_POSIX_PATH_MAX.
_PC_PIPE_BUF
returns the size of the pipe buffer, where fd must refer to a
pipe or FIFO and path must refer to a FIFO. The corresponding
macro is _POSIX_PIPE_BUF.
_PC_CHOWN_RESTRICTED
returns nonzero if the chown(2) call may not be used on this
file. If fd or path refer to a directory, then this applies to
all files in that directory. The corresponding macro is
_POSIX_CHOWN_RESTRICTED.
_PC_NO_TRUNC
returns nonzero if accessing filenames longer than
_POSIX_NAME_MAX generates an error. The corresponding macro is
_POSIX_NO_TRUNC.
_PC_VDISABLE
returns nonzero if special character processing can be disabled,
where fd or path must refer to a terminal.
.............................................
black@slack:~/test$
|
可以看到,在Linux系统中,可以使用的配置名有PC_LINK_MAX(最大硬链接数),PC_NAME_MAX(文件名的最大长度)等,下面是一个简单的例子:
import os
no = os.pathconf('test.txt', 'PC_LINK_MAX')
print "Maximum number of hard link to the file. : %d" % no
fd = os.open("tmpdir", os.O_RDONLY)
no = os.fpathconf(fd, 'PC_NAME_MAX')
print "Maximum length of a filename :%d" % no
os.close(fd)
|
这段代码的执行结果如下:
black@slack:~/test$ python test.py
Maximum number of hard link to the file. : 32000
Maximum length of a filename :255
black@slack:~/test$
|
可以看到,在作者的Linux系统里,最多可以为一个文件创建32000个硬链接,有关硬链接的概念会在下面讲解os.link方法时进行介绍。从PC_NAME_MAX配置返回的值255可知,tmpdir所在的文件系统可以创建的文件名的最大长度为255(也就是文件名中最多只能包含255个字符)。
Linux系统中的PC_MAX_CANON与PC_MAX_INPUT的配置值并不会起到它应有的作用。从man手册里可以看到,这两个配置表示终端中一次最多可以输入多少个字符,Linux系统的终端可输入的字符数是由内部的N_TTY_BUF_SIZE常量来决定的,这个常量值通常是4096。在其他Unix系统里,比如Mac OS X中,这两个配置值就可以起到应有的作用,例如下面这段代码:
from sys import stdin
import os
print 'PC_MAX_CANON:', os.fpathconf(0, 'PC_MAX_CANON')
print 'input something:',
line = stdin.readline()
print 'stdin.readline:', len(line)
|
0是unix系统中标准输入设备的文件描述符的整数值,因此,os.fpathconf(0, 'PC_MAX_CANON')在非Linux的unix系统中,可以获取到终端一次最多可以输入多少个字符,这段代码在Mac OS X系统中的执行结果如下:
MacOSX:~ black$ python test.py
PC_MAX_CANON: 1024
input something:ssineksssssssssssssssssssssxxxxxxxxx
xxxxxxxxxxxxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkiiiiiiii
iiiiiiiiissineksssssssssssssssssssssxxxxxxxxxxxxxxxx
xxxxxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkiiiiiiiiiiiiiii
iissineksssssssssssssssssssssxxxxxxxxxxxxxxxxxxxxxxx
xkkkkkkkkkkkkkkkkkkkkkkkkkkkkkiiiiiiiiiiiiiiiiissine
ksssssssssssssssssssssxxxxxxxxxxxxxxxxxxxxxxxxkkkkkk
kkkkkkkkkkkkkkkkkkkkkkkiiiiiiiiiiiiiiiiissinekssssss
sssssssssssssssxxxxxxxxxxxxxxxxxxxxxxxxkkkkkkkkkkkkk
kkkkkkkkkkkkkkkkiiiiiiiiiiiiiiiiissineksssssssssssss
ssssssssxxxxxxxxxxxxxxxxxxxxxxxxkkkkkkkkkkkkkkkkkkkk
kkkkkkkkkiiiiiiiiiiiiiiiiissinekssssssssssssssssssss
sxxxxxxxxxxxxxxxxxxxxxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkk
kkiiiiiiiiiiiiiiiiissineksssssssssssssssssssssxxxxxx
xxxxxxxxxxxxxxxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkiiiii
iiiiiiiiiiiissineksssssssssssssssssssssxxxxxxxxxxxxx
xxxxxxxxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkiiiiiiiiiiii
iiiiissineksssssssssssssssssssssxxxxxxxxxxxxxxxxxxxx
xxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkiiiiiiiiiiiiiiiiiss
ineksssssssssssssssssssssxxxxxxxxxxxxxxxxxxxxxxxx
stdin.readline: 1024
MacOSX:~ black$
|
也就是Mac OS X的终端最多只能输入1024个字符,当超过1024个字符时,你就没办法继续输入了。如果是在Linux系统中,那么PC_MAX_CANON与PC_MAX_INPUT都将返回255,但是终端实际最多可以输入4096个字符,也就是上面提到的由Linux内部的N_TTY_BUF_SIZE常量(值为4096)来确定,因此,Linux里的这两个配置值不会发挥应有的作用。
至于PC_CHOWN_RESTRICTED这个配置,表示对某文件进行chown操作时,是否会受到权限方面的限制。如果PC_CHOWN_RESTRICTED返回非0值,则表示chown是受限的,用户必须具有足够的权限才能使用chown去修改某个文件的owner与group。在PC_CHOWN_RESTRICTED起作用时(也就是返回非0值时),主要有以下限制:
1) 只有root(超级用户)的进程能更改文件的owner。
2) 对于非root的普通用户的进程,如果你是某文件的所有者(进程的euid == 文件的owner的uid),那么你就可以修改该文件的group,并且只能更改到你所属的组。
例如下面这段代码:
import os
print 'PC_CHOWN_RESTRICTED for test.txt:', os.pathconf('test.txt', 'PC_CHOWN_RESTRICTED')
os.chown('test.txt', -1, 1001)
print 'change test.txt group to wifi'
|
这段代码的执行结果如下:
black@slack:~/test$ sudo groupadd wifi
black@slack:~/test$ sudo gpasswd -a black wifi
Adding user black to group wifi
black@slack:~/test$ ls -l
....................................................
-rwxr--r-- 1 black users 24 Dec 2 15:02 test.txt
....................................................
black@slack:~/test$ id
uid=1001(black) gid=100(users) groups=100(users),1001(wifi)
black@slack:~/test$ python test.py
PC_CHOWN_RESTRICTED for test.txt: 1
change test.txt group to wifi
black@slack:~/test$ ls -l
....................................................
-rwxr--r-- 1 black wifi 24 Dec 2 15:02 test.txt
....................................................
black@slack:~/test$
|
上面在作者的Slackware系统中,先通过groupadd添加了一个wifi组,再将black用户加入到wifi组(要让组生效,需要先退出black用户,再重新登录)。通过id命令可以看到black用户属于users与wifi组,这样,black用户的进程就可以将所拥有的文件的group修改为users或者wifi组了。
上面PC_CHOWN_RESTRICTED配置所返回的值为1,因此,对于test.txt文件进行chown操作是受限的,我们只能将该文件的group修改为所属的users或wifi组,如果改成别的非所属的组,就会抛出操作无权限的错误。
从前面的man命令中可以看到,底层的pathconf与fpathconf系统调用的第二个参数name是int整数类型(man手册中显示的_PC_LINK_MAX之类的宏对应的都是整数值),而我们在上面的Python脚本中都是直接使用的
'PC_LINK_MAX' 之类的字符串。这是因为,在Python内部,会自动将这些字符串转换为对应的整数值。在os模块里,有一个pathconf_names词典,该词典中就存储了字符串与对应的整数值的名值对信息:
black@slack:~/test$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46)
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.pathconf_names
{'PC_MAX_INPUT': 2, 'PC_VDISABLE': 8, 'PC_SYNC_IO': 9,
'PC_SOCK_MAXBUF': 12, 'PC_NAME_MAX': 3, 'PC_MAX_CANON': 1,
'PC_PRIO_IO': 11, 'PC_CHOWN_RESTRICTED': 6, 'PC_ASYNC_IO': 10,
'PC_NO_TRUNC': 7, 'PC_FILESIZEBITS': 13, 'PC_LINK_MAX': 0,
'PC_PIPE_BUF': 5, 'PC_PATH_MAX': 4}
>>> quit()
black@slack:~/test$
|
你当然也可以在Python脚本中,直接使用整数值,例如,PC_LINK_MAX对应的整数值为0,那么就可以提供0来表示PC_LINK_MAX的配置:
black@slack:~/test$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46)
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.pathconf('test.txt', 0)
32000
>>> quit()
black@slack:~/test$
|
如果你提供的配置不被Unix系统所支持的话,pathconf与fpathconf方法可能会返回-1,也可能会抛出Invalid argument的错误:
black@slack:~/test$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46)
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.pathconf('test.txt', 'PC_SYNC_IO')
-1
>>> os.pathconf('test.txt', 111)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OSError: [Errno 22] Invalid argument
>>> quit()
black@slack:~/test$
|
作者的Linux系统中不存在PC_SYNC_IO的配置,上面就返回了-1。而111则根本就是一个无效的配置,上面就抛出了错误。
os.pathconf对应的底层C函数为posix_pathconf,os.fpathconf对应的底层C函数则为posix_fpathconf,这两个C函数都定义在Python源码中的Modules/posixmodule.c文件里。这两个C函数中会调用的conv_path_confname函数,就是用来将你提供的字符串转换为对应的整数的。有兴趣的读者可以阅读相关C源码来加深理解。
os模块的stat、lstat及fstat方法:
通过stat,lstat或者fstat方法可以获取到文件的相关信息,例如文件的大小,访问时间等信息。这几个方法的语法格式如下:
os.stat(path) -> stat result
os.lstat(path) -> stat result
os.fstat(fd) -> stat result |
stat与lstat的path参数,用于指定文件的路径。fstat则需要接受fd(文件描述符)作为输入参数。它们都会返回stat result对象,例如下面这段代码:
black@slack:~/test$ python
....................................................
>>> import os
>>> os.stat('test.txt')
posix.stat_result(st_mode=33252, st_ino=110517L,
st_dev=2050L, st_nlink=1, st_uid=1001, st_gid=1001,
st_size=24L, st_atime=1449223021, st_mtime=1449039743,
st_ctime=1449223012)
>>>
|
可以看到stat_result中包含了st_mode之类的成员,这些成员的具体含义,可以在Linux命令行中通过man 2 stat命令来查看其详情(因为,os.stat方法会通过底层的stat系统调用来执行具体的操作,os.lstat与os.fstat方法也都会通过同名的系统调用去执行具体的操作):
black@slack:~/test$ man 2 stat
STAT(2) Linux Programmer's Manual STAT(2)
NAME
stat, fstat, lstat - get file status
SYNOPSIS
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
.............................................
stat() stats the file pointed to by path and fills in buf.
lstat() is identical to stat(), except that if path is a symbolic link,
then the link itself is stat-ed, not the file that it refers to.
fstat() is identical to stat(), except that the file to be stat-ed is
specified by the file descriptor fd.
All of these system calls return a stat structure, which contains the
following fields:
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; /* protection */
nlink_t st_nlink; /* number of hard links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
dev_t st_rdev; /* device ID (if special file) */
off_t st_size; /* total size, in bytes */
blksize_t st_blksize; /* blocksize for file system I/O */
blkcnt_t st_blocks; /* number of 512B blocks allocated */
time_t st_atime; /* time of last access */
time_t st_mtime; /* time of last modification */
time_t st_ctime; /* time of last status change */
};
.............................................
black@slack:~/test$
|
从上面的输出信息里可以看到,lstat与stat的区别在于,当lstat遇到符号链接时,只会将符号链接本身的文件信息返回,不会将符号链接所指向的文件的信息返回。此外,我们还可以看到返回的stat结构中各成员的含义:
st_dev表示包含该文件的设备ID。通过这个设备ID,我们可以知道该文件位于哪个磁盘的哪个分区中。例如:0x802就表示文件位于第一个SCSI磁盘的第二个分区里。设备ID的具体概念在后面讲解os.major方法时会进行介绍。
st_ino表示inode number(索引节点号),unix系统的每个文件都有一个inode结构,该结构中存储了磁盘数据的磁盘块位置信息,以及文件相关的各种属性(比如文件的访问权限,修改时间等),stat系统调用主要就是从文件的inode结构中获取的数据。有了inode number节点号,内核就可以找到文件对应的inode结构,并从该结构中得到磁盘块位置信息,从而在磁盘中索引到文件的磁盘数据。对于那些没有具体的磁盘数据的设备文件来说,它们的节点号所对应的inode结构中,并没有磁盘块位置信息,只有一些用于标识设备类型的设备ID等信息。有关Inode的相关信息,可以参考
http://www.thegeekstuff.com/2012/01/linux-inodes/ 该链接对应的文章。
st_mode里存储了文件的onwer,group,other的读写执行的访问权限。
st_nlink用于存储文件的硬链接数。
st_uid用于存储文件的owner的uid。
st_gid用于存储文件的group的gid。
st_rdev主要用于设备文件。通过rdev中存储的设备ID,我们就可以知道某个设备文件具体对应的是什么类型的设备。这些设备文件都是在内核启动时,内核为不同的设备临时创建的文件,它们没有具体的磁盘数据。
st_size表示文件的实际内容的大小。
st_blksize表示文件系统的Fragment Size(分段大小),这个分段大小是为了提高磁盘的I/O性能,在文件系统创建时设置的。磁盘中的文件是以分段的大小来分配空间的。在作者的Linux中,这个值为4096。那么一个文件最少要占用4096个字节的磁盘空间,如果文件大小超过4096字节,也会以4096的倍数来分配磁盘空间。
st_blocks表示文件在磁盘中所占用的物理块的数量(作者的Linux系统是以512字节为一个物理块)。由于在作者的Linux系统中,st_blksize为4096,因此,哪怕文件的实际内容的大小只有几十个字节,也会为其分配4096个字节的磁盘空间,也就是4096 / 512 = 8块。因此,磁盘文件最少会占用8个物理块。如果实际内容超过4096个字节的,也会按8的倍数来分配物理块。因此,作者的Linux系统中,普通磁盘文件的st_blocks会是8的倍数。目前很多磁盘都以4096字节作为一个实际的物理扇区,但是这4096字节又被平分为8个512字节的逻辑扇区,因此,依然可以使用512字节作为一个物理块来访问这种4K扇区的磁盘,有关4K扇区的相关内容可以参考
http://flashdba.com/4k-sector-size/ 该链接对应的文章。
st_atime表示文件的最后访问时间。
st_mtime表示文件的最后修改时间。
st_ctime表示文件的inode结构的最后改变时间,与st_mtime的区别在于,st_mtime主要表示文件的内容的修改时间,而st_ctime则主要表示inode结构中的状态属性的改变时间,例如,文件的owner,group发生改变时,以及读写执行的访问权限发生改变时。因此,在你对某个文件进行写入操作并保存后,先会修改st_mtime的值,最后会修改st_ctime的值。而如果你只用chmod修改文件的读写执行的访问权限,或者只用chown修改文件的owner或group时,则只有st_ctime会发生改变,st_mtime的值此时不会发生改变。
在Linux系统中,有一个stat命令,使用该命令可以直接获取到文件的这些信息:
black@slack:~/test$ stat test.txt
File: `test.txt'
Size: 26 Blocks: 8 IO Block: 4096 regular file
Device: 802h/2050d Inode: 110531 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 1001/ black) Gid: ( 100/ users)
Access: 2015-12-05 16:05:16.992788154 +0800
Modify: 2015-12-05 16:02:14.472433891 +0800
Change: 2015-12-05 16:05:06.627788291 +0800
Birth: -
black@slack:~/test$
|
我们可以利用os模块的stat与lstat方法,来创建一个跨平台的stat程式:
import os, stat, sys, time
def convmode(mode):
ret = ''
ret = 's' if (mode & 0770000 == stat.S_IFSOCK) else \
'l' if (mode & 0770000 == stat.S_IFLNK) else \
'-' if (mode & 0770000 == stat.S_IFREG) else \
'b' if (mode & 0770000 == stat.S_IFBLK) else \
'd' if (mode & 0770000 == stat.S_IFDIR) else \
'c' if (mode & 0770000 == stat.S_IFCHR) else \
'f' if (mode & 0770000 == stat.S_IFIFO) else '?'
ret += 'r' if (mode & stat.S_IRUSR) else '-'
ret += 'w' if (mode & stat.S_IWUSR) else '-'
if(mode & stat.S_ISUID):
ret += 's'
elif(mode & stat.S_IXUSR):
ret += 'x'
else:
ret += '-'
ret += 'r' if (mode & stat.S_IRGRP) else '-'
ret += 'w' if (mode & stat.S_IWGRP) else '-'
if(mode & stat.S_ISGID):
ret += 's'
elif(mode & stat.S_IXGRP):
ret += 'x'
else:
ret += '-'
ret += 'r' if (mode & stat.S_IROTH) else '-'
ret += 'w' if (mode & stat.S_IWOTH) else '-'
if(mode & stat.S_ISVTX):
ret += 't'
elif(mode & stat.S_IXOTH):
ret += 'x'
else:
ret += '-'
return ret
if(len(sys.argv) != 3):
sys.exit('usage: python '+ sys.argv[0] + ' <stat|lstat> <filename>')
if(sys.argv[1] == 'stat'):
stat_func = os.stat
elif(sys.argv[1] == 'lstat'):
stat_func = os.lstat
else:
sys.exit('usage: python '+ sys.argv[0] + ' <stat|lstat> <filename>')
if(not os.path.exists(sys.argv[2])):
sys.exit(sys.argv[2] + ' not exists')
filename = sys.argv[2]
statinfo = stat_func(filename)
print 'File:'.rjust(7), filename, \
'[socket]' if (statinfo.st_mode & 0770000 == stat.S_IFSOCK) else \
'[symbolic link]' if (statinfo.st_mode & 0770000 == stat.S_IFLNK) else \
'[regular file]' if (statinfo.st_mode & 0770000 == stat.S_IFREG) else \
'[block device]' if (statinfo.st_mode & 0770000 == stat.S_IFBLK) else \
'[directory]' if (statinfo.st_mode & 0770000 == stat.S_IFDIR) else \
'[character device]' if (statinfo.st_mode & 0770000 == stat.S_IFCHR) else \
'[FIFO]' if (statinfo.st_mode & 0770000 == stat.S_IFIFO) else '[other type]'
print 'Size:'.rjust(7), statinfo.st_size, \
'/ Blocks: ' + str(statinfo.st_blocks) if hasattr(statinfo, 'st_blocks') else '', \
'/ IO Block: ' + str(statinfo.st_blksize) if hasattr(statinfo, 'st_blksize') else ''
print 'Device:', hex(statinfo.st_dev) + '/' + str(statinfo.st_dev) + 'd', \
'Inode:'.rjust(10), statinfo.st_ino, \
'Links:'.rjust(10), statinfo.st_nlink, \
('Device type: ' + str(os.major(statinfo.st_rdev)) + \
',' + str(os.minor(statinfo.st_rdev))).rjust(20) \
if hasattr(statinfo, 'st_rdev') and statinfo.st_rdev != 0 else ''
print 'Access:', '(' + oct(statinfo.st_mode & 07777) + '/' + \
convmode(statinfo.st_mode) + ')', \
'Uid:'.rjust(6), statinfo.st_uid, \
'Gid:'.rjust(6), statinfo.st_gid
print 'Access:', time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(statinfo.st_atime))
print 'Modify:', time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(statinfo.st_mtime))
print 'Change:', time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(statinfo.st_ctime))
if hasattr(statinfo, 'st_birthtime'):
print 'Birth:'.rjust(7), \
time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(statinfo.st_birthtime))
|
上面的S_IFSOCK,S_IFLNK之类的常量都可以在man 2 stat命令中查看其含义,这些常量定义在Python的stat模块中。通过这些常量就可以知道一个文件的具体类型,比如S_IFSOCK表示套接字类型的文件,S_IFLNK表示符号链接类型的文件,S_IFREG表示常规文件,S_IFBLK表示块设备文件,S_IFDIR表示目录类型,S_IFCHR表示字符设备文件,S_IFIFO表示named pipe(命名管道)。
此外,由于st_blocks,st_blksize,st_rdev这些属性成员只存在于Unix系统中。因此,为了能让该脚本在windows系统中正常运行,代码中就使用了hasattr内建函数来判断返回的statinfo对象中是否存在这些属性,如果存在就显示,不存在就不显示。同理,由于st_birthtime存在于Mac OS X及FreeBSD系统中,但是不存在于Linux系统里,因此,对于st_birthtime也使用了hasattr来进行检测。其余的不用检测的属性都是各系统中都存在的属性。在
https://docs.python.org/2/library/os.html#os.stat 该链接对应的官方手册中,可以看到哪些属性是通用的,哪些是某些系统所专有的。
这段代码在Linux系统中的执行情况如下:
black@slack:~/test$ python test.py
usage: python test.py <stat|lstat> <filename>
black@slack:~/test$ python test.py stat test.txt
File: test.txt [regular file]
Size: 26 / Blocks: 8 / IO Block: 4096
Device: 0x802L/2050d Inode: 110531 Links: 1
Access: (0644/-rw-r--r--) Uid: 1001 Gid: 100
Access: 2015-12-05 16:05:16
Modify: 2015-12-05 16:02:14
Change: 2015-12-05 16:05:06
black@slack:~/test$ ln -s test.txt symlink
black@slack:~/test$ python test.py lstat symlink
File: symlink [symbolic link]
Size: 8 / Blocks: 0 / IO Block: 4096
Device: 0x802L/2050d Inode: 96163 Links: 1
Access: (0777/lrwxrwxrwx) Uid: 1001 Gid: 100
Access: 2015-12-05 16:24:40
Modify: 2015-12-05 16:24:39
Change: 2015-12-05 16:24:39
black@slack:~/test$ python test.py lstat /dev/sr0
File: /dev/sr0 [block device]
Size: 0 / Blocks: 0 / IO Block: 4096
Device: 0x5L/5d Inode: 2715 Links: 1 Device type: 11,0
Access: (0660/brw-rw----) Uid: 0 Gid: 19
Access: 2015-12-05 13:58:02
Modify: 2015-12-05 13:58:02
Change: 2015-12-05 13:58:02
black@slack:~/test$
|
由于作者的Linux系统中,上面的symlink符号链接属于fast symlinks,因此,系统并没有为此符号链接分配磁盘块,上面symlink的Blocks也就显示为了0,读者可以参考
http://superuser.com/questions/640164/disk-usage-of-a-symbolic-link 这篇帖子里的信息来了解fast symlinks的相关内容。对于/dev/sr0这种设备文件,本身就没有磁盘数据,所以Blocks也都显示的是0 。
在windows中的执行情况如下:
G:\Python27\mytest>..\python.exe test.py stat test.txt
File: test.txt [regular file]
Size: 95
Device: 0x0/0d Inode: 0 Links: 0
Access: (0666/-rw-rw-rw-) Uid: 0 Gid: 0
Access: 2015-11-25 11:01:27
Modify: 2015-12-02 15:06:04
Change: 2015-11-14 16:17:41
G:\Python27\mytest>..\python.exe test.py stat mydir
File: mydir [directory]
Size: 0
Device: 0x0/0d Inode: 0 Links: 0
Access: (0555/dr-xr-xr-x) Uid: 0 Gid: 0
Access: 2015-12-03 15:04:59
Modify: 2015-12-03 15:04:59
Change: 2015-11-14 13:14:42
G:\Python27\mytest>
|
windows中可以查看的信息比较少,主要是文件名,文件类型,文件大小(windows中目录不能直接显示出大小),读写访问权限,以及文件的访问修改时间等信息。其他的显示0的部分,就是一些dummy value(虚值,没啥参考价值)。
在Mac OS X中的执行情况如下:
MacOSX:~ black$ python test.py lstat test.py
File: test.py [regular file]
Size: 3410 / Blocks: 8 / IO Block: 4096
Device: 0xe000002L/234881026d Inode: 410587 Links: 1
Access: (0700/-rwx------) Uid: 501 Gid: 20
Access: 2015-12-05 18:46:57
Modify: 2015-12-05 18:46:18
Change: 2015-12-05 18:46:18
Birth: 2015-11-30 15:28:03
MacOSX:~ black$ python test.py lstat symlink
File: symlink [symbolic link]
Size: 10 / Blocks: 8 / IO Block: 4096
Device: 0xe000002L/234881026d Inode: 410002 Links: 1
Access: (0775/lrwxrwxr-x) Uid: 501 Gid: 20
Access: 2015-11-23 18:29:57
Modify: 2015-11-23 18:29:57
Change: 2015-11-23 18:31:47
Birth: 2015-11-23 18:29:57
MacOSX:~ black$ python test.py lstat /dev/tty
File: /dev/tty [character device]
Size: 0 / Blocks: 0 / IO Block: 131072
Device: 0x34117a4L/54597540d Inode: 295 Links: 1 Device type: 2,0
Access: (0666/crw-rw-rw-) Uid: 0 Gid: 0
Access: 2015-12-05 18:42:06
Modify: 2015-12-05 18:42:06
Change: 2015-12-05 18:42:06
Birth: 2015-12-05 18:42:06
MacOSX:~ black$
|
Mac OS X系统中就存在Birth字段,也就是文件的诞生时间。
我们也可以使用os.fstat来访问文件的信息,只不过fstat需要接受文件描述符作为输入参数,如下面这个例子:
black@slack:~/test$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46)
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> fd = os.open('test.txt', os.O_RDONLY)
>>> os.fstat(fd)
posix.stat_result(st_mode=33188, st_ino=110531L,
st_dev=2050L, st_nlink=1, st_uid=1001, st_gid=100,
st_size=26L, st_atime=1449302716, st_mtime=1449302534,
st_ctime=1449302706)
>>> os.close(fd)
>>> quit()
black@slack:~/test$
|
os模块的statvfs与fstatvfs方法:
statvfs与fstatvfs方法可以将文件所在的文件系统的相关信息获取出来,其语法格式如下(只存在于Unix系统中):
os.statvfs(path) -> statvfs result
os.fstatvfs(fd) -> statvfs result |
statvfs的path参数用于指定目标文件的路径,fstatvfs则需要接受fd文件描述符为输入参数。这两个方法都会返回statvfs result对象:
black@slack:~/test$ python
....................................................
>>> import os
>>> import statvfs
>>> vfsinfo = os.statvfs('test.txt')
>>> print vfsinfo
posix.statvfs_result(f_bsize=4096, f_frsize=4096,
f_blocks=1941642L, f_bfree=238885L, f_bavail=140253L,
f_files=493856L, f_ffree=163306L, f_favail=163306L,
f_flag=4096, f_namemax=255)
>>> vfsinfo.f_bsize
4096
>>> vfsinfo[statvfs.F_BSIZE]
4096
>>> dir(statvfs)
['F_BAVAIL', 'F_BFREE', 'F_BLOCKS', 'F_BSIZE', 'F_FAVAIL',
'F_FFREE', 'F_FILES', 'F_FLAG', 'F_FRSIZE', 'F_NAMEMAX',
............................]
>>> print statvfs.F_BSIZE
0
>>>
|
statvfs_result对象中包含的成员既可以用属性成员的方式来访问,如vfsinfo.f_bsize。还可以把vfsinfo当成元组,使用索引来访问这些成员,如上面的vfsinfo[statvfs.F_BSIZE]。statvfs模块里定义了statvfs_result对象中各成员的索引值。statvfs.F_BSIZE其实就是索引值0。
由于os.statvfs与os.fstatvfs方法,都是通过底层的同名的系统调用来执行具体操作的,因此,我们可以通过man命令来查看结果对象中各成员的含义:
black@slack:~/test$ man statvfs
STATVFS(3) Linux Programmer's Manual STATVFS(3)
NAME
statvfs, fstatvfs - get file system statistics
SYNOPSIS
#include <sys/statvfs.h>
int statvfs(const char *path, struct statvfs *buf);
int fstatvfs(int fd, struct statvfs *buf);
DESCRIPTION
The function statvfs() returns information about a mounted file system.
path is the pathname of any file within the mounted file system. buf
is a pointer to a statvfs structure defined approximately as follows:
struct statvfs {
unsigned long f_bsize; /* file system block size */
unsigned long f_frsize; /* fragment size */
fsblkcnt_t f_blocks; /* size of fs in f_frsize units */
fsblkcnt_t f_bfree; /* # free blocks */
fsblkcnt_t f_bavail; /* # free blocks for unprivileged users */
fsfilcnt_t f_files; /* # inodes */
fsfilcnt_t f_ffree; /* # free inodes */
fsfilcnt_t f_favail; /* # free inodes for unprivileged users */
unsigned long f_fsid; /* file system ID */
unsigned long f_flag; /* mount flags */
unsigned long f_namemax; /* maximum filename length */
};
Here the types fsblkcnt_t and fsfilcnt_t are defined in <sys/types.h>.
Both used to be unsigned long.
The field f_flag is a bit mask (of mount flags, see mount(8)). Bits
defined by POSIX are
ST_RDONLY
Read-only file system.
ST_NOSUID
Set-user-ID/set-group-ID bits are ignored by exec(3).
.............................................
black@slack:~/test$
|
f_bsize与f_frsize都是文件系统的基本单位。只不过f_bsize是为了能够在I/O操作时最大化提高磁盘读写性能而定义的值,f_frsize则是文件系统的最小的分配单元(也就是前面提到过的Fragment Size)。在作者的Linux系统中,这两个成员的值都是4096。但是在作者的Mac OS X系统中,f_bsize为1048576,f_frsize则为4096,因此,Mac OS X中进行磁盘读写操作时,为了提高I/O性能,它最多能以1048576字节为基本单位对磁盘进行读写操作。而为某个文件分配磁盘空间时,则是以f_frsize的值即4096字节作为最小分配单位的,因此,一个文件在磁盘中占用的空间会是4096字节的倍数。
f_blocks表示文件系统的总的Fragment(分段)数量,每个分段的大小由f_frsize的值来决定。
f_bfree表示文件系统中所有空闲的Fragment(分段)的数量。
f_bavail表示文件系统中可以被非root的普通用户使用的空闲Fragment数量。上面的f_bfree统计的数量中,有一部分是为root用户预留的(这部分预留空间可以防止文件系统的碎片)。在计算文件系统的磁盘占用率时,一般是用f_bavail来进行计算。
f_files表示文件系统中总的inode节点数。前面提到过,Unix系统的每个文件都对应有一个inode结构。通过inode的数量就可以知道系统中最多可以存储多少个文件。
f_ffree表示空闲的inode节点数。
f_favail表示可以被普通用户所使用的空闲的inode节点数。
f_fsid表示文件系统的ID,虽然statvfs与fstatvfs系统调用会返回该成员,但是Python并没有将其加入到结果对象中,因此,你无法在Python里得到这个成员。
f_flag包含了一些与文件系统的mount(加载)相关的二进制位标志。比如:ST_RDONLY(值为1,对应f_flag二进制里的位0)可以检测是否为只读文件系统,ST_NOSUID(值为2,对应f_flag二进制里的位1)可以检测文件系统是否会忽略可执行文件的setuid,setgid位(setuid与setgid的概念在上一篇文章中介绍过)。如果你想对f_flag的这两个二进制位进行检测,需要自行定义ST_RDONLY与ST_NOSUID的常量,因为Python的各模块中并没有定义过这两个常量。
f_namemax表示文件系统中文件名的最大长度。
在了解了这些成员的含义后,我们就可以利用os模块的statvfs方法来创建一个简易的文件系统的磁盘空间统计程式:
import os, sys, math
from sys import platform as _platform
if(len(sys.argv) != 2):
sys.exit('usage: python '+ sys.argv[0] + ' <filename>')
if _platform == "linux" or _platform == "linux2":
# linux
unit = 1024
sunit = '1K-'
elif _platform == "darwin":
# MAC OS X
unit = 512
sunit = '512-'
else:
sys.exit('please run it in Linux or Mac OS X')
vfsinfo = os.statvfs(sys.argv[1])
sblocks = str(vfsinfo.f_frsize * vfsinfo.f_blocks / unit)
used = vfsinfo.f_blocks - vfsinfo.f_bfree
sused = str(vfsinfo.f_frsize * used / unit)
savail = str(vfsinfo.f_frsize * vfsinfo.f_bavail / unit)
suse_percent = '%d%%' % \
math.ceil((float(used) / (used + vfsinfo.f_bavail)) * 100)
sinodes = str(vfsinfo.f_files)
iused = vfsinfo.f_files - vfsinfo.f_ffree
siused = str(iused)
sifree = str(vfsinfo.f_ffree)
siuse_per = '%d%%' % \
math.ceil((float(iused) / (iused + vfsinfo.f_favail)) * 100)
lfirst = max(len(sblocks), len(sinodes), 9 if unit == 1024 else 10)
lsecond = max(len(sused), len(siused), 5)
lthird = max(len(savail), len(sifree), 9)
lfourth = max(len(suse_percent), len(siuse_per), 5)
print 'FileSystem Info for ' + sys.argv[1] + ':\n'
print (sunit+'blocks').rjust(lfirst), 'Used'.rjust(lsecond), \
'Available'.rjust(lthird), 'Use%'.rjust(lfourth)
print sblocks.rjust(lfirst), sused.rjust(lsecond), \
savail.rjust(lthird), suse_percent.rjust(lfourth)
print 'Inodes'.rjust(lfirst), 'IUsed'.rjust(lsecond), \
'IFree'.rjust(lthird), 'IUse%'.rjust(lfourth)
print sinodes.rjust(lfirst), siused.rjust(lsecond), \
sifree.rjust(lthird), siuse_per.rjust(lfourth)
print '\nmaximum filename length:', vfsinfo.f_namemax
|
这段代码在Linux中的执行情况如下:
black@slack:~/test$ python test.py test.txt
FileSystem Info for test.txt:
1K-blocks Used Available Use%
7766568 6811036 561004 93%
Inodes IUsed IFree IUse%
493856 330552 163304 67%
maximum filename length: 255
black@slack:~/test$ df
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/root 7766568 6811036 561004 93% /
....................................................
black@slack:~/test$ df -i
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/root 493856 330552 163304 67% /
....................................................
black@slack:~/test$
|
可以看到,test.py脚本对test.txt文件所在的根分区,统计出来的磁盘与inode节点的使用情况,与Linux中df工具显示的结果是一致的。
这段代码在Mac OS X中的执行情况如下:
MacOSX:~ black$ python test.py
usage: python test.py <filename>
MacOSX:~ black$ python test.py test.txt
FileSystem Info for test.txt:
512-blocks Used Available Use%
83214256 20104472 62597784 25%
Inodes IUsed IFree IUse%
10401780 2577057 7824723 25%
maximum filename length: 255
MacOSX:~ black$ df -i
Filesystem 512-blocks Used Available Capacity iused ifree %iused Mounted on
/dev/disk0s2 83214256 20104472 62597784 25% 2577057 7824723 25% /
devfs 213 213 0 100% 606 0 100% /dev
map -hosts 0 0 0 100% 0 0 100% /net
map auto_home 0 0 0 100% 0 0 100% /home
/dev/disk1s2 82196 82196 0 100% 20547 0 100% /Volumes/Legacy Empire EFI
MacOSX:~ black$
|
Mac OS X的df工具是以传统的磁盘扇区大小(512字节)为基本单位,来显示磁盘的使用情况的。脚本代码中就针对这种情况进行了处理,在Linux中会使用1K(即1024)作为除数,Mac中则使用512作为除数。如果你的系统里,df工具使用了别的尺寸作为基本单位的话,你需要自行调整脚本中的除数。
我们当然也可以使用os模块的fstatvfs方法来获取文件系统的相关信息,只不过该方法需要接受文件描述符作为输入参数:
black@slack:~/test$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46)
....................................................
>>> import os
>>> fd = os.open('test.txt', os.O_RDONLY)
>>> os.fstatvfs(fd)
posix.statvfs_result(f_bsize=4096, f_frsize=4096,
f_blocks=1941642L, f_bfree=238883L, f_bavail=140251L,
f_files=493856L, f_ffree=163304L, f_favail=163304L,
f_flag=4096, f_namemax=255)
>>>
|
os模块的ftruncate方法:
ftruncate可以将文件尺寸精确的截取到某个大小,其语法格式如下(只存在于Unix系统中):
fd为文件描述符,length用于指定需要截取到的大小(以字节为单位)。ftruncate会将文件大小设置到length,如果length小于原文件尺寸,那么length之后的数据就会丢失。如果length大于原文件尺寸,那么就相当于给原文件扩容,文件中的原有数据会保持不变,多出来的字节可能会用0来填充。
例如下面这段代码:
import os
fd = os.open('test.txt', os.O_WRONLY)
os.ftruncate(fd, 6)
print 'truncate test.txt size to 6'
|
这段代码会将test.txt的文件尺寸截取到6个字节,执行结果如下:
black@slack:~/test$ ls -l test.txt
-rw-r--r-- 1 black users 12 Dec 7 14:58 test.txt
black@slack:~/test$ cat test.txt
hello
world
black@slack:~/test$ python test.py
truncate test.txt size to 6
black@slack:~/test$ cat test.txt
hello
black@slack:~/test$ ls -l test.txt
-rw-r--r-- 1 black users 6 Dec 7 14:59 test.txt
black@slack:~/test$
|
test.txt的原始尺寸是12个字节,经过ftruncate的截取操作后,文件尺寸就变为了6,只有前6个字节的数据保留了下来,其余的数据都被截掉了。
当然,我们也可以对test.txt文件进行扩容操作:
black@slack:~/test$ ls -l test.txt
-rw-r--r-- 1 black users 6 Dec 7 14:59 test.txt
black@slack:~/test$ cat test.txt
hello
black@slack:~/test$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46)
....................................................
>>> import os
>>> fd = os.open('test.txt', os.O_WRONLY)
>>> os.ftruncate(fd, 11)
>>> quit()
black@slack:~/test$ od -t c test.txt
0000000 h e l l o \n \0 \0 \0 \0 \0
0000013
black@slack:~/test$ ls -l test.txt
-rw-r--r-- 1 black users 11 Dec 7 15:10 test.txt
black@slack:~/test$
|
上面通过将ftruncate的第二个参数设置为11,从而将test.txt文件的尺寸调整到11个字节,多出来的5个字节都被0填充。
如果将ftruncate的第二个参数设置为0,就可以清空原文件的内容。使用ftruncate方法时,必须确保fd是以可写的方式打开的,否则会抛出Invalid argument的错误。
os模块的getcwdu方法:
getcwdu与getcwd方法类似,都可以获取到当前的工作目录。只不过getcwd返回的是普通的字符串对象,而getcwdu则返回的是Unicode字符串对象,其语法格式如下:
os.getcwdu() -> unicode string path |
例如下面这段代码:
import os
def whatisthis(s):
if isinstance(s, str):
return "ordinary string"
elif isinstance(s, unicode):
return "unicode string"
else:
return "not a string"
cwd = os.getcwd()
cwdu = os.getcwdu()
print 'getcwd:', cwd, ' [type: ' + whatisthis(cwd) + ']'
print 'getcwdu:', cwdu, ' [type: ' + whatisthis(cwdu) + ']'
|
这段代码的执行结果如下:
black@slack:~/test$ python test.py
getcwd: /home/black/test [type: ordinary string]
getcwdu: /home/black/test [type: unicode string]
black@slack:~/test$
|
脚本中通过isinstance来判断返回的对象类型。可以看到,getcwd与getcwdu返回的路径信息是一致的,只不过getcwd返回的是ordinary string(普通字符串),而getcwdu返回的则是unicode string(Unicode字符串)。
os模块的isatty方法:
在之前的文章中,我们介绍过文件对象的isatty方法。这里的os.isatty方法与文件对象的isatty方法相类似,都可以用来判断目标文件是否与一个终端设备相关联。只不过os.isatty方法需要接受文件描述符为输入参数,其语法格式如下:
os.isatty(fd) -> True or False |
fd为目标文件的描述符。如果返回True,则表明文件描述符与终端设备相关联,否则返回False。例如下面这段代码:
import sys
import os
print 'os.isatty(sys.stdout.fileno())', os.isatty(sys.stdout.fileno())
print 'os.isatty(sys.stdin.fileno())', os.isatty(sys.stdin.fileno())
fd = os.open('test.txt', os.O_RDONLY)
print 'test.txt isatty:', os.isatty(fd)
os.close(fd)
|
这段代码的执行结果如下:
black@slack:~/test$ python test.py
os.isatty(sys.stdout.fileno()) True
os.isatty(sys.stdin.fileno()) True
test.txt isatty: False
black@slack:~/test$
|
sys.stdin对应标准输入设备,sys.stdout对应标准输出设备。因此,对它们的文件描述符执行isatty方法都会返回True。而test.txt只是普通的磁盘文件,不与任何终端设备相关联,因此,对test.txt的文件描述符执行isatty判断,就会返回False。
os模块的link与symlink方法:
link方法可以为某个文件创建hard link(硬链接),symlink方法则可以用于创建symbolic link(符号链接)。这两个方法的语法格式如下(只存在于Unix系统中):
os.link(dst_path, link_name)
os.symlink(dst_path, symlink_name) |
dst_path表示需要建立链接的目标文件的路径,link_name表示需要创建的硬链接的名称,symlink_name表示需要创建的符号链接的名称。
有关符号链接与硬链接的区别,可以参考
http://www.geekride.com/hard-link-vs-soft-link/ 该链接对应的文章,符号链接也被叫做soft link(软链接)。
简单的来说,对于符号链接,系统会为其分配一个新的inode节点。对于fast symlink,在新的inode结构中就已经存储了需要链接的目标文件的路径信息。而对于slow symlink,则路径会存储在符号链接的磁盘数据中。新的inode节点号与符号链接名称,会被存储在目录入口中。对于硬链接,系统不会为其分配新的inode节点,而是直接将需要链接的目标文件的inode节点号与硬链接的名称写入到目录入口中。
因此,对于符号链接的查询过程会是:先通过符号链接名称在目录入口中找到新的inode节点号,由此节点号找到新分配的inode结构,如果是fast symlink,那么直接就可以从新的inode结构中得到目标文件的路径,接着系统会解析该路径,并通过解析的结果得到目标文件的inode节点号,最终找到目标文件的inode结构,有了inode结构,就可以得到目标文件的属性状态,以及目标文件数据在磁盘中的位置了。
对于硬链接,查询过程会是:通过硬链接名称在目录入口中直接得到目标文件的inode节点号,并通过该节点号直接就可以找到目标文件的inode结构了。因此,硬链接其实就是文件的另一个名称,包括你最开始创建某个文件时,为该文件设置的名称都属于文件的硬链接。只有当文件所有的硬链接名称都从目录入口处被删除后,此文件才会被真正的删除掉。一个文件的硬链接数,就是目录入口中所有这些硬链接名称的总数。
从两者的查询过程可以看出,硬链接比符号链接的查询过程要快的多,此外,硬链接相当于给文件做了个简单的保护,因为只有当所有的硬链接名称都被删除后,文件才会被真正的删除掉,如果你不小心删除了某个硬链接名称时,只要其他硬链接没被删除,那么文件数据就不会被删除。
如果你要跨越文件系统,或者说跨分区,来链接文件的话,就只能用符号链接。此外,如果你想对目录创建链接的话,也只能使用符号链接。有关符号链接的更多内容,可以参考
https://en.wikipedia.org/wiki/Symbolic_link 该链接对应的维基百科文章。
下面是一段简单的测试代码:
import os
os.link('test.txt', 'my_hardlink')
print 'create my_hardlink to test.txt'
os.symlink('test.txt', 'my_symlink')
print 'create my_symlink to test.txt'
|
这段代码的执行结果如下:
black@slack:~/test$ python test.py
create my_hardlink to test.txt
create my_symlink to test.txt
black@slack:~/test$ stat my_symlink
File: `my_symlink' -> `test.txt'
Size: 8 Blocks: 0 IO Block: 4096 symbolic link
Device: 802h/2050d Inode: 110360 Links: 1
Access: (0777/lrwxrwxrwx) Uid: ( 1001/ black) Gid: ( 100/ users)
Access: 2015-12-07 20:46:30.204973495 +0800
Modify: 2015-12-07 20:46:29.189973267 +0800
Change: 2015-12-07 20:46:29.189973267 +0800
Birth: -
black@slack:~/test$ stat my_hardlink
File: `my_hardlink'
Size: 6 Blocks: 8 IO Block: 4096 regular file
Device: 802h/2050d Inode: 110531 Links: 2
Access: (0644/-rw-r--r--) Uid: ( 1001/ black) Gid: ( 100/ users)
Access: 2015-12-07 20:46:30.203973517 +0800
Modify: 2015-12-07 15:17:39.985019002 +0800
Change: 2015-12-07 20:46:29.188973267 +0800
Birth: -
black@slack:~/test$ stat test.txt
File: `test.txt'
Size: 6 Blocks: 8 IO Block: 4096 regular file
Device: 802h/2050d Inode: 110531 Links: 2
Access: (0644/-rw-r--r--) Uid: ( 1001/ black) Gid: ( 100/ users)
Access: 2015-12-07 20:46:30.203973517 +0800
Modify: 2015-12-07 15:17:39.985019002 +0800
Change: 2015-12-07 20:46:29.188973267 +0800
Birth: -
black@slack:~/test$ find ./ -samefile test.txt
./my_hardlink
./test.txt
black@slack:~/test$
|
可以看到,系统会为my_symlink符号链接分配一个新的inode节点,例如本例中就是Inode: 110360 。而my_hardlink会与test.txt共用同一个文件的inode节点,上面显示它们的节点号都是110531。通过find ./ -samefile test.txt命令,就可以在当前目录内找到test.txt对应的文件的所有的硬链接名称,从显示结果中可以看出,my_hardlink与test.txt都是同一个文件的硬链接。
os模块的listdir方法:
listdir可以将目录中包含的文件名,子目录名等以列表的形式作为结果返回。其语法格式如下:
os.listdir(path) -> list_of_strings |
path用于指定目录的路径,返回的结果列表中,将包含该目录里所有的文件名和子目录名,但是不包括 '.' 和 '..' (也就是当前目录与父目录)。并且列表中的名称是以任意的顺序进行排列的,因此在使用时,你需要自行对列表中的成员进行排序处理。
下面是一个简单的例子:
black@slack:~/test$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46)
....................................................
>>> import os
>>> os.listdir('tmpdir')
['tmp2.txt', 'mydir', 'hardlink', 'tmp.txt']
>>> quit()
black@slack:~/test$ ls tmpdir
hardlink mydir tmp.txt tmp2.txt
black@slack:~/test$
|
listdir返回的结果,与ls命令显示的结果是一致的,只不过文件与目录名的排列顺序不一样而已。
我们可以借助前面提到过的os.lstat方法,加上这里介绍的os.listdir方法,做出一个跨平台的ls程式:
import os, stat, sys, time
def convmode(mode):
ret = ''
ret = 's' if (mode & 0770000 == stat.S_IFSOCK) else \
'l' if (mode & 0770000 == stat.S_IFLNK) else \
'-' if (mode & 0770000 == stat.S_IFREG) else \
'b' if (mode & 0770000 == stat.S_IFBLK) else \
'd' if (mode & 0770000 == stat.S_IFDIR) else \
'c' if (mode & 0770000 == stat.S_IFCHR) else \
'f' if (mode & 0770000 == stat.S_IFIFO) else '?'
ret += 'r' if (mode & stat.S_IRUSR) else '-'
ret += 'w' if (mode & stat.S_IWUSR) else '-'
if(mode & stat.S_ISUID):
ret += 's'
elif(mode & stat.S_IXUSR):
ret += 'x'
else:
ret += '-'
ret += 'r' if (mode & stat.S_IRGRP) else '-'
ret += 'w' if (mode & stat.S_IWGRP) else '-'
if(mode & stat.S_ISGID):
ret += 's'
elif(mode & stat.S_IXGRP):
ret += 'x'
else:
ret += '-'
ret += 'r' if (mode & stat.S_IROTH) else '-'
ret += 'w' if (mode & stat.S_IWOTH) else '-'
if(mode & stat.S_ISVTX):
ret += 't'
elif(mode & stat.S_IXOTH):
ret += 'x'
else:
ret += '-'
return ret
max_len_lst = [0,0,0,0,0,0,0]
show_lst = []
def file_info(fpath, base = True):
statinfo = os.lstat(fpath)
smode = convmode(statinfo.st_mode)
lst = [0,0,0,0,0,0,0]
lst[0] = smode
max_len_lst[0] = max(max_len_lst[0], len(lst[0]))
lst[1] = str(statinfo.st_nlink)
max_len_lst[1] = max(max_len_lst[1], len(lst[1]))
lst[2] = str(statinfo.st_uid)
max_len_lst[2] = max(max_len_lst[2], len(lst[2]))
lst[3] = str(statinfo.st_gid)
max_len_lst[3] = max(max_len_lst[3], len(lst[3]))
lst[4] = str(statinfo.st_size)
max_len_lst[4] = max(max_len_lst[4], len(lst[4]))
format = '%d' if os.name == 'nt' else '%e'
lst[5] = time.strftime('%b '+ format +' %H:%M %Y', \
time.localtime(statinfo.st_mtime))
max_len_lst[5] = max(max_len_lst[5], len(lst[5]))
lst[6] = (os.path.basename(fpath) if base else fpath)
max_len_lst[6] = max(max_len_lst[6], len(lst[6]))
show_lst.append(lst)
def print_all_lst():
for x in show_lst:
for i in range(0, 7):
if i == 6:
print x[i].ljust(max_len_lst[i])
else:
print x[i].rjust(max_len_lst[i]),
if(len(sys.argv) != 2):
sys.exit('usage: python '+ sys.argv[0] + ' <path>')
if(not os.path.exists(sys.argv[1])):
sys.exit(sys.argv[1] + ' is not exists')
if(os.path.isdir(sys.argv[1])):
lst = os.listdir(sys.argv[1])
totalfilenum = len(lst)
lst.sort()
print 'total file number:', totalfilenum
for fname in lst:
file_info(sys.argv[1] + '/' + fname)
print_all_lst()
elif(os.path.isfile(sys.argv[1])):
file_info(sys.argv[1], False)
print_all_lst()
else:
sys.exit(sys.argv[0] + 'must be file or directory')
|
当向脚本提供的path参数是一个目录时,会先通过os.listdir返回一个列表,接着通过列表的sort方法对列表里的文件名等进行排序。然后在file_info函数中,对每个文件执行os.lstat方法,来获取这些文件或目录的属性,比如文件的访问权限,文件的大小,文件的修改时间等。并将这些属性与文件名都加入到show_lst列表中,最后通过print_all_lst函数将show_lst中的信息逐条的显示出来。在最终的显示结果中,文件及目录的每个属性都会占用一列,每一列都有一个最大的宽度值,这些列宽存储在max_len_lst列表中,显示时,就会使用这些列宽与字符串的ljust或rjust方法来进行对齐。
这段代码在Linux中的执行结果如下:
black@slack:~/test$ python test.py
usage: python test.py <path>
black@slack:~/test$ python test.py ./
total file number: 16
-rwxr-xr-x 1 1001 100 6240 Dec 1 15:23 2015 a.out
-rw-r--r-- 1 0 0 927 Dec 1 12:37 2015 example.c
-rw-r--r-- 1 0 0 282 Nov 23 16:50 2015 example2.c
-rw-r--r-- 2 1001 100 6 Dec 7 15:17 2015 my_hardlink
lrwxrwxrwx 1 1001 100 8 Dec 7 20:46 2015 my_symlink
drwxr-xr-x 3 1001 100 4096 Dec 3 15:00 2015 mydir
drwxr-xr-x 4 1001 100 4096 Dec 3 14:56 2015 mydir2
lrwxrwxrwx 1 1001 100 8 Dec 5 16:24 2015 symlink
-rw-r--r-- 1 0 0 2566 Dec 8 14:51 2015 test.py
-rw-r--r-- 2 1001 100 6 Dec 7 15:17 2015 test.txt
-rwxr--r-- 1 1001 1001 24 Dec 2 15:02 2015 test.txt~
-rw-r--r-- 1 0 0 11842 Dec 3 17:22 2015 test2.py
-rwxr-xr-x 1 1001 100 5 Nov 27 15:14 2015 test2.txt
-rw-r--r-- 1 1001 100 6 Dec 5 16:25 2015 test3.txt
-rw-r--r-- 1 1001 100 12 Nov 25 15:59 2015 test3.txt~
drwxr-xr-x 3 0 100 4096 Dec 3 14:40 2015 tmpdir
black@slack:~/test$ python test.py test.txt
-rw-r--r-- 2 1001 100 6 Dec 7 15:17 2015 test.txt
black@slack:~/test$
|
显示结果中,第1列是文件的类型与读写执行的访问权限,第2列是文件的硬链接数,第3列是文件的owner的uid值,第4列是文件的group的gid值。第5列是文件的大小,第6列到第9列是文件的修改时间,最后一列则是文件或目录的名称。
在windows中的执行情况如下:
G:\Python27\mytest>..\python.exe test.py .\
total file number: 15
drwxrwxrwx 0 0 0 0 Nov 14 15:24 2015 Phone
drwxrwxrwx 0 0 0 0 Nov 19 17:26 2015 c_test
-rw-rw-rw- 0 0 0 52 Nov 19 17:39 2015 data
-rw-rw-rw- 0 0 0 88 Nov 13 13:52 2015 foo.txt
-rw-rw-rw- 0 0 0 0 Nov 09 11:19 2015 foo3.txt
-rw-rw-rw- 0 0 0 966 Nov 29 20:22 2015 link.lnk
drwxrwxrwx 0 0 0 0 Dec 03 15:43 2015 macTestPython
dr-xr-xr-x 0 0 0 0 Dec 03 15:04 2015 mydir
drwxrwxrwx 0 0 0 0 Dec 03 15:04 2015 mydir2
-rw-rw-rw- 0 0 0 2654 Dec 08 15:32 2015 test.py
-rw-rw-rw- 0 0 0 95 Dec 02 15:06 2015 test.txt
-rw-rw-rw- 0 0 0 7571 Dec 05 18:32 2015 test2.py
-rw-rw-rw- 0 0 0 5 Nov 25 13:06 2015 test2.txt
-rw-rw-rw- 0 0 0 15 Nov 25 13:06 2015 test3.txt
drwxrwxrwx 0 0 0 0 Dec 01 17:40 2015 tmpdir
G:\Python27\mytest>
|
在Mac OS X中的执行情况如下:
MacOSX:~ black$ python test.py /
total file number: 26
-rw-rw-r-- 1 0 80 12292 Dec 5 17:43 2015 .DS_Store
drwx------ 3 0 80 102 Nov 26 21:45 2013 .Spotlight-V100
d-wx-wx-wt 2 0 20 68 Nov 26 21:01 2013 .Trashes
---------- 1 0 80 0 Jun 23 14:19 2009 .file
drwx------ 5 0 80 170 Dec 6 19:46 2015 .fseventsd
-rw------- 1 0 0 65536 Nov 26 21:45 2013 .hotfiles.btree
drwxr-xr-x 2 0 0 68 May 19 02:29 2009 .vol
drwxrwxr-x 28 0 80 952 Nov 26 13:49 2013 Applications
drwxrwxr-t 54 0 80 1836 Nov 26 21:45 2013 Library
drwx------ 193 0 0 6562 Nov 26 21:05 2013 Mac OS X Install Data
drwxr-xr-x 2 0 0 68 Jun 23 14:19 2009 Network
drwxr-xr-x 4 0 0 136 Nov 26 21:09 2013 System
drwxr-xr-x 5 0 80 170 Nov 26 21:48 2013 Users
drwxrwxrwt 4 0 80 136 Dec 8 15:39 2015 Volumes
drwxr-xr-x 39 0 0 1326 Nov 26 21:06 2013 bin
drwxrwxr-t 2 0 80 68 Jun 23 14:19 2009 cores
dr-xr-xr-x 3 0 0 3977 Dec 8 15:37 2015 dev
lrwxr-xr-x 1 0 0 11 Nov 26 21:06 2013 etc
dr-xr-xr-x 2 0 0 1 Dec 8 15:38 2015 home
-rw-r--r-- 1 0 0 18672224 Aug 1 13:49 2009 mach_kernel
dr-xr-xr-x 2 0 0 1 Dec 8 15:38 2015 net
drwxr-xr-x 6 0 0 204 Nov 26 21:25 2013 private
drwxr-xr-x 64 0 0 2176 Nov 26 21:07 2013 sbin
lrwxr-xr-x 1 0 0 11 Nov 26 21:06 2013 tmp
drwxr-xr-x 13 0 0 442 Nov 26 15:14 2013 usr
lrwxr-xr-x 1 0 0 11 Nov 26 21:06 2013 var
MacOSX:~ black$
|
os模块的lseek方法:
os模块的lseek方法类似于文件对象的seek方法,这两个方法都可以调整文件的当前文件指针。只不过,os.lseek方法还会将调整后的新的文件指针的位置作为结果返回,因此,os.lseek还可以实现类似文件对象的tell方法,以及可以用lseek来获取文件的尺寸大小。其语法格式如下:
os.lseek(fd, offset, from) -> newpos |
lseek会以from参数作为参考点,在该点基础上进行offset偏移(以字节为偏移单位),操作成功后,会将新的文件指针的位置作为结果返回。当from为os.SEEK_SET或者整数0时,就是相对于文件的开头的第一个字节进行偏移。当from为os.SEEK_CUR或者整数1时,就是相对于当前的文件指针进行偏移。当from为os.SEEK_END或者整数2时,就是相对于文件的结尾位置处进行偏移。
例如下面这段代码:
import os
fd = os.open('test.txt', os.O_RDWR)
curpos = os.lseek(fd, 0, os.SEEK_CUR)
print 'current position of file pointer:', curpos
print 'test.txt current file length:', os.lseek(fd, 0, os.SEEK_END)
print 'set file pointer to the end'
os.write(fd, 'black world\n')
print 'write "black world\\n" at the end of test.txt'
flen = os.lseek(fd, 0, os.SEEK_END) # or use os.SEEK_CUR in this case
print 'test.txt file length:', flen
os.lseek(fd, 0, os.SEEK_SET)
print os.read(fd, flen)
|
上面使用os.lseek(fd, 0, os.SEEK_CUR)来获取当前文件指针的位置,以实现类似文件对象的tell方法。并使用os.lseek(fd, 0, os.SEEK_END)来获取文件的尺寸大小。这段代码的执行结果如下:
black@slack:~/test$ cat test.txt
hello world
black@slack:~/test$ python test.py
current position of file pointer: 0
test.txt current file length: 12
set file pointer to the end
write "black world\n" at the end of test.txt
test.txt file length: 24
hello world
black world
black@slack:~/test$
|
这段代码在windows中的执行情况如下:
G:\Python27\mytest>..\python.exe test.py
current position of file pointer: 0
test.txt current file length: 13
set file pointer to the end
write "black world\n" at the end of test.txt
test.txt file length: 26
hello world
black world
G:\Python27\mytest>
|
windows会将普通文本文件中写入的\n换行符转为\r\n,因此,上面的file length(文件长度),比Linux中显示的数值要大,就是因为多了\r字符。
os模块的major、minor及makedev方法:
之前介绍os.stat方法时,提到过设备ID,每个设备ID都可以分解为主次设备号,通过主次设备号就可以确定具体的设备类型。在Linux的内核源码中有一个devices.txt文件,该文件中就记录了完整的设备类型,以及不同设备类型所对应的主次设备号:
black@slack:~/test$ cat /usr/src/linux/Documentation/devices.txt | less
LINUX ALLOCATED DEVICES (2.6+ version)
Maintained by Alan Cox <[email protected]>
Last revised: 6th April 2009
This list is the Linux Device List, the official registry of allocated
device numbers and /dev directory nodes for the Linux operating
system.
The latest version of this list is available from
http://www.lanana.org/docs/device-list/ or
ftp://ftp.kernel.org/pub/linux/docs/device-list/. This version may be
newer than the one distributed with the Linux kernel.
....................................................
0 Unnamed devices (e.g. non-device mounts)
0 = reserved as null device number
See block major 144, 145, 146 for expansion areas.
1 char Memory devices
1 = /dev/mem Physical memory access
2 = /dev/kmem Kernel virtual memory access
3 = /dev/null Null device
4 = /dev/port I/O port access
5 = /dev/zero Null byte source
6 = /dev/core OBSOLETE - replaced by /proc/kcore
7 = /dev/full Returns ENOSPC on write
8 = /dev/random Nondeterministic random number gen.
9 = /dev/urandom Faster, less secure random number gen.
10 = /dev/aio Asynchronous I/O notification interface
11 = /dev/kmsg Writes to this come out as printk's
12 = /dev/oldmem Used by crashdump kernels to access
the memory of the kernel that crashed.
....................................................
8 block SCSI disk devices (0-15)
0 = /dev/sda First SCSI disk whole disk
16 = /dev/sdb Second SCSI disk whole disk
32 = /dev/sdc Third SCSI disk whole disk
...
240 = /dev/sdp Sixteenth SCSI disk whole disk
Partitions are handled in the same way as for IDE
disks (see major number 3) except that the limit on
partitions is 15.
....................................................
|
当设备ID为0x802时,二进制的高8位表示主设备号,低8位则表示次设备号。因此,0x802中的主设备号为8,次设备号为2。对照上面devices.txt文件中的内容来看,当主设备号为8时,表示该设备是一个SCSI磁盘设备。当次设备号为1到15之间时,表示当前设备为第一个SCSI磁盘中的某个分区。这里次设备号为2,就说明0x802表示第一个SCSI磁盘中的第二个分区,在Linux中对应的设备文件为/dev/sda2 (设备文件名中的sd表示SCSI disk,a表示第一个磁盘,2表示第二个分区)。
os模块提供了major与minor方法,可以从设备ID中分解出主次设备号,这两个方法的语法格式如下(只存在于Unix系统中):
os.major(device_id) -> device major number
os.minor(device_id) -> device minor number |
例如下面这段代码:
import os
statinfo = os.lstat('test.txt')
print 'test.txt device ID:', hex(statinfo.st_dev)
print 'dev major:', os.major(statinfo.st_dev)
print 'dev minor:', os.minor(statinfo.st_dev)
statinfo = os.lstat('/dev/tty')
print '\n/dev/tty device ID:', hex(statinfo.st_rdev)
print 'rdev major:', os.major(statinfo.st_rdev)
print 'rdev minor:', os.minor(statinfo.st_rdev)
|
这段代码的执行结果如下:
black@slack:~/test$ python test.py
test.txt device ID: 0x802L
dev major: 8
dev minor: 2
/dev/tty device ID: 0x500
rdev major: 5
rdev minor: 0
black@slack:~/test$
|
对于普通的磁盘文件,例如上例中的test.txt文件,我们需要获取st_dev值来判断该文件位于哪个磁盘设备上。而对于设备文件,例如上面的/dev/tty文件,我们则需要获取st_rdev的值,来判断该设备文件对应的是什么类型的设备。st_dev与st_rdev的区别请参考之前介绍的os.stat方法。
我们当然也可以通过位运算来手动提取主次设备号:
import os
statinfo = os.lstat('test.txt')
print 'test.txt device ID:', hex(statinfo.st_dev)
print 'dev major:', int(statinfo.st_dev >> 8 & 0xff)
print 'dev minor:', int(statinfo.st_dev & 0xff)
|
执行结果如下:
black@slack:~/test$ python test.py
test.txt device ID: 0x802L
dev major: 8
dev minor: 2
black@slack:~/test$
|
如果想通过主次设备号组建设备ID的话,就可以使用os模块的makedev方法,该方法的语法格式如下(只存在于Unix系统中):
os.makedev(major, minor) -> device ID |
major表示主设备号,minor表示次设备号。该方法执行后会将主次设备号构成的设备ID作为结果返回,例如下面这段代码:
import os
statinfo = os.lstat('test.txt')
major = os.major(statinfo.st_dev)
minor = os.minor(statinfo.st_dev)
print 'major:', major
print 'minor:', minor
print 'makedev(major, minor):', hex(os.makedev(major,minor))
print '(major << 8 | minor):', hex((major << 8 & 0xff00) | (minor & 0xff))
|
这段代码的执行结果如下:
black@slack:~/test$ python test.py
major: 8
minor: 2
makedev(major, minor): 0x802
(major << 8 | minor): 0x802
black@slack:~/test$
|
可以看到,我们除了可以用makedev方法来构建设备ID外,还可以通过位运算来手动构建设备ID。
os模块的makedirs、removedirs及renames方法:
makedirs类似于之前文章中介绍过的os.mkdir方法,它们都可以用于创建目录。只不过makedirs还会将路径中间不存在的目录也一并创建了,例如,假设要创建/home/black/mydir/subdir/blackdir的目录,如果路径中间的mydir与subdir一开始不存在的话,makedirs会自动帮你先创建好中间的这两个目录,最后再创建blackdir目录。如果是os.mkdir的话,就无法创建中间的目录。
在作者的Linux系统中,os.makedirs的Python源代码定义在/usr/local/lib/python2.7/os.py文件中。通过阅读源码,我们就可以知道它的工作原理:
def makedirs(name, mode=0777):
"""makedirs(path [, mode=0777])
Super-mkdir; create a leaf directory and all intermediate ones.
Works like mkdir, except that any intermediate path segment (not
just the rightmost) will be created if it does not exist. This is
recursive.
"""
head, tail = path.split(name)
if not tail:
head, tail = path.split(head)
if head and tail and not path.exists(head):
try:
makedirs(head, mode)
except OSError, e:
# be happy if someone already created the path
if e.errno != errno.EEXIST:
raise
if tail == curdir: # xxx/newdir/. exists if xxx/newdir exists
return
mkdir(name, mode)
|
它会先将路径通过path.split分解为head(上层路径)与tail叶目录,例如对于mydir/subdir/blackdir,会分解出来的上层路径就是mydir/subdir,分解出来的叶目录则是blackdir。如果head上层路径不存在时,则会递归调用makedirs去创建上层路径。目录的具体创建工作最终都会交由mkdir方法去执行。mode参数最终也是传递给mkdir,并由其来设置目录的访问权限的。
上面的源码中,已经显示出了makedirs的语法格式:
os.makedirs(path [, mode=0777]) |
path为需要创建的目录路径,mode则为创建目录时需要设置的读写执行的访问权限,有关mode的具体详情包括mode与umask之间的公式,请参考之前
"Python基本的I/O操作 (二)"文章中的
"os模块的mkdir方法"的内容。
下面是一段简单的测试代码:
import os
os.makedirs('mydir/subdir/blackdir')
print 'create mydir/subdir/blackdir'
|
这段代码的执行结果如下:
black@slack:~/test$ python test.py
create mydir/subdir/blackdir
black@slack:~/test$ ls mydir
subdir
black@slack:~/test$ ls mydir/subdir/
blackdir
black@slack:~/test$
|
在作者的/usr/local/lib/python2.7/os.py文件中,还可以看到removedirs方法的源代码:
def removedirs(name):
"""removedirs(path)
Super-rmdir; remove a leaf directory and all empty intermediate
ones. Works like rmdir except that, if the leaf directory is
successfully removed, directories corresponding to rightmost path
segments will be pruned away until either the whole path is
consumed or an error occurs. Errors during this latter phase are
ignored -- they generally mean that a directory was not empty.
"""
rmdir(name)
head, tail = path.split(name)
if not tail:
head, tail = path.split(head)
while head and tail:
try:
rmdir(head)
except error:
break
head, tail = path.split(head)
|
从源码中可以看到,removedirs方法会先将叶目录删除掉,再循环将上层路径中所有的空目录都删除掉,直到遇到非空的上层目录时,才会跳出循环。还可以看到,removedirs最终也是通过之前文章中介绍过的rmdir方法来执行具体的删除操作的。其语法格式在上面的源码中已经显示出来了:
path为需要删除的目录路径,以下是一段简单的测试代码:
import os
os.removedirs('mydir/subdir/blackdir')
print 'remove mydir/subdir/blackdir'
|
这段代码的执行结果如下:
black@slack:~/test$ ls mydir
subdir
black@slack:~/test$ ls mydir/subdir/
blackdir
black@slack:~/test$ python test.py
remove mydir/subdir/blackdir
black@slack:~/test$ ls mydir
ls: cannot access mydir: No such file or directory
black@slack:~/test$
|
可以看到,在删除blackdir后,上层的subdir与mydir也都被删除掉了,因为,blackdir被删除掉后,subdir就变为了空目录,因此被删除掉,subdir被删除掉后,mydir又变为了空目录,因此也被删除掉了。如果删除blackdir后,subdir中还存在其他文件或目录的话,subdir与mydir就都不会被执行删除操作。
在上面提到的os.py文件中,还可以看到renames方法的源代码:
def renames(old, new):
"""renames(old, new)
Super-rename; create directories as necessary and delete any left
empty. Works like rename, except creation of any intermediate
directories needed to make the new pathname good is attempted
first. After the rename, directories corresponding to rightmost
path segments of the old name will be pruned way until either the
whole path is consumed or a nonempty directory is found.
Note: this function can fail with the new directory structure made
if you lack permissions needed to unlink the leaf directory or
file.
"""
head, tail = path.split(new)
if head and tail and not path.exists(head):
makedirs(head)
rename(old, new)
head, tail = path.split(old)
if head and tail:
try:
removedirs(head)
except error:
pass
|
renames方法可以对文件或目录进行重命名操作。第一个old参数表示文件或目录的原路径,第二个new参数表示重命名后的新路径。它会先通过makedirs方法将new路径中不存在的目录自动帮你创建好,再调用rename方法对文件或目录进行重命名,最后会通过removedirs方法将old路径中所有上层的空目录都给删除掉。
下面是一段简单的测试代码:
import os
os.renames('mydir/subdir/blackdir/test.txt', 'mydir2/sdir/bdir/test2.txt')
print 'rename mydir/subdir/blackdir/test.txt to mydir2/sdir/bdir/test2.txt'
|
这段代码的执行结果如下:
black@slack:~/test$ mkdir mydir
black@slack:~/test$ mkdir mydir/subdir
black@slack:~/test$ mkdir mydir/subdir/blackdir
black@slack:~/test$ vim mydir/subdir/blackdir/test.txt
black@slack:~/test$ python test.py
rename mydir/subdir/blackdir/test.txt to mydir2/sdir/bdir/test2.txt
black@slack:~/test$ ls mydir2/
sdir
black@slack:~/test$ ls mydir2/sdir/
bdir
black@slack:~/test$ ls mydir2/sdir/bdir/
test2.txt
black@slack:~/test$ ls mydir
ls: cannot access mydir: No such file or directory
black@slack:~/test$
|
可以看到,renames方法会自动创建mydir2,sdir及bdir目录。在test.txt移动到bdir目录中并重命名为test2.txt后,blackdir,subdir及mydir目录都会被自动删除掉(test.txt移走后,blackdir就是空目录了,就会被删除,blackdir被删除后,subdir也变为空目录了,也会被删除,subdir删除后,mydir也因为变为了空目录而被删除)。
在Unix系统中使用上面介绍的makedirs,removedirs及renames方法时,需要注意权限问题,如果权限不够,可能会导致执行失败。
结束语:
文章中涉及到的相关链接地址如下(可能都需要通过代理才能正常访问):
http://www.thegeekstuff.com/2012/01/linux-inodes/ 记录了与Inode相关的信息。
http://flashdba.com/4k-sector-size/ 记录了与4K扇区相关的内容。
https://docs.python.org/2/library/os.html#os.stat 该链接对应的Python官方手册中,可以看到stat方法返回的对象里,哪些属性是通用的,哪些是某些系统所专有的。
http://superuser.com/questions/640164/disk-usage-of-a-symbolic-link 这篇帖子里记录了有关fast symlinks的相关内容。
http://www.geekride.com/hard-link-vs-soft-link/ 有关符号链接与硬链接的区别。
https://en.wikipedia.org/wiki/Symbolic_link 符号链接相关的维基百科文章。
限于篇幅,本章先到这里,其他的内容将放到以后的章节中。
OK,就到这里,休息,休息一下 o(∩_∩)o~~
If you can't explain it simply, you don't understand it well enough.
(如果你不能简单说清楚,就是你没完全明白)
—— 阿尔伯特·爱因斯坦 (Albert Einstein)