美文网首页
第5章 Linux系统管理

第5章 Linux系统管理

作者: super_pcm | 来源:发表于2021-03-23 19:26 被阅读0次

    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:读取文件中的所有内容
    • readline: 一次读取一行
    • readlines: 将文件内容存到一个列表中,列表中的每一行对应于文件中的一行。

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

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

    • 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模块用来对文件和路径进行管理,显然,它会包含很多拆分路径的函数。相关的函数有:
    • split: 返回一个二元组,包含文件的路径与文件名
    • dirname: 返回文件的路径
    • basename: 返回文件的文件名
    • splitext: 返回一个除去文件拓展名的部分和拓展名的二元组
    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函数。它们的作用:
    • expanduser: 展开用户的HOME目录,如~、~username
    • 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. 获取文件属性
      下面几个函数用来获取文件的属性:
    • getatime: 获取文件的访问时间
    • getmtime: 获取文件的修改时间
    • getctime: 获取文件的创建时间
    • getsize: 获取文件的大小
    1. 判断文件类型
      下面截个行数用来判断文件路径是否存在以及路径所指文件的类型。
    • exists: 参数path所指向的路径是否存在
    • isfile: 参数path所指向的路径存在,并且是一个文件
    • isdir: 参数path所指向的路径存在,并且是一个文件夹
    • islink: 参数path所指向的路径存在,并且是一个链接
    • ismount: 参数path所指向的路径存在,并且是一个挂载点
      下面有几个应用示例:
    #获取当前用户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模块还包含了文件和目录的操作函数,包括创建目录、删除目录、删除文件、重命名文件。

    • unlink/remove: 删除path路径所指向的文件
    • rmdir:删除path路径所指向的文件夹,该文件夹必须为空,否则报错
    • rename: 重命名文件或文件夹
    • mkdir: 创建一个文件夹

    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个函数,分别是:

    • fnmatch: 判断文件名是否符合特定的模式
    • fnmatchcase: 判断文件名是否符合特定的模式,不区分大小写
    • filter: 返回输入列表中,符合特定模式的文件名列表
    • translate: 将通配符模式转换成正则表达式

    使用示例:

    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中有不少函数,其中最常用的有:
    • getname: 获取tar包的文件列表
    • extract: 提取单个文件
    • extractall: 提取所有文件

    示例:

    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的常用方法有:
    • namelist: 返回zip文件中包含的所有文件和文件夹的字符串列表
    • extract: 从zip文件中提取单个文件
    • extractall: 从zip文件中提取所有文件

    使用示例:

    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模块的命令行接口包含以下几个选项:
    • -l: 显示zip格式压缩包中的文件列表
    • -c: 创建zip格式的压缩包
    • -e: 提取zip格式压缩包
    • -t: 验证文件是一个有效的zip格式的压缩包

    下面是示例:

    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类提供了若干方法来控制子进程的运行,包括:

    • wait: 等待子进程结束
    • poll: 检查子进程状态
    • kill: 给子进程发送SIGKILL信号终止子进程
    • send_signal: 给子进程发送信息
    • terminate: 给子进程发送SIGTERM信号终止子进程
    • communicate: 与子进程交互

    其中,使用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
    

    相关文章

      网友评论

          本文标题:第5章 Linux系统管理

          本文链接:https://www.haomeiwen.com/subject/gmxqhltx.html