Python OS SYS

OS 库

之前的 Linux 基础教程 中提到了一些可用于目录操作、文件操作的命令,在理解上述知识的基础上我们便可以很快上手 Python os 库的大多数方法。在 Python OS 文件/目录方法 | 菜鸟教程 (runoob.com) 中列出了多达 64 个方法,这里选取几个常用方法介绍。

os.chdir()

作用:改变当前工作目录(change directory),作用类似于 Shell 命令 cd

语法格式:os.chdir(path)

参数:

  • path:要切换到的路径

返回值:如果允许访问返回 True,否则返回 False

os.getcwd()

作用:返回当前工作目录(get current working directory),作用类似于 Shell 命令 pwd

语法格式:os.getcwd()

返回值:返回当前进程的工作目录

os.listdir()

作用:返回指定文件夹包含的文件或文件夹的名字的列表(list directory),作用类似于 Shell 命令 ls

语法格式:os.listdir(path)

参数:

  • path:需要列出的目录路径

返回值:指定路径 path 下包含的文件或文件夹的名字的列表

os.mkdir()

作用:以指定权限模式创建文件夹(make directory),作用类似于 Shell 命令 mkdir

语法格式:os.mkdir(path[,mode])

参数:

  • path:要创建的目录路径
  • mode:八进制整数,为目录设置的权限数字模式。默认为 0777。

os.mkdirs()

作用:以递归方式创建文件夹(make directories),作用类似于 Shell 命令 mkdir -p

语法格式:os.mkdirs(path[,mode])

参数:同 os.mkdir()

os.remove()

作用:删除指定路径的文件(remove),作用类似于 Shell 命令 rm

语法格式:os.remove(path)

参数:

  • path:要删除的文件路径

返回值:无返回值,但如果 path 是一个文件夹,会抛出 OSError

os.rmdir()

作用:删除指定路径的空目录(remove directory),作用类似于 Shell 命令 rmdir

语法格式:os.rmdir(path)

参数:

  • path:要删除的空目录路径

返回值:无返回值,但如果 path 是非空目录,会抛出 OSError

os.removedirs()

作用:递归地删除目录,如果子文件夹成功删除继续尝试父文件夹,直到抛出 Error。作用类似 rmdir -p

语法格式:os.removedirs(path)

参数:

  • path:要删除的目录路径

os.open()

作用:以指定打开选项打开一个文件。

语法格式:os.open(file,flags)

参数:

  • file:要打开的文件路径
  • flags:打开模式参数,可以是以下选项,多个则使用 | 隔开:
    • os.O_RDONLY: 以只读的方式打开
    • os.O_WRONLY: 以只写的方式打开
    • os.O_RDWR : 以读写的方式打开
    • os.O_NONBLOCK: 打开时不阻塞
    • os.O_APPEND: 以追加的方式打开
    • os.O_CREAT: 创建并打开一个新文件
    • os.O_TRUNC: 打开一个文件并截断它的长度为零(必须有写权限)
    • os.O_EXCL: 如果指定的文件存在,返回错误
    • os.O_SHLOCK: 自动获取共享锁
    • os.O_EXLOCK: 自动获取独立锁
    • os.O_DIRECT: 消除或减少缓存效果
    • os.O_FSYNC : 同步写入
    • os.O_NOFOLLOW: 不追踪软链接
  • mode:权限,默认为 0777,可以用以下选项按位生成:
    • stat.S_IXOTH: 其他用户有执行权0o001
    • stat.S_IWOTH: 其他用户有写权限0o002
    • stat.S_IROTH: 其他用户有读权限0o004
    • stat.S_IRWXO: 其他用户有全部权限(权限掩码)0o007
    • stat.S_IXGRP: 组用户有执行权限0o010
    • stat.S_IWGRP: 组用户有写权限0o020
    • stat.S_IRGRP: 组用户有读权限0o040
    • stat.S_IRWXG: 组用户有全部权限(权限掩码)0o070
    • stat.S_IXUSR: 拥有者具有执行权限0o100
    • stat.S_IWUSR: 拥有者具有写权限0o200
    • stat.S_IRUSR: 拥有者具有读权限0o400
    • stat.S_IRWXU: 拥有者有全部权限(权限掩码)0o700
    • stat.S_ISVTX: 目录里文件目录只有拥有者才可删除更改0o1000
    • stat.S_ISGID: 执行此文件其进程有效组为文件所在组0o2000
    • stat.S_ISUID: 执行此文件其进程有效用户为文件所有者0o4000
    • stat.S_IREAD: windows下设为只读
    • stat.S_IWRITE: windows下取消只读

