第5章 Linux系统管理

2021-03-23  本文已影响0人  super_pcm

5.1 文件读写

文件可以从多个维度进行管理,例如,重命名文件、获取文件属性、判断文件是否存在、备份文件、读写文件等。

5.1.1 Python内置的open函数

在Python中,要对一个文件进行操作,只需要使用内置的open函数打开文件即可。open函数接受文件名和打开模式作为参数,返回一个文件对象。工程师通过文件对象操作文件,完成以后,调用文件对象的close方法关闭文件,如下:

f=open('data.txt')
f.close()

5.1.2 避免文件句柄泄露

在计算机程序中,每打开一个文件就要占用一个文件句柄,而一个进程拥有的文件句柄数事有限。此外,文件句柄也会消耗资源。所以,我们在打开文件的时候,记得要关闭文件,释放资源。在Python中,可以使用finally语句来保证,无论在什么情况下文件都会被关闭。如下:

try:
    f=open('data.txt')
    ...
finally:
    f.close()

Python中还有更加简洁优美的写法,就是使用上下文文管理器,使用上下文管理器的代码如下:

with open('data.txt') as f:
    print(f.read())

5.1.3 常见的文件操作函数

Python的文件对象有多种类型的函数,如刷新缓存的flush函数。获取文件位置的tell函数,改变文件读写偏移量的seek函数。但是,工作中使用最多的还是与读写相关的函数。
Python提供了三个读相关得到函数,分别是read、readline和readlines,他们的作用如下:

可以看到read和readlines函数都是一次性把文件的所有内容都读出,如果是大文件,有OOM的风险。

Python提供了两个写函数,分别是write和writelines,它们的区别如下:

在Python中,除了使用文件对象的write和writelines函数向文件写入数据以外,也可以使用print函数将输出结果输出到文件中。print函数比write和writelines函数更加灵活,如下:

from __future__ import print_function
with open('/tmp/data.txt', 'w') as f:
    print(1,2,'hello world',sep=",", file=f)

5.1.4 Python的文件是一个可迭代对象

如果我们要以行为单位依次处理文件中的每一行,应该怎么读取文件呢?如果使用readline或者readlines函数,但是每读一行就调用一次readline函数这种方式效率很低,并且会使得我们的代码非常混乱;如果使用readlines,加载大文件又是一个问题。
在Python中,还有更好的方式来依次处理文件的内容,即使用for循环遍历文件。为什么在Python中能使用for来循环遍历文件呢?因为Python的for循环比大家看到的还要通用,它不但可以遍历如字符串、列表、元组这个可迭代的序列,我们还可以使用迭代器协议来遍历可迭代对象。而Python的文件对象实现了迭代器协议,因此我们可以在for循环中遍历文件内容。如下:

with open('data.txt') as f:
    for line in f:
        print(line.upper)

5.2 文件与文件路径管理

介绍os模块,os模块对操作系统的API做了封装,并且使用统一的API访问不同的操作系统的相同功能。

5.2.1 使用os.path进行路径和文件管理

在os模块中最常见的就是getcwd和listdir函数了,前者用来获取当前目录,后者用来列出目录下的所有文件和文件夹。如下:

import os
os.getcw
os.listdir('.')
  1. 拆分路径
    os.path模块用来对文件和路径进行管理,显然,它会包含很多拆分路径的函数。相关的函数有:
path='/home/pangcm/test.txt'
os.path.split(path)
os.path.basename(path)
os.path.dirname(path)
os.path.splitext(path)
  1. 构建路径
    os.path能拆分路径,自然也能构建路径。最常用的函数是expanduser、abspath和join函数。它们的作用:
import  os
os.getcwd()
os.path.expanduser('~')
os.path.expanduser('~pangcm')
os.path.expanduser('~pangcm/test')
os.path.abspath('.')
os.path.abspath('./test')
os.path.join('~','t','a.py')

前面介绍os.path模块构建路径时,介绍了abspath来返回绝对路径。相应地,os.path还存在一个函数用判断一个路径是否为绝对路径。

os.path.isabs('.')

在Python代码中,可以使用 '__file__' 这个特殊的变量表示当前代码所在的源文件。在编写代码时,有时需要导入当前源文件父目录下的软件包。因此,需要到这里的路径函数获取源文件的父目录。如下:

import os
os.path.abspath(__file__)
  1. 获取文件属性
    下面几个函数用来获取文件的属性:
  1. 判断文件类型
    下面截个行数用来判断文件路径是否存在以及路径所指文件的类型。
