自动化常规运维活动

第四章 自动化常规运维活动

系统管理员有需要执行的各种各样管理活动。这些活动可能包含文件处理、日志、管理CPU和内存、密码处理以及最为重要的进行备份。需要自动化这些活动。本章中,我们将学习使用Python来自动化这些活动。

本章中,我们将讨论如下课题:

  • 通过重定向、管道和输入文件来接收输入
  • 脚本中运行时密码处理
  • 执行外部命令并获取输出
  • 在运行时和验证时弹出密码输入
  • 读取配置文件
  • 在脚本中添加日志和警告代码
  • 为CPU和内存使用设置上限
  • 启动网页浏览器
  • 使用os模块处理目录和文件
  • (使用 rsync)创建备份

通过重定向、管道和输入文件来接收输入

在这部分中,我们将学习如何让用户接收通过重定向、管道和外部输入文件的输入。

对于接收重定向的输入,我们使用stdin。管道是另一种形式的重定向。这个概念是指将一个程序的输出作为另一个程序的输入。我们可以通过外部文件以及使用Python来接收输入。

通过重定向输入

stdin和stdout是由os模块创建的对象。我们将编写一个脚本来使用到stdin和stdout。

创建一个名为redirection.py的脚本并编写如下代码:

import sys

class Redirection(object):
        def __init__(self, in_obj, out_obj):
                self.input = in_obj
                self.output = out_obj
        def read_line(self):
                res = self.input.readline()
                self.output.write(res)
                return res

if __name__ == '__main__':
        if not sys.stdin.isatty():
                sys.stdin = Redirection(in_obj=sys.stdin, out_obj=sys.stdout)
        a = input("Enter a string: ")
        b = input("Enter another string: ")
        print('Entered strings are:', repr(a), 'and', repr(b))

运行以上程序如下:

$ python3 redirection.py

我们将得到如下的输出:

Enter a string: hello
Enter another string: python
Entered strings are: 'hello' and 'python'

程序在交互会话中运行时,stdin是键盘输入,stdout是用户的终端。input()函数用于从用户接收输入,print()是一种写到终端(stdout)的方式。

通过管道输入

管道(pipe)是另一种形式的重定向。 这一技术用于从一个程序向另一个程序传递信息。符号 | 表示管道。通过使用管道技术,我们可以使用两个以上的命令,将一个命令的输出作为下一个命令的输入。

下面我们来看看如何使用管道来接收输入。首先我们要写一个返回向下整除的简单脚本。创建一个名为accept_by_pipe.py的脚本并加入如下代码:

import sys