返回值:返回新打开文件的描述符。

os.close()

作用:关闭指定的文件描述符

语法格式:os.close(fd)

参数:

  • fd:文件描述符

os.dup2()

作用:将指定文件描述符复制到指定文件描述符

语法格式:os.dup2(fd1,fd2)

参数:

  • fd1:要被复制的文件描述符
  • fd2:复制的文件描述符

os.rename()

作用:重命名文件或目录

语法格式:os.rename(src, dst)

参数:

  • src:要修改的目录名
  • dst:修改后的目录名

os.path

os.path 模块主要用于获取文件的路径,具有很多方法,详见 Python os.path() 模块 | 菜鸟教程 (runoob.com)

os.walk()

作用:迭代遍历目录,返回指定目录及其子目录下的文件名

语法格式:os.walk(top[, topdown=True[, onerror=None[, followlinks=False]]])

参数:

  • top:要遍历的目录的地址
  • topdown:默认为 True,优先遍历 top 目录,否则优先遍历子目录
  • onerror:默认为 None,可填入 callable 对象以供 walk() 异常时调用
  • followlinks:默认为 False;若为 True,则会遍历目录下的快捷方式(软连接)所指向的目录。

返回值:返回一个生成器,是一个三元组 (root, dirs, files)

  • root:当前正在遍历的这个文件夹本身的地址
  • dirs:一个 list,内容是该文件夹中所有目录的名字(不包括子目录)
  • files:一个 list,内容是该文件夹中所有的文件(不包括子目录)

walk()listdir() 主要的不同点:迭代。即前者会访问子目录

SYS 库

sys 库同样也是 Python 的内置库,下面介绍几个该库常见的方法或成员变量。

sys.argv

获取脚本执行参数列表。一般来说 Python 程序执行参数的第一个参数(sys.argv[0])是当前文件名。

sys.path

获取 Python 目录列表。列表中的这些目录供 Python 在其中查找安装的其他模块。在 Python 启动时,sys.path 根据内建规则和 PYTHONPATH 变量进行初始化。这个列表的第一个元素表示当前目录,在交互环境下通常是空字符串。

sys.platform

获取当前运行的平台。如 win32linux

sys.stdin

Python 的标准输入通道,默认为键盘输入字符。可以将该属性的值改为文件,这样,调用标准输入的方法(如 input())就会改为从文件读入。

如:

1
2
3
4
with open("input.txt") as f:
sys.stdin = f
x = input()
# 随后屏幕不会提示输入内容,而是直接从文件读取第一行不包含换行符的内容

sys.stdout

Python 的标准输出通道,默认为屏幕。也可以将该属性的值进行更改,调整输出流。

sys.err

Python 的标准错误输出流,默认为屏幕。

实战

使用 Python 打印命令行参数

  1. home/datawhale 目录下,昵称文件夹中新建 test5.py
  2. 脚本使用 sys 模块实现打印命令行参数功能

实操:

  1. 创建文件

    1
    [I have no name!@i-ym8u2kyp MarioZZJ]$ touch test5.py
  2. 编辑脚本

    1
    [I have no name!@i-ym8u2kyp MarioZZJ]$ vim test5.py

    脚本内容:

    1
    2
    3
    import sys
    for arg in sys.argv:
    print(arg)
  3. 脚本运行与结果

    1
    2
    3
    4
    [I have no name!@i-ym8u2kyp MarioZZJ]$ python test5.py argument1 argument2
    test5.py
    argument1
    argument2

使用 Python 扫描文件目录

  1. home/datawhale 目录下,昵称文件夹中新建 test5.py
  2. 脚本使用 os 模块实现打印 /usr/bin 路径下所有以 m 开头的文件

实操:

  1. 创建文件(略)

  2. 编辑脚本

    脚本内容:

    1
    2
    3
    4
    5
    import os
    for filename in os.listdir('/usr/bin'):
    # listdir 展示列表包含目录和文件,所以需要条件过滤
    if filename[0] == 'm' and os.path.isfile('/usr/bin/'+filename):
    print(filename)
  3. 脚本运行与结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    [I have no name!@i-ym8u2kyp MarioZZJ]$ python test5.py
    makedb
    make
    md5sum
    mkdir
    mkfifo
    mknod
    mktemp
    mv
    mesg
    mount
    msgattrib
    msgcat
    msgcmp
    msgcomm
    msgconv
    msgen
    msgexec
    msgfilter
    msgfmt
    msggrep
    msghack
    msginit
    msgmerge
    msgunfmt
    msguniq
    miniterm.py
    modutil
    mcookie
    more
    man
    mountpoint
    mkinitrd
    machinectl
    mapscrn
    mailq.postfix
    mailq
    manpath
    mandb