#获取当前用户home目录下所有的文件列表
[item for item in os.listdir(os.path.expanduser('~')) if os.path.isfile(item)]
#获取当前用户home目录下所有的目录列表
[item for item in os.listdir(os.path.expanduser('~')) if os.path.isdir(item)]
#获取当前用户home目录下所有的目录名到绝对路径之间的字典
{item: os.path.realpath(item) for item in os.listdir(os.path.expanduser('~')) if os.path.isdir(item)}

5.2.2 使用os模块管理文件和目录

前面已经介绍了getcwd函数,该函数用来获取当前目录,与之相关的chdir函数,该函数用来修改当前目录。

import os
os.getcwd()
os.chdir(os.path.expanduser('~pangcm'))

os模块还包含了文件和目录的操作函数,包括创建目录、删除目录、删除文件、重命名文件。

os模块也包含了修改文件权限、判断文件权限的函数,即chmod和access。chmod用来修改文件的权限,access用来判断文件是否具有相应的权限。在Linux中,权限分为读、写和执行。因此,os模块也提供了三个常量来表示读、写、可执行权限,即R_OK、W_OK和X_OK。
下面实例演示chmod和access的用法:

#!/usr/bin/python

from __future__ import print_function
import os
import sys

def main():
    sys.argv.append("")
    filename = sys.argv[1]
    if not os.path.isfile(filename):
        raise SystemExit(filename + ' does not exits')
    elif not os.access(filename, os.R_OK):
        os.chmod(filename,0777)
    else:
        with open(filename) as f:
            print(f.read)
if __name__ == '__main__':
    main()

5.3 查找文件

5.3.1 使用fnmatch找到特定的文件

大部分情况下,使用字符串匹配查找特定的文件就能满足需求,如果需要更加灵活的字符串匹配,可以使用标准库的fnmatch。这个库专门用来进行文件名匹配,支持使用通配符进行字符串匹配。支持的字符串列表如下:

通配符 含义
* 匹配任何数量的字符
? 匹配单个字符
[seq] 匹配seq中的字符
[!seq] 匹配除了seq以外的任何字符

fnmatch这个库比较简单,只有4个函数,分别是:

使用示例:

import fnmatch
[ name for name in os.listdir('.') if fnmatch.fnmatch(name, '*.gz')]
[ name for name in os.listdir('.') if fnmatch.fnmatch(name, '[a-c]*')]
[ name for name in os.listdir('.') if fnmatch.fnmatch(name, '[a-c]*.gz')]

fnmatchcase函数与fnmatch函数几乎一样,只是在匹配文件名时会忽略文件名中字母的大小写。filter函数与fnmatch函数比较类似,区别在于fnmatch每次对一个文件名进行匹配判断,filter函数每次都一组文件名进行匹配判断。filter函数接受文件名列表为第一个参数,文件名模式为第二个参数,然后以列表的形式返回输出例表中符合模式的文件名。

5.3.2 使用glob找到特定的文件

在前面我们都是通过os.listdir获取文件列表,然后再去匹配。在Python中还有更加简单的方式,那就是使用标准库的glob库。glob的作用相当于os.listdir加上fnmatch。如下:

import glob
glob.glob('*.txt')
glob.glob('[a-c]?.txt')

5.3.3 使用os.walk遍历目录树

前面的示例都是查找当前目录下的内容,很多时候我们还需要查找其子目录里面匹配的内容。这时候就需要使用os模块中的walk函数了。对于每一个目录,walk返回一个三元组(dirpath,dirnames,filenames)。其中dirpath保存的是当前目录,dirnames是当前目录的子目录列表,filenames是当前目录下的文件列表。下面演示其用法

import os
import fnmatch

images = ['*.jpg','*.jpeg','*.png','*.tif']
matches = []

for root,dirnames,filenames in os.walk(os.path.expanduser('~pangcm')):
    for extensions in images:
        for filename in fnmatch.filter(filenames,extensions):
            matches.append(os.path.join(root,filename))
        print(matches)

如果要忽略某个子目录,只需要修改三元组中的dirnames即可。

if 'exclude_dir' in dirnames:
    dirnames.remove('exclude_dir')

5.4 高级文件处理接口shutil

如果读者对比一下os模块的函数和shutil模块中的函数,会发现它们有一些重叠。例如os.rename和shutil.move都可以用来重命名一个文件。那么,为什么会存在两个模块提供相同功能的情况呢?这就涉及标准库模块定位的问题,os模块是对操作系统的接口进行封装,主要作用是跨平台。shutil模块包含复制、移动、重命名和删除文件及目录的函数,主要作用是管理文件和目录。因此,他们并不冲突,并且是互补的关系。对于常见的文件操作,shutil更易于使用。我们应该尽可能使用shutil里面的函数,在shutil里面没有相应的功能情况下再使用os模块下的函数。