for n in sys.stdin:
        print(int(n.strip())//2)

运行脚本,我们将得到如下输出:

$ echo 15 | python3 accept_by_pipe.py

输出结果:
7

在以上脚本中,stdin是一个键盘输入。我们执行对运行时中所输入的数字进行向下整除。向下整除仅返回商的整数部分。运行程序时,我们通过管道符号 | 传入了15,然后接我们的脚本名称。因此,我们将15作为脚本的输入。因此执行了向下整除,我们得到的结果是7。

我们可以向脚本传入多个输入。下面一个执行中,我们传入了多个输入值:15, 45和20。为处理多个输入值,我们在脚本中编写了一个 for 循环。因此首先将15作为输入,接着是45,然后是20。每个输入的输出会在新行中打印,因为我们在输入值之间加了\n。为开启对反斜线的解释,我们传入了-e 标记:

$ echo -e '15\n45\n20' | python3 accept_by_pipe.py

输出结果:
7
22
10

进行这个运行后,我们分别得到了15, 45和20向下整除的结果7, 22和10,每个结果另起一行。

通过输入文件输入

在这一部分中,我们将学习如何从输入文件中接收输入。Python中接收输入文件来作为输入更为容易。我们将通过示例来进行查看。但首先要创建一个名为 sample.txt 的 简单文本文件,并写入如下代码:

# sample.txt

Hello World
Hello Python

下面创建一个名为accept_by_input_file.py的脚本并编写如下的代码:

i = open('sample.txt', 'r')
o = open('sample_output.txt', 'w')

a = i.read()
o.write(a)

运行程序,我们将得到如下输出:

$ python3 accept_by_input_file.py
$ cat sample_output.txt
Hello World
Hello Python

脚本中运行时密码处理

在这一部分中,我们来看看一个脚本中处理密码的简单示例。我们将创建一个名为handling_password.py的脚本并编写如下内容:

import sys
import paramiko
import time

ip_address = "192.168.2.106"
username = "student"
password = "training"
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.load_system_host_keys()
ssh_client.connect(hostname=ip_address, username=username, password=password)
print("Successful connection", ip_address)
ssh_client.invoke_shell()
remote_connection = ssh_client.exec_command('cd Desktop; mkdir work\n')
remote_connnection = ssh_client.exec_command('mkdir test_folder\n')
# print(remote_connection.read())
ssh_client.close()

运行以上脚本,将得到如下输出:

$ python3 handling_password.py

输出结果:
Successful connection 192.168.2.106

在上述脚本中,我们使用了paramiko模块。这个paramiko模块是一个ssh的Python实现,提供了客户端的功能。安装paramiko命令如下:

pip3 install paramiko

在前面的脚本中,我们远程连接了主机192.168.2.106。在脚本中提供了主机的用户名和密码。

在运行脚本之后,192.168.2.106的桌面上,可在192.168.2.106的home/目录内找到work和test_folder两个文件夹。

译者注:
1、IP、用户名、密码等相关信息请自行修改,包括 cd 进入的目录也请根据实际进行修改
2、解决运行中的警告

# 警告信息
CryptographyDeprecationWarning: encode_point has been deprecated on EllipticCurvePublicNumbers and will be removed in a future version. Please use EllipticCurvePublicKey.public_bytes to obtain both compressed and uncompressed point encoding.

# 解决方法
pip3 uninstall cryptography
pip3 install cryptography==2.4.2

# 原因:
这在 GitHub 是一个已知 issue,主要原因是默认安装的cryptography==2.5弃用了一些 API

执行外部命令并获取输出

在这一部分中,我们将学习Python的子进程模块。使用subprocess,可以很容易地生成新的进程并获取它们的返回码,执行外部命令和启用新的应用。
我们来看看如何通过subprocess模块执行外部命令并获取它们的输出。我们将创建一个名为execute_external_commands.py的脚本并编写如下脚本:

import subprocess
subprocess.call(["touch", "sample.txt"])
subprocess.call(["ls"])
print("Sample file created")
subprocess.call(["rm", "sample.txt"])
subprocess.call(["ls"])
print("Sample file deleted")

运行程序,我们将得到如下输出:

$ python3 execute_external_commands.py
accept_by_input_file.py       handling_password.py  sample.txt
accept_by_pipe.py	      redirection.py
execute_external_commands.py  sample_output.txt
Sample file created
accept_by_input_file.py  execute_external_commands.py  redirection.py
accept_by_pipe.py	 handling_password.py	       sample_output.txt
Sample file deleted

使用子进程模块捕获输出

在这一部分中,我们将学习如何捕获输出。我们可以传递PIPE作为标准输出stdout的参数来捕获输出。。编写名为capture_output.py的脚本并添加如下代码:

import subprocess
res = subprocess.run(['ls', '-l'], stdout=subprocess.PIPE,)
print('returncode:', res.returncode)
print('{} bytes in stdout:\n{}'.format(len(res.stdout), res.stdout.decode('utf-8')))

执行如下命令运行脚本:

$ python3 capture_output.py

通过执行,将得到如下输出:

returncode: 0
143 bytes in stdout:
accept_by_input_file.py
accept_by_pipe.py
capture_output.py
execute_external_commands.py
handling_password.py
redirection.py
sample_output.txt

译者注: 考虑版面以及与原书一致,以上在运行时去除了代码中的-l 参数

在以上脚本中,我们导入了Python的subprocess模块,有助于输出的捕获。子进程模块用于创建新的进程。它还有且于连接输入/输出管道并获取返回码。subprocess.run()运行作为参数传入的命令。returncode 是子进程的返回状态。在输出中,如果得到了返回码0,表示成功运行。

在运行时和验证时弹出密码输入

这一部分我们将学习getpass模块来在运行时处理密码。Python中的getpass()模块弹出让用户输入密码并不进行打印。getpass模块用于终端中程序需要处理密码弹出的用户交互。
我们来看看一些有关如何使用getpass模块的示例:

1、创建名为no_prompt.py的脚本并编写如下代码:

import getpass
try:
        p = getpass.getpass()
except Exception as error:
        print('ERROR', error)
else:
        print('Password entered:', p)

这个脚本中,我们没为用户提供提示文件。因此默认的提示内容为Password。

运行脚本如下:

$ python3 no_prompt.py
Password:
Password entered: abcd

2、我们将为密码输出添加提示文本。创建一个名为with_prompt.py的脚本并编写如下代码:

import getpass
try:
        p = getpass.getpass("Enter your password:")
except Exception as error:
        print('ERROR', error)
else:
        print('Password entered:', p)

这样我们就编写了一个带有密码输入提示文本的脚本。运行程序如下:

$ python3 with_prompt.py
Enter your password:
Password entered: abcd

这里,我们为用户提供了一个Enter your password 的提示。

现在我们来编写一个脚本,如果密码输入错误,将打印一条普通消息,并不再弹出输入正确密码的提示。

3、编写一个名为getpass_example.py的脚本并添加如下代码:

import getpass
passwd = getpass.getpass(prompt='Enter your password:')
if passwd.lower() == '#pythonworld':
        print('Welcome!!')
else:
        print('The password entered is incorrect!!')

运行程序如下(此处我们输入正确的密码,即#pythonworld):

$ python getpass_example.py

# 输出结果:
Enter your password:
Welcome!!

如果我们输入一个错误的密码,查看得到的消息:

$ python getpass_example.py

# 输出结果
Enter your password:
The password entered is incorrect!!

这里我们编写的脚本在密码输入错误时不会再要求重新输入密码。

下面我们再来编写一个脚本,在密码输入错误时要求重新输入正确的密码。为获取登录用户,我们使用了getuser()。getuser()函数将返回系统登录的用户。创建一个名为password_prompt_again.py的脚本并编写如下代码:

import getpass
user_name = getpass.getuser()
print('User Name : %s' % user_name)
while True:
        passwd = getpass.getpass('Enter your password : ')
        if passwd == '#pythonworld':
                print('Welcome!!!')
                break
        else:
                print('The password you entered is incorrect.')

运行程序,将得到如下输出:

$ python3 password_prompt_again.py
User Name : student
Enter your password :
The password you entered is incorrect.
Enter your password :
Welcome!!!

读取配置文件

这一部分中我们将学习Python中的configparser模块。通过使用configparser模块,我们可以管理应用的用户可编辑配置文件。

这些配置文件常用于用户或系统管理人员通过普通文本编辑器来编辑文件设置应用的一些默认值,然后应用会读取并解析文件,根据文件中写入的内容来进行运行。
读取配置文件可使用configparser中的read()方法。下面我们编写一个名为read_config_file.py的普通脚本。在这之前,编写一个名为read_simple.ini 的.ini文件并加入如下内容:

[bug_tracker]
url = https://www.baidu.com/

创建 read_config_file.py并添加如下内容:

from configparser import ConfigParser
p = ConfigParser()
p.read('read_simple.ini')
print(p.get('bug_tracker', 'url'))

运行read_config_file.py,我们将得到如下输出:

$ python3 read_config_file.py

# 输出结果:
https://www.baidu.com/

read()方法可接收一个以上的文件名。对每个文件名进行扫描并且该文件存在时,就会打开并读取。下面我们编写脚本来读取一个以上的文件名。创建名为read_many_config_file.py的脚本并编写如下代码:

from configparser import ConfigParser
import glob

p = ConfigParser()
files = ['hello.ini', 'bye.ini', 'read_simple.ini', 'welcome.ini']
files_found = p.read(files)
files_missing = set(files) - set(files_found)
print('Files found: ', sorted(files_found))
print('Files missing: ', sorted(files_missing))

运行以上脚本,我们将得到如下输出:

$ python3 read_many_config_file.py

# 输出结果:
Files found:  ['read_simple.ini']
Files missing:  ['bye.ini', 'hello.ini', 'welcome.ini']

在上面的例子中,我们使用了Python中的configparser模块,它有助于管理配置文件。首先我们创建一个名为files的列表。read()函数将读取配置文件。在本例中,我们创建了一名为files_found的变量,用于存储目录中存在的配置文件名。接着我们创建了另一个名为files_missing的变量,用于返回目录中不存在的文件名。最后,我们打印出了存在和不存在的文件名。

在脚本中添加日志和警告代码

这一部分中我们学习Python中的日志和警告模块。logging模块将记录程序中发生的事件。warnings向编程人员发出语言和库中发生的变化的警告。

下面我们来看一个简单的日志示例。我们将编写一个名为logging_example.py的脚本并编写如下代码:

import logging

LOG_FILENAME = 'log.txt'
logging.basicConfig(filename=LOG_FILENAME, level=logging.DEBUG,)
logging.debug('This message should go to the log file.')
with open(LOG_FILENAME, 'rt') as f:
        prg = f.read()
print('FILE:')
print(prg)

运行程序如下:

$ python3 logging_example.py

# 输出结果:
FILE:
DEBUG:root:This message should go to the log file.

查看 log.txt,我们可以看到脚本中打印的调试信息:

$ cat log.txt

# 输出结果: 
DEBUG:root:This message should go to the log file.

下面我们编写一个名为logging_warnings_codes.py的脚本并添加如下代码:

import logging
import warnings

logging.basicConfig(level=logging.INFO,)
warnings.warn('This warning is not sent to the logs')
logging.captureWarnings(True)
warnings.warn('This warning is sent to the logs')

运行脚本如下:

$ python3 logging_warnings_codes.py

# 输出结果:
logging_warnings_codes.py:5: UserWarning: This warning is not sent to the logs
  warnings.warn('This warning is not sent to the logs')
WARNING:py.warnings:logging_warnings_codes.py:7: UserWarning: This warning is sent to the logs
  warnings.warn('This warning is sent to the logs')

生成警告

warn()用于生成警告。下面我们来看一个生成警告的简单示例。编写名为generate_warnings.py的脚本并加入如下代码:

import warnings

warnings.simplefilter('error', UserWarning)
print('Before')
warnings.warn('Write your warning message here')
print('after')

运行脚本如下:

python3 generate_warnings.py

# 运行结果:
Before
Traceback (most recent call last):
  File "generate_warnings.py", line 5, in <module>
    warnings.warn('Write your warning message here')
UserWarning: Write your warning message here

在上面的脚本中,我们通过warn()传入了一条警告消息。我们使用了一个简单过滤器,这样我们的警告会作为 error 来处理,该错误将由编程人员进行相应的处理。

为CPU和内存使用设置上限

这部分中我们将学习如何设置 CPU和内存的使用限制。首先编写一个放置CPU使用限制的脚本。创建一个名为put_cpu_limit.py的脚本并添加如下代码:

import resource
import sys
import signal
import time

def time_expired(n, stack):
        print('EXPIRED :', time.ctime())
        raise SystemExit('(time ran out)')
signal.signal(signal.SIGXCPU, time_expired)
# Adjust the CPU time limit
soft, hard = resource.getrlimit(resource.RLIMIT_CPU)
print('Soft limit start as :', soft)
resource.setrlimit(resource.RLIMIT_CPU, (10, hard))
soft, hard = resource.getrlimit(resource.RLIMIT_CPU)
print('Soft limit changed to :', soft)
print()
# Consume some CPU time in a pointless exercise
print('Starting:', time.ctime())
for i in range(200000):
        for i in range(200000):
                v = i * i
# We should never make it this far
print('Exiting :', time.ctime())

运行上述脚本如下:

$ python3 put_cpu_limit.py
Soft limit start as : -1
Soft limit changed to : 10

Starting: Sun Feb 24 23:57:27 2019
EXPIRED : Sun Feb 24 23:57:37 2019
(time ran out)

译者注: 实际运行结果可能不是10秒,Alan 在本地一台资源有限的虚拟机上运行打印时间相关15秒,而 Mac 本机上则刚好10秒

在前面的脚本中,我们使用了setrlimit()来限制CPU的使用。在我们脚本中所设置的限制为10秒。

启动网页浏览器

这一部分中,我们将学习Python中的webbrowser模块。这一模块中带有可以在浏览器应用中打开URL的函数。我们来看一个简单的示例。创建一个名为open_web.py的脚本,并添加如下代码:

import webbrowser
webbrowser.open('https://www.baidu.com')

运行脚本如下:

$ python3 open_web.py

运行结果如下:
Url mentioned in open() will be opened in your browser.
webbrowser – Command line interface

译者注: 实际在 MacOS 上测试未输出相关信息,命令行仅输出 True,虚拟机命令行进入,未安装浏览器,无返回内容

我们还可以通过命令行来使用Python的webbrowser模块,可以使用所有的功能。要在命令行中使用webbrowser,运行命令如下:

python3 -m webbrowser -n https://www.baidu.com/

这里,https://www.baidu.com/ 会在一个浏览窗口中进行打开。我们可以使用如下两个选项:

  • -n:新窗口打开
  • -t:新标签页打开

使用os模块处理目录和文件

这一部分中,我们将学习Python中的os模块。Python的os模块有助于实现操作系统任务。要想实现这些操作系统任务,我们需要导入os模块。

我们来看一些处理文件和目录相关示例:

创建和删除模块

这一部分中,我们将创建一个脚本来看可在文件系统中处理目录的函数,包含创建、列出和删除其中内容。创建一个名为os_dir_example.py的脚本并编写如下代码:

import os

directory_name = 'abcd'
print('Creating', directory_name)
os.makedirs(directory_name)
file_name = os.path.join(directory_name, 'sample_example.txt')
print('Creating', file_name)
with open(file_name, 'wt') as f:
        f.write('sample example file')
print('Cleaning up')
os.unlink(file_name)
os.rmdir(directory_name) # Will delete the directory

运行脚本如下:

$ python3 os_dir_example.py

# 输出结果:
Creating abcd
Creating abcd/sample_example.txt
Cleaning up

使用mkdir()创建目录时,其所有父目录必须已经被创建。但在使用makedirs()创建目录时,会创建任意目录,即便是指定的路径并不存在。unlink()会删除文件路径, rmdir()会删除目录路径。

检查文件系统内容

这一部分中,我们将使用 listdir()列出目录中的所有内容。创建一个名为list_dir.py的脚本并编写如下代码:

import os
import sys

print(sorted(os.listdir(sys.argv[1])))

运行脚本如下:

$ python3 list_dir.py /home/student

# 输出结果:
['.bash_history', '.bash_logout', '.bashrc', '.cache', '.local', '.profile', '.python_history', '.ssh', '.viminfo', 'Chapter01', 'Chapter02', 'Chapter03', 'Chapter04', '__pycache__']

通过使用listdir(),我们可以列出文件夹中的所有内容。

(使用 rsync)创建备份

这是系统运维人员要做的最重要的工作。这一部分中,我们将学习使用rsync来进行备份。rsync命令用于拷贝本地和远程的文件和目录,并使用rsync执行数据备份。为此,我们来编写一个名为take_backup.py的脚本并编写如下代码:

import os
import shutil
import time
from sh import rsync

def check_dir(os_dir):
        if not os.path.exists(os_dir):
                print(os_dir, 'does not exist.')
                exit(1)

def ask_for_confirm():
        ans = input('Do you want to Continue? yes/no\n')
        global con_exit
        if ans == 'yes':
                con_exit = 0
                return con_exit
        elif ans == 'no':
                con_exit = 1
                return con_exit
        else:
                print('Answer with yes or no.')
                ask_for_confirm()

def delete_files(ending):
        for r, d, f in os.walk(backup_dir):
                for files in f:
                        if files.endswith('.' + ending):
                                os.remove(os.path.join(r, files))

backup_dir = input('Enter directory to backup\n') # Enter directory name
check_dir(backup_dir)
print(backup_dir, 'saved.')
time.sleep(3)
backup_to_dir = input('Where to backup?\n')
check_dir(backup_to_dir)
print('Doing the backup now!')
ask_for_confirm()
if con_exit == 1:
        print('Aborting the backup process!')
        exit(1)
rsync('-auhv', '--delete', '--exclude=list+found', '--exclude=/sys', '--exclude=/tmp', '--exclude=/proc', '--exclude=/mnt', '--exclude=/dev', '--exclude=/backup', backup_dir, backup_to_dir)

运行脚本结果如下:

$ python3 take_backup.py
Enter directory to backup
/home/student/work
/home/student/work saved.
Where to backup?
/home/student/Desktop
Doing the backup now!
Do you want to Continue? yes/no
yes

译者注:
1、如提示 ImportError: No module named ‘sh’请执行 pip3 install sh
2、Deskstop目录需自行创建

现在查看Desktop/目录,你会在该目录中发现 work 文件夹。rsync使用了一些选项,具体如下:

  • -a: 存档
  • -u: 升级
  • -h: 易于阅读的格式
  • -v: 详细信息(verbose)
  • –delete: 在接收端删除不相关文件
  • –exclude: 排队规则

总结

本章中我们学习了如何来自动化常规的运行任务。学习了通过不同技术来接收输入:运行时提示密码输入、执行外部命令、读取配置文件、在脚本中添加警告、通过脚本和命令行启动浏览器、使用os模块处理文件和目录,以及进行备份。

下一章中,我们将学习os和处理数据的相关知识。同时我们还会学习tarfile模块以及如何进行使用。

☞☞☞ 第五章 文件、目录和数据处理

课后问题

  1. 如何使用readline模块?
  2. 读取、创建新文件、删除文件、列出当前目录文件的Linux命令分别是什么?
  3. Python 中的哪些包可用于运行 Linux/Windows 命令?
  4. 如何读取或为配置文件 init 设置新值?
  5. 列出可用于查看 CPU 使用情况的库?
  6. 列出从用户接收输入的不同方法?
  7. sort和sorted之间的区别是什么?

扩展阅读

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值