Python 模块化

Python 模块是一个 Python 脚本文件,其中可包含自定义的对象、方法、变量等,供其他 Python 脚本使用。

引入模块

import 语句

Python 脚本文件的后缀是 .py,通过 import 语句,可以实现对 Python 模块的引入。

如之前提到的 sys 库,当我们 import sys 时,实际上就引入了 Python 标准库中的 sys.py 模块。引入之后就可以使用 sys.* 调用 sys.py 中的方法或变量。

也可以使用 as 子句将引入的模块重命名。import sys as syst

__name__ 属性

**当一个模块被另一程序第一次引入时,其主程序将运行。**如果我们想在模块被引入时模块中的某一程序块不被执行,可以使用 __name__ 属性来使该程序块仅在该模块自身运行时执行。如:

1
2
3
4
5
6
# Filename: using_name.py

if __name__ == '__main__':
print('程序自身在运行')
else:
print('我来自另一模块')
1
2
3
4
5
6
$ python using_name.py
程序自身在运行

$ python
>>> import using_name
我来自另一模块

from ... import ... 语句

使用该语句可以从模块中导入指定部分到当前命名空间,如我们仅仅想引入 sys 中的 sys.argv,可以使用 from sys import argv,后面可以直接使用 argv 变量(而不是使用 sys.argv)。

这里也支持引入某模块的所有子对象,即 from [module_name] import *,但是一般不这么做,因为在很多库中子对象较多,会占用较大命名空间,随后自其他来源创建或引入的命名可能会与之矛盾。

包是一种管理 Python 模块命名空间的形式,它是一个分层次的文件目录结构,定义了一个由模块及子包,和子包下的子包等组成的 Python 应用环境。

形如 A.B 的模块中,A 一般为包名,B 为包 A 的子模块。在文件系统中体现为名为 A 的文件夹下的 B.py

为了避免非包文件夹被识别为包,只有目录下包含 __init__.py 的文件夹才会被识别为一个包。

1
2
3
4
5
/Package_a/
|---- __init__.py
|---- B.py
|---- not_package_folder # 没有 init,不是包
| ------- C.py

实战:在目录下创建包目录,并导入

  1. home/datawhale 目录下,昵称文件夹中新建 affairs 文件夹和 test6.py 文件,affairs 文件夹中新建 affairs.py

    1
    2
    3
    4
    5
    /home/datawhale/
    MarioZZJ/
    test6.py
    affairs/
    affairs.py
  2. affairs.py 可完成 https://mirror.coggle.club/dataset/affairs.txt 的读取(pandas.read_csv()

  3. test6.py 可以导入 affairs.py 代码,并进行命令行解析,输出 affairs.txt 的具体第几行内容(行数由命令行参数指定)

实操:

  1. 新建文件和文件夹

    1
    2
    3
    4
    [I have no name!@i-ym8u2kyp MarioZZJ]$ touch test6.py
    [I have no name!@i-ym8u2kyp MarioZZJ]$ mkdir affairs
    [I have no name!@i-ym8u2kyp MarioZZJ]$ cd affairs
    [I have no name!@i-ym8u2kyp affairs]$ touch affairs.py
  2. 编辑 affairs.py 脚本,内容:

    1
    2
    3
    4
    5
    6
    7
    8
    import pandas as pd

    def parse_line(line_num):
    txt = pd.read_csv('https://mirror.coggle.club/dataset/affairs.txt')
    if line_num > len(txt):
    raise ValueError("Input line number exceeded lenth of file.")
    else:
    return txt.iloc[line_num]

    因为 affairs.py 模块在 affairs 文件夹中,而 test6.pyaffairs 文件夹在同一层级,所以需要通过引入 affairs 包的子模块 affairs 实现模块引入,而目前 affairs 文件夹下没有 __init__.py ,该目录不能被识别为包,所以需要创建:

    1
    2
    3
    [I have no name!@i-ym8u2kyp affairs]$ touch __init__.py
    [I have no name!@i-ym8u2kyp affairs]$ ls
    affairs.py __init__.py
  3. 编辑 test6.py 脚本,内容:

    1
    2
    3
    4
    5
    6
    [I have no name!@i-ym8u2kyp MarioZZJ]$ cat test6.py
    from affairs import affairs
    import sys

    if __name__ == '__main__': # 输入参数为字符串,需转换为整数。因为行数比索引值多 1,须加 1
    print(affairs.parse_line(int(sys.argv[1]))+1)
  4. 运行 test6.py 脚本实现文件第 10 行的输出:

    1
    2
    3
    4
    5
    6
    7
    8
    [I have no name!@i-ym8u2kyp MarioZZJ]$ python3 test6.py 10
    rate_marriage 4.0
    age 23.0
    yrs_married 3.5
    children 1.0
    religious 3.0
    affairs 2.0
    Name: 10, dtype: float64

