【Python】python脚本实例

python脚本的实例

本文通过一个简单的python脚本实例,来介绍python语法。
参数:ip地址,端口号port
需求:

  1. 首先进行ping,如果ping不成功就就进行traceroute
  2. 如果ping得通就行telnet
  3. traceroute的结果要输出最后一跳的地址
  4. telnet如果成功输出成功,不成功且失败信息报refused则输出“端口未开启”,如果不成功且失败信息没有报refused则报访问不通
  5. 需要进行一个ip校验,看是否符合格式

1.首先编写ip地址检验的方法

import sys
import re

def checkIp(host):
    ip = host
    # 检验ip地址中的正则
    res = re.match(r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$", ip)
    if res:
        print ("ip is ok")
        return 0
    else:
        print ("ip is error")
        return 1

ip地址的检验逻辑非常简单,只要用正则表达式就可以。这里介绍一下python正则中的“原始字符串”功能。

反斜杠,在Python中比较特殊,就是它可以用来构成一些特殊字符,比如“\n”表示换行,“\t”表示制表符。下面是使用“\n”的一行代码:

>>>print ('Hello\World\nPython' )
结果为:
“Hello\World
Python“

可以看到其中的“\n”已转义为换行符,而“\W”没有发生转义,原因是“\W”在“字符串转义”中并不对应着特殊字符,没有特殊含义。

如果现在要求变了,要求不对“\n”转义为换行,而是原封不动输出为“Hello\World\nPython”,该怎么办呢?

1)可以这样写“Hello\World\nPython”,这样输出的时候,“字符串转义”会把“\”转义为“\”;
2)也可使用另一种方法:原始字符串;原始字符串(即r’…’):字符串中所有字符都直接按照字面意思来使用,不转义特殊字符。
下面是使用原始字符串的代码:

 print r'Hello\World\nPython' 
 结果为:
 “Hello\World\nPython”

可以清楚看到,在使用原始字符串之后,“\n”未被转义为换行符,而是直接被输出了。

1.1正则转义

上面讲的只是“字符串转义”。同理,在正则表达式中也存在转义,称其为“正则转义”,其与“字符串转义”完全不同,比如“\d”代表数字,“\s”代表空白符。下面我们先编写开头的例子,然后再分析。
需求:提取“3\8”反斜杠之前的数字:

import re 
string = '3\8' 
m = re.search('(\d+)\\\\', string) 

if m is not None: 
    print m.group(1)                  # 结果为:3 
n = re.search(r'(\d+)\\', string) 
if n is not None: 
    print n.group(1)                  # 结果为:3

正则表达式字符串需要经过两次转义,这两次分别是上面的“字符串转义”和“正则转义”,个人认为“字符串转义”一定先于“正则转义”。

1)’\\\\'的过程:
先进行“字符串转义”,前两个反斜杠和后两个反斜杠分别被转义成了一个反斜杠;即“\|\”被转成了“\|\”(“|”为方便看清,请自动忽略)。“字符串转义”后马上进行“正则转义”,“\\”被转义为了“\”,表示该正则式需要匹配一个反斜杠。

2)r’\\'的过程:
由于原始字符串中所有字符直接按照字面意思来使用,不转义特殊字符,故不做“字符串转义”,直接进入第二步“正则转义”,在正则转义中“\\”被转义为了“\”,表示该正则式需要匹配一个反斜杠。

也就是说原始字符串(即r’…’)与“正则转义”毫无关系,原始字符串仅在“字符串转义”中起作用,使字符串免去一次转义。

我们再来看这个检验ip地址的正则表达式

r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"

用圆括号将所有选择项括起来,相邻的选择项之间用|分隔,表示可以匹配25[0-5]或者2[0-4][0-9]或者[01]?[0-9][0-9]?,第一个就是250~255,第二个是200-249,第三个稍微复杂,?表示匹配0次或者1次前面的字符串,表示0-199。同时^表示匹配开头,配合{3}表示匹配3次,表示前面三个字符串为0.0.0.到255.255.255.。最后一个字符串用$匹配结尾。但用圆括号会有一个副作用,使相关的匹配会被缓存,导致匹配出现问题。此时可用?:放在第一个选项前来消除这种副作用。

因此该正则表达式的作用就是匹配0.0.0.0~255.255.255.255

2.traceroute函数编写

def trace(host):
    cmd = "traceroute {}".format(host)
    # 创建一个subproess实例
    p = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE)
    result = p.stdout.readlines()   # 读取输出
    print(type(result))                   # 查看类型
    out = result[-1]                       # 取得最后一跳地址的string
    res = out.split()                      # 将最后一跳的string按照空格分隔
    print (res[2])       
    return res[2]