5.4.1 复制文件和文件夹

shutil里面的复制类函数很多,用得最多的是copy和copytree。前者是复制一个文件,后者用来复制整个目录。

import shutil
shutil.copy('a.py','b.py')
shutil.copytree('dir1','dir2')

5.4.2 文件和文件夹的移动与改名

shutil模块中的move函数的作用几乎等同于Linux下的mv命令。可以用来移动或者重命名文件

shutil.move('b.py','a.py')
shutil.move('a.py','dir1')

5.4.3 删除目录

shutil.rmtree函数能够删除非空的目录,比起os.rmdir实用多了。

shutil.rmtree('dir1')

5.5 文件内容管理

5.5.1 目录和文件比较

filecmp模块包含了比较目录和文件的操作。其中filecmp模块最简单的函数是cmp函数,该函数用来比较两个文件是否相同,如果文件相同就返回True,否则返回False。

import filecmp

filecmp.cmp('a.txt','b.txt')

filecmp目录下还有一个名为cmpfiles的函数,该函数用来同时比较两个不同目录下的多个文件,并且返回一个三元组。分别包含相同的文件、不同的文件和无法比较的文件。

filecmp.cmpfiles('dir1','dir2',['a.txt', 'b.txt', 'c.txt', 'a_copy.txt'])

filecmp也可以用来对比两个目录,但是需要传入两个目录的所有文件,这个比较麻烦。比较目录的话,可以使用dircmp函数。调用dircmp函数以后会返回一个dircmp类的对象,该对象保存了诸多属性,工程师可以通过读取这些属性的方式获取目录之间的差异。要注意的是,dircmp不会去比较子目录下的文件的差异。

5.5.2 MD5校验和比较

在Python中计算文件的MD5校验码可以使用标准库中的hashlib模块

import hashlib

d = hashlib.md5()
with open('/etc/passwd') as f:
    for line in f:
        d.update(line)

d.hexdigest()

5.6 使用Python管理压缩包

5.6.1 使用tarfile库读取与创建tar包

在Linux中,tar命令可以创建一个tar包,而不进行压缩,这是为了方便传输。在Python中,tarfile标准库提供了相同的功能,我们可以使用它来创建一个压缩或者不压缩的tar包。

  1. 读取tar包
    tarfile中有不少函数,其中最常用的有:

示例:

import tarfile
with tarfile.open('tarfile_add.tar') as t:
    for member_info in t.getmembers():
        print(member_info.name)
  1. 创建tar包
    创建tar包的方式和写一个文件比较类似,如下:
import tarfile
with tarfile.open('tarfile_add.tar',mode=w) as out:
    out.add('Readme.txt')

5.6.2 使用tarfile库读取与创建压缩包

下面演示读取和创建经过压缩过的tar包

#读取
with tarfile.open('tarfile_add.tar',mode='r:gz') as t:
#创建
with tarfile.open('tarfile_add.tar',mode='w:bz2') as out:

5.6.4 使用zipfile库创建和读取zip压缩包

在windows下zip格式的压缩方式更为常见,下面介绍zipfile库来读取和创建zip压缩包

  1. 读取zip文件
    zipfile的常用方法有:

使用示例:

import zipfile
example_zip = zipfile.ZipFile('example.zip')
example_zip.namelist()
  1. 创建zip文件
import zipfile
new_zip = zipfile.ZipFile('new.zip','w')
new_zip.write('spam.txt')
new_zip.close()
  1. 使用Python的命令行工具创建zip格式的压缩包
    在Linux中,我们使用zip命令来创建zip压缩包,使用unzip命令来解压。但是,Linux默认是没有安装这两个软件的。如果不想安装这两个软件,由于实现这功能可以使用Python中的zipfile模块,其提供了命令行接口。zipfile模块的命令行接口包含以下几个选项:

下面是示例:

pythom -m zipfile -c monty.zip  spam.txt eggs.txt
pythom -m zipfile -e monty.zip target-dir/
pythom -m zipfile -l monty.zip  

5.6.6 使用shutil创建和读取压缩包

shutil模块是高层次的文件接口,除了包含文件和目录的操作函数以外,还包含了压缩包的创建和压缩的函数。并且使用起来更加简单,shutil支持的格式可以通过get_archive_formats函数获取。

  1. shutil创建压缩包
    使用shutil模块创建压缩包,只需要调用shutil模块下的make_archive函数即可。make_archive函数有多个参数,其中只有base_name与format这两个参数是必填的。参数base_name说是创建压缩包的名称,参数format用来指定压缩的格式。下面是一个示例:
import shutil

shutil.make_archive('backup','gztar')

需要注意的是base_name是压缩包的名称,但是不包含文件拓展名,拓展明会更加压缩的格式自动添加上去的。

  1. 在Python3中使用shutil读取压缩包
    在Python2中,shutil模块包含了创建压缩包的函数,并没有解压的函数。在Python3中,shutil模块包含了解压函数,名为unpack_archive函数。示例:
import shutil

shutil.unpack_archive('backup.tar.gz')

5.7 Python中执行外部命令

5.7.1 subprocess模块简介

subprocess模块最早在Python2.4版本中引入的,正如它名字所反应的,这个模块用于创建和管理子进程。它提供了高层次的接口,用来替换os.system(),os.spawn*()等模块和函数。subprocess其实非常简单,它提供了一个名为Popen的类来启动和设置子进程的参数。由于这个类比较复杂,subprocess还提供了若干便利函数。这些便利函数都是对Popen这个类的封装,以便于工程师能够快速启动一个子进程并获取他们的输出结果。

5.7.2 subprocess模块的便利函数

在subprocess模块中启动子进程,最简单的方式就是使用这一节介绍的便利函数。当这些便利函数不能满足需求时,再去使用底层的Popen类。便利函数包括:call、check_call与check_ouput.

  1. call
    call 函数的定义如下
subprocess.call(args,*,stdin=None,stdout=None,stderr=None,shell=False)

call函数运行由args参数指定的命名直到命令结束。call函数的返回值是命令的退出状态码,工程师可以退出状态码判断命令是否执行成功。如下:

import subprocess
subprocess.call(['ls','-l'])
subprocess.call('exit 1',shell=True)

call函数执行的外部命令以一个字符串列表的形式进行传递,如果设置了shell为True,则可以使用一个字符串命令,而不是一个字符串列表来运行子进程。如果设置了shell为True,Python将先运行一个shell,再用这个shell来解析整个字符串。

  1. check_call
    与call函数类似,不同是是如果命令执行失败,check_call不是返回非0,而是抛出subprocess.CalledProcessError异常。
In [7]: subprocess.check_call('exit 1',shell=True)
---------------------------------------------------------------------------
CalledProcessError                        Traceback (most recent call last)
<ipython-input-7-0e1de6e31a15> in <module>()
----> 1 subprocess.check_call('exit 1',shell=True)

/usr/lib64/python2.7/subprocess.pyc in check_call(*popenargs, **kwargs)
    540         if cmd is None:
    541             cmd = popenargs[0]
--> 542         raise CalledProcessError(retcode, cmd)
    543     return 0
    544 

CalledProcessError: Command 'exit 1' returned non-zero exit status 1
  1. check_output
    上面两个函数的输出结果都是再命令行终端,check_out的输出结果不会输出到命令行终端,而是可以进一步处理。如下
output=subprocess.check_output(['df','-h'])
 print(output)

check_output函数通过方式之来返回命令的执行结果,但是却没办法和call函数一样返回退出状态码来表示异常。因此,check_output函数通过抛出一个subprocess.CalledProcessError异常来表示命令执行出错。此外,check_output命令捕获命令的标准输出,要捕获错误输出,需要将错误输出重定向到标准输出,如下:

output=subprocess.check_output(['cmd','arg1','arg2'],stderr=subprocess.STDOUT)

5.7.3 subprocess模块的Popen类

当便利函数不能满足需求的时候,需要使用Popen类,它能处理更多的情况。Popen的基本使用方式与上一节中介绍的便利函数类似。在Linux系统中,到shell设置为True时,shell默认使用/bin/sh。args时需要执行的命令,可以时一个命令字符串,也可以时一个字符串列表。

Popen对象创建后,子进程便会运行。Popen类提供了若干方法来控制子进程的运行,包括:

其中,使用communicate函数可以与子进程进行交互,包括输入数据,获取子命令的标准输出和错误输出。下面的函数对Popen执行shell命令进行封装,封装以后,只需要将要执行的命令传递给函数即可。当命令执行成功时,将返回命令的退出状态码和标准输出,当命令执行失败时,将返回退出状态码和错误输出。

def excute_cmd(cmd):
    p = subprocess.Popen(cmd,shell=True,stdin=subprocess.PIPE,
                        stdout=subprocess.PIPE,stderr=subprocess.PIPE)
    stdout,stderr = p.communicate()
    if p.returncode != 0:
        return p.returncode,stderr
    return p.returncode, stdout
上一篇下一篇

猜你喜欢

热点阅读