Linux 后台运行、关闭与查看后台任务

&

在命令的最后添加 &,即可将该命令放到后台执行,此时交互终端可以进行其他操作,而不是默认打印当前命令的输出并等待到进程结束。如:

1
python3 test.py &

& 的后台运行任务在后台执行,要求当前终端不能关闭。如果终端被关闭(除使用 Ctrl+D 关闭的方法外),执行的这条命令也会随之终止。

nohup

在命令之前添加 nohup,可将该命令放到后台执行。与 & 有所不同的是,使用 nohup 执行的后台任务在账户退出/终端关闭时仍然继续执行。如:

1
2
3
nohup python3 test.py
# 此时终端会提示:
nohup: ignoring input and appending output to ‘nohup.out’

如上,使用 nohup 后会提示忽略输入,输出日志将打印到工作目录下的 nohup.out 文件。此时如果关闭终端,命令仍然会在后台执行。如果不关闭终端,此时的终端是无法交互的。所以如果既要让命令在退出后仍可运行,又想继续和终端交互,可以将 nohup& 一起使用,即:nohup [command] &

如果想指定输出文件的位置(而不是 nohup.out),可以利用 Linux 的输出重定向符号 >nohup [command] > output.log &

Ctrl+Z

在命令执行过程中,按下 Ctrl+Z 可以挂起进程,此时该进程处于暂停状态,即不继续执行进程且稍后可以恢复。

jobs

使用 jobs 命令可以显示当前控制台创建的任务。 jobs 的状态可以是 runningstoppedTerminated。如使用 jobs -l 可以查看所有后台运行任务的 pid,这样可以根据 pid 操作对应进程。

1
2
[I have no name!@i-ym8u2kyp MarioZZJ]$ jobs -l
[1]+ 18860 Running python3 test_temp.py &

[ ] 内为进程 job 号,如这里为 1;后面为 pid,如这里为 18860)

bg

使用 bg 命令可以将后台暂停的命令变为在后台继续执行。(back go)

语法格式: bg <jobid>

该命令可以和 Ctrl+Zjobs 较好配合使用。一般常用的做法如:

1
2
3
4
5
6
7
8
9
10
11
[I have no name!@i-ym8u2kyp MarioZZJ]$ nohup python3 test_temp.py # 单独 nohup 执行命令
nohup: ignoring input and appending output to ‘nohup.out’
^Z # 使用 Ctrl + Z 挂起进程
[1]+ Stopped nohup python3 test_temp.py # 进程的状态为 Stopped
[I have no name!@i-ym8u2kyp MarioZZJ]$ jobs -l # jobs 查看所有后台进程,1 状态 stopped
[1]+ 20145 Stopped nohup python3 test_temp.py
[I have no name!@i-ym8u2kyp MarioZZJ]$ bg 1 # bg 让 1 在后台继续执行
[1]+ nohup python3 test_temp.py &
[I have no name!@i-ym8u2kyp MarioZZJ]$ jobs -l # jobs 查看所有后台进程,1 状态 running
[1]+ 20145 Running nohup python3 test_temp.py &
[I have no name!@i-ym8u2kyp MarioZZJ]$

fg

使用 fg 命令可以将后台命令变为在前台执行。(fore go)

语法格式:fg [jobid]

kill

使用 kill 命令可以结束(kill)进程。

语法格式:

  1. kill %<jobid>
  2. kill <pid>

tmux

tmux 是一个终端复用软件,使用 tmux 可以实现在一个终端登录远程主机,并在该终端窗口中运行多个终端对话,也可按需使终端会话运行于后台或是按需接入、断开会话。下面列出 tmux 的主要操作,更多详细操作可参见 Tmux终端复用详解 (cnblogs.com)

安装

以我所使用的 CentOS 系统为例,需要先安装 tmux 才可使用:

1
yum install -y tmux

创建会话

使用 tmux 即可打开软件(单独使用 tmux 命令会创建一个会话,以数字命名),可以加上命令 new -s 对创建的会话进行命名:

1
tmux new -s name

tmux 界面

列出创建的所有会话

添加命令 ls 即可列出创建的所有会话