可以看到这里使用了一个subprocess模块的使用,允许你去创建一个新的进程让其执行另外的程序,并与它进行通信,获取标准的输入、标准输出、标准错误以及返回码等。

2.1Popen类

subprocess模块中定义了一个Popen类,通过它可以来创建进程,并与其进行复杂的交互。我们可以用它来调用shell来在Linux系统中进行交互,查看一下它的构造函数:

__init__(self, args, bufsize=0, executable=None, 
stdin=None, stdout=None, stderr=None, preexec_fn=None, 
close_fds=False, shell=False, cwd=None, env=None, 
universal_newlines=False, startupinfo=None, 
creationflags=0)

主要参数说明:

  1. args:args should be a string, or a sequence of program arguments.也就是说必须是一个字符串或者序列类型(如:字符串、list、元组),用于指定进程的可执行文件及其参数。如果是一个序列类型参数,则序列的第一个元素通常都必须是一个可执行文件的路径。当然也可以使用executeable参数来指定可执行文件的路径。
  2. stdin,stdout,stderr:分别表示程序的标准输入、标准输出、标准错误。有效的值可以是PIPE,存在的文件描述符,存在的文件对象或None,如果为None需从父进程继承过来,stdout可以是PIPE,表示对子进程创建一个管道,stderr可以是STDOUT,表示标准错误数据应该从应用程序中捕获并作为标准输出流stdout的文件句柄。
  3. shell:如果这个参数被设置为True,程序将通过shell来执行。
  4. env:它描述的是子进程的环境变量。如果为None,子进程的环境变量将从父进程继承而来。

创建Popen类的实例对象

  1. res = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

  2. cmd:标准像子进程传入需要执行的shell命令,如:ls -al

  3. subprocess.PIPE:在创建Popen对象时,subprocess.PIPE可以初始化为stdin, stdout 或stderr的参数,表示与子进程通信的标准输入流,标准输出流以及标准错误

  4. subprocess.STDOUT:作为Popen对象的stderr的参数,表示将标准错误通过标准输出流输出。

因此我们可以看到p = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE)
中调用shell的命令来执行
利用type方法来判断result的类型,其中readlines()返回的是shell执行结果的list形式,二read()或者readline()返回的是string类型。我们需要得到traceroute的最后一跳结果,因此需要list类型的result,使用[-1]的切片来得到list的最后一个元素

result = p.stdout.readlines()  #返回一个list
print(type(result))
输出结果
traceroute to 192.168.229.2 (192.168.229.2), 30 hops max, 60 byte packets
1  192.168.229.1
2  192.168.229.2
3  192.168.229.3
4  192.168.229.4
5  192.168.229.5
out = result[-1]
输出结果
5  192.168.229.5

3.telnet函数的编写

def do_telnet(Host, port):
    cmd = "telnet {} {}".format(Host,port)
    out = commands.getstatusoutput(cmd)  # 输出的是元组
    print out
    index = out.index(256) 
    
    # 如果返回的结果中没有256,则证明返回码不是256,证明telnet成功
    if(index < 0):
        print ("telnet成功")
    # 如果telnet失败,且失败信息报refused,则输出检查机构端口
    elif(out[1].find('refused') > -1):
        print ("机构端口未开启,请检查贵司端口是否启用")
    # 如果失败信息没有报refused,则需要进行traceroute
    else:
        print ("Telnet不通,访问结果:xxx")

commands.getstatusoutput和subprocess方法一样也是调用shell命令。这个方法唯一的优点是,它不是一个阻塞的方法并且能够获得执行的返回码。而上面提到的subprocess的Popen方法会有阻塞的问题,就是它是一个阻塞的方法。如果运行cmd时产生的内容非常多,函数非常容易阻塞住。

3.ping函数的编写

def pingIp(host):
    ip = host
    res = checkIp(ip)
    if res:
        return 1
    else:
        backinfo = os.system('ping -c 5 -w 1 %s' % ip)
        # 如果ping不通,进入traceroute
        if backinfo:
            lastHop = trace(ip)
            print ("网络访问不通,traceroute最后一跳地址为{}".format(lastHop))
        # 如果ping通进行telnet
        else:
            do_telnet(ip,port)

这里介绍第三个可以调用shell的python模块,就是os.system,执行该函数的时候会把信息直接打印出来。并且我们无法以数据结构的形式保存结果,因此对于需要后续逻辑判断的函数就不适用了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章