本篇文章由网友“小黑”投稿发布。 pathconf与fpathconf方法可以在Unix系统中,获取与文件或终端相关的一些配置值,这些配置值也可以看作是Unix对文件或终端的限制值。例如,这两个方法可以获取到最多可以为某个文件创建多少个hard link(硬链接),还可以获取到文件名所支持的最大长度等...

    页面导航: 前言:

    本篇文章由网友“小黑”投稿发布。

    这篇文章是接着上一篇的内容来写的。

    相关英文教程的下载链接,以及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系统中):

os.ftruncate(fd, length)

    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方法来执行具体的删除操作的。其语法格式在上面的源码中已经显示出来了:
   
os.removedirs(path)

    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)
 
上下篇

下一篇: Python基本的I/O操作 (五)

上一篇: Python基本的I/O操作 (三)

相关文章

Python定义和使用模块

Python基本的I/O操作 (一)

Python的time模块

Python基本的I/O操作 (二)

Python的数字类型及相关的类型转换函数

Python基本的操作运算符