1
2
3
4
[I have no name!@i-ym8u2kyp ~]$ tmux ls
0: 1 windows (created Tue Dec 21 11:13:19 2021) [120x34]
name: 1 windows (created Tue Dec 21 11:25:02 2021) [88x15] (attached)
[I have no name!@i-ym8u2kyp ~]$

登录一个会话

添加命令 attach (或简写为 a)并附加参数 -t <会话名> 即可使用 tmux 登录一个已知会话。

退出会话

  • 依次按 Ctrl+bd 即可退出会话,但不关闭该会话。
  • Ctrl+d 退出并直接关闭该会话
  • tmux kill-session -t <会话名> 可以销毁指定会话。

快捷键

进入 tmux 面板后,按 Ctrl+b 再按其他按键,即可进行一系列快捷操作:

Ctrl+b 按键 说明
ctrl+b ? 显示快捷键帮助
ctrl+b [space] 采用下一个内置布局,这个很有意思,在多屏时,用这个就会将多有屏幕竖着展示
ctrl+b ! 把当前窗口变为新窗口
ctrl+b " 模向分隔窗口
ctrl+b % 纵向分隔窗口
ctrl+b q 显示分隔窗口的编号
ctrl+b o 跳到下一个分隔窗口。多屏之间的切换
ctrl+b / 上一个及下一个分隔窗口
ctrl+b C+/// 调整分隔窗口大小
ctrl+b & 确认后退出当前 tmux
ctrl+b [ 复制模式,即将当前屏幕移到上一个的位置上,其他所有窗口都向前移动一个。
ctrl+b c 创建新窗口
ctrl+b n 选择下一个窗口
ctrl+b l 最后使用的窗口
ctrl+b p 选择前一个窗口
ctrl+b w 以菜单方式显示及选择窗口
ctrl+b s 以菜单方式显示和选择会话。这个常用到,可以选择进入哪个 tmux
ctrl+b t 显示时钟。然后按 enter 键后就会恢复到 shell 终端状态
ctrl+b d 脱离当前会话;这样可以暂时返回 Shell 界面,输入 tmux attach 能够重新进入之前的会话

实战:在 Linux 系统后台运行应用程序,并打印日志

  1. home/datawhale 目录下,昵称文件夹中新建 sleep.py 文件,该脚本能一直运行并每隔 10 秒输出当前时间
  2. 使用适当的命令能使该程序后台运行,并将输出结果写入 txt 文件。

实操:

  1. 创建脚本,内容如下:

    1
    2
    3
    4
    5
    import time

    while(True):
    print(time.strftime("%Y-%m-%d %H:%M:%S",time.localtime()))
    time.sleep(10)
  2. 利用 nohup& 命令和输出重定向实现后台运行,并查看日志

    1
    2
    3
    4
    5
    6
    7
    [I have no name!@i-ym8u2kyp MarioZZJ]$ nohup python3 sleep.py > output.log & # 后台运行
    [1] 15563
    nohup: ignoring input and redirecting stderr to stdout
    [I have no name!@i-ym8u2kyp MarioZZJ]$ jobs -l # 查看 job,刚才的任务正在运行
    [1]+ 15563 Running nohup python3 sleep.py > output.log &
    [I have no name!@i-ym8u2kyp MarioZZJ]$ cat output.log # 查看输出内容,结果为空!!
    [I have no name!@i-ym8u2kyp MarioZZJ]$

    出现问题:使用 cat 查看输出的日志内容,为空!

    查询资料后发现原因:Python 的两个输出流 stdoutstderr 都是默认指向屏幕,我们的命令将其输出重定向到文件,但是二者机制不同。stderr 无缓存,程序每往 stderr 输出时都会立刻输出到指定的目标,而 stdout 有缓存,只有遇到需要清空缓存的情况才会向目标输出(如输出为屏幕时的换行,程序结束时的缓存清空等),为使运行过程中输出内容可以实时输出至日志,可以为 python 命令附加 -u 参数以取消缓存。

    重新操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [I have no name!@i-ym8u2kyp MarioZZJ]$ nohup python3 -u sleep.py > outlog.txt &
    [1] 18442
    nohup: ignoring input and redirecting stderr to stdout
    [I have no name!@i-ym8u2kyp MarioZZJ]$ jobs -l
    [1]+ 18442 Running nohup python3 -u sleep.py > outlog.txt &
    [I have no name!@i-ym8u2kyp MarioZZJ]$ cat outlog.txt
    2021-12-21 15:29:44
    2021-12-21 15:29:54
    [I have no name!@i-ym8u2kyp MarioZZJ]$