python常用模块(二)
1.ConfigParser模块
用于生成和修改配置文档,在python3.x中变更为configparser
1 [DEFAULT] 2 ServerAliveInterval = 45 3 Compression = yes 4 CompressionLevel = 9 5 ForwardX11 = yes 6 7 [bitbucket.org] 8 User = hg 9 10 [topsecret.server.com] 11 Port = 50022 12 ForwardX11 = no
1 import configparser 2 3 config = configparser.ConfigParser() 4 config["DEFAULT"] = {'ServerAliveInterval': '45', 5 'Compression': 'yes', 6 'CompressionLevel': '9'} 7 8 config['bitbucket.org'] = {} 9 config['bitbucket.org']['User'] = 'hg' 10 config['topsecret.server.com'] = {} 11 topsecret = config['topsecret.server.com'] 12 topsecret['Host Port'] = '50022' # mutates the parser 13 topsecret['ForwardX11'] = 'no' # same here 14 config['DEFAULT']['ForwardX11'] = 'yes' 15 with open('example.ini', 'w') as configfile: 16 config.write(configfile) 17 18 #读取 19 >>> import configparser 20 >>> config = configparser.ConfigParser() 21 >>> config.sections() 22 [] 23 >>> config.read('example.ini') 24 ['example.ini'] 25 >>> config.sections() 26 ['bitbucket.org', 'topsecret.server.com'] 27 >>> 'bitbucket.org' in config 28 True 29 >>> 'bytebong.com' in config 30 False 31 >>> config['bitbucket.org']['User'] 32 'hg' 33 >>> config['DEFAULT']['Compression'] 34 'yes' 35 >>> topsecret = config['topsecret.server.com'] 36 >>> topsecret['ForwardX11'] 37 'no' 38 >>> topsecret['Port'] 39 '50022' 40 >>> for key in config['bitbucket.org']: print(key) 41 ... 42 user 43 compressionlevel 44 serveraliveinterval 45 compression 46 forwardx11 47 >>> config['bitbucket.org']['ForwardX11'] 48 'yes' 49 50 #增删改查 51 [section1] 52 k1 = v1 53 k2:v2 54 55 [section2] 56 k1 = v1 57 58 import ConfigParser 59 60 config = ConfigParser.ConfigParser() 61 config.read('i.cfg') 62 63 # ########## 读 ########## 64 #secs = config.sections() 65 #print secs 66 #options = config.options('group2') 67 #print options 68 69 #item_list = config.items('group2') 70 #print item_list 71 72 #val = config.get('group1','key') 73 #val = config.getint('group1','key') 74 75 # ########## 改写 ########## 76 #sec = config.remove_section('group1') 77 #config.write(open('i.cfg', "w")) 78 79 #sec = config.has_section('wupeiqi') 80 #sec = config.add_section('wupeiqi') 81 #config.write(open('i.cfg', "w")) 82 83 84 #config.set('group2','k1',11111) 85 #config.write(open('i.cfg', "w")) 86 87 #config.remove_option('group2','age') 88 #config.write(open('i.cfg', "w"))
2.hashlib模块
用于加密相关的操作,3.x里代替了md5模块和sha模块,主要提供 SHA1, SHA224, SHA256, SHA384, SHA512 ,MD5 算法
1 import hashlib 2 3 -----md5----- 4 md_five = hashlib.md5() 5 md_five.update(b'Hello World') #加密 6 7 print(md_five.digest()) #二进制hash 8 print(md_five.hexdigest()) #十六进制hash 9 10 ''' 11 def digest(self, *args, **kwargs): # real signature unknown 12 """ Return the digest value as a string of binary data. """ 13 pass 14 15 def hexdigest(self, *args, **kwargs): # real signature unknown 16 """ Return the digest value as a string of hexadecimal digits. """ 17 pass 18 19 ''' 20 ----sha1----- 21 md_five = hashlib.sha1() 22 md_five.update(b'Hello World') 23 24 print(md_five.hexdigest()) 25 26 -----sha256----- 27 md_five = hashlib.sha256() 28 md_five.update(b'Hello World') 29 30 print(md_five.hexdigest()) 31 32 -----sha384----- 33 md_five = hashlib.sha384() 34 md_five.update(b'Hello World') 35 36 print(md_five.hexdigest()) 37 38 -----sha512----- 39 md_five = hashlib.sha512() 40 md_five.update(b'Hello World') 41 42 print(md_five.hexdigest()) 43 44 45 #还有一个,这个就是相当于双层加密,大家应该都知道撞库啊,那么用了这个你解掉一层还有一层,是不是非常的牛X! 46 47 #HMAC:散列消息鉴别码,简称HMAC,是一种基于消息鉴别码MAC(Message Authentication Code)的鉴别机制。使用HMAC时,消息通讯的双方,通过验证消息中加入的鉴别密钥K来鉴别消息的真伪; 48 一般用于网络通信中消息加密,前提是双方先要约定好key,就像接头暗号一样,然后消息发送把用key把消息加密,接收方用key + 消息明文再加密,拿加密后的值 跟 发送者的相对比是否相等,这样就能验证消息的真实性,及发送者的合法性了。 49 50 #怎么用呢??? 51 import hmac 52 53 hm = hmac.new(b'Hello World!','你好世界!'.encode(encoding="utf-8")) 54 print(hm.digest()) 55 print(hm.hexdigest())
3.subprocess模块
这个模块跟os模块有点类似,也是可以和操作系统进行交互
我们这里举个例子
import os os.system("dir") #这里会直接打印到屏幕上,也就是直接输出结果,那我想储存到一个变量里可以吗? a = os.system("dir") a >>>0 #为什么输出的是0呢?os.system()只能输出命令结果到屏幕,赋值则返回的是命令执行结果,0为成功,失败则为非0而不是1 #那么我想保存到一个变量里就要用os.popen() os.popen("dir"),read
subprocess是从3.x的新模块,比os更好用,接下来我们就举一些例子
import subprocess #run subprocess.run("df -h",shell = True) #命令正常则执行0,命令错误会报错 #call subprocess.call("df -h",shell = True) #命令正常则执行并返回状态0,命令错误会报错并返回状态非0 #check_call subprocess.echk_call("df -h",shell = True) #同上 #getstatusoutput subprocess.getstatusoutput('mkdir -p /Q/Q/Q') >>>(0, '') #接收命令,以元组的方式返回,第一个为执行状态,第二个为执行结果 #getoutput #跟上面比没有执行状态 #check_output subprocess.check_output("ls -l",shell = True) #直接返回结果,以二进制方式返回 #上面的这些底层封装的都是subprocess.Popen 例子: a = subprocess.Popen("pwd",shell = True,stdout = subprocess.PIPE) a.stdout.read() #stdin:标准输入 #stdout:标准输出 #stderr:标准错误 #poll a = subprocess.Popen("sleep 10;echo 'hehe'",shell = True,stdout=subprocess.PIPE) print(a.poll()) >>>None print(a.poll()) >>>0 #查询执行状态,未执行完返回None,执行完返回0,执行失败返回非0,有一个类似的是wait() #terminate:杀掉所启动进程 #communicate:等待任务结束
可用参数:
- args:shell命令,可以是字符串或者序列类型(如:list,元组)
- bufsize:指定缓冲。0 无缓冲,1 行缓冲,其他 缓冲区大小,负值 系统缓冲
- stdin, stdout, stderr:分别表示程序的标准输入、输出、错误句柄
- preexec_fn:只在Unix平台下有效,用于指定一个可执行对象(callable object),它将在子进程运行之前被调用
- close_sfs:在windows平台下,如果close_fds被设置为True,则新创建的子进程将不会继承父进程的输入、输出、错误管道。
所以不能将close_fds设置为True同时重定向子进程的标准输入、输出与错误(stdin, stdout, stderr)。 - shell:同上
- cwd:用于设置子进程的当前目录
- env:用于指定子进程的环境变量。如果env = None,子进程的环境变量将从父进程中继承。
- universal_newlines:不同系统的换行符不同,True -> 同意使用 \n
- startupinfo与createionflags只在windows下有效
将被传递给底层的CreateProcess()函数,用于设置子进程的一些属性,如:主窗口的外观,进程的优先级等等
终端输入的命令分为两种:
- 输入即可得到输出,如:ifconfig
- 输入进行某环境,依赖再输入,如:python
4.logging模块
#做运维的肯定要记录日志,不管是服务的报警还是记录日常的错误以及一些其他信息,日志都是不可或缺的.在python中有一个logging模块使专门用来干这个的,可以用logging模块来记录各种格式的日志,熟悉日志程序的都知道,日志有五个级别,分别是debug()
, info()
, warning()
, error()
and critical(),让我们看看5个级别都如何使用
1 import logging 2 3 logging.warning("Hello World!") 4 logging.debug("Hello World!") 5 logging.info("Hello World!") 6 logging.error("Hello World!") 7 logging.critical("Hello World!") 8 >>>WARNING:root:Hello World! 9 ERROR:root:Hello World! 10 CRITICAL:root:Hello World! 11 12 #诶,这里为什么只打印除了"warning","error","critical"这三个级别呢? 13 14 #日志肯定是可以写到文件里的,so 15 logging.basicConfig(filename="cacti.log",level=logging.DEBUG) #level是日志级别 16 #这样就写到文件里了,再写就是追加不会覆盖,设置日志级别,只会把比设置的日志级别高的日志写到日志中,比如设置的是error,那么debug就不会出现在日志中 17 18 #那么做运维的就会说了“你TM的时间呢,没有时间我排个毛线的错”,说得好,所以呢: 19 20 logging.basicConfig(filename="cacti.log",level=logging.ERROR,format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p') 21 logging.error("Hello error") 22 >>>11/23/2017 02:45:46 PM Hello error
#以下是其他格式
日志格式
%(name)s |
Logger的名字 |
%(levelno)s |
数字形式的日志级别 |
%(levelname)s |
文本形式的日志级别 |
%(pathname)s |
调用日志输出函数的模块的完整路径名,可能没有 |
%(filename)s |
调用日志输出函数的模块的文件名 |
%(module)s |
调用日志输出函数的模块名 |
%(funcName)s |
调用日志输出函数的函数名 |
%(lineno)d |
调用日志输出函数的语句所在的代码行 |
%(created)f |
当前时间,用UNIX标准的表示时间的浮 点数表示 |
%(relativeCreated)d |
输出日志信息时的,自Logger创建以 来的毫秒数 |
%(asctime)s |
字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒 |
%(thread)d |
线程ID。可能没有 |
%(threadName)s |
线程名。可能没有 |
%(process)d |
进程ID。可能没有 |
%(message)s |
用户输出的消息 |
通过上面的东西呢,我们知道了日志可以输出到屏幕上也可以输出到文件里,那么我想同时输出到文件中和屏幕上呢.那么我们就需要学习其他姿势
logging模块记录日志主要分为四个类,请看下面
1.logger:用户可直接调用
每个程序在输出信息之前都要获得一个Logger。Logger通常对应了程序的模块名,比如聊天工具的图形界面模块可以这样获得它的Logger:
LOG=logging.getLogger(”chat.gui”)
而核心模块可以这样:
LOG=logging.getLogger(”chat.kernel”)
Logger.setLevel(lel):指定最低的日志级别,低于lel的级别将被忽略。debug是最低的内置级别,critical为最高
Logger.addFilter(filt)、Logger.removeFilter(filt):添加或删除指定的filter
Logger.addHandler(hdlr)、Logger.removeHandler(hdlr):增加或删除指定的handler
Logger.debug()、Logger.info()、Logger.warning()、Logger.error()、Logger.critical():可以设置的日志级别
2.handler:将(logger)创建的日志信息发送到指定的输出位置
handler对象负责发送相关的信息到指定目的地。Python的日志系统有多种Handler可以使用。有些Handler可以把信息输出到控制台,有些Logger可以把信息输出到文件,还有些 Handler可以把信息发送到网络上。如果觉得不够用,还可以编写自己的Handler。可以通过addHandler()方法添加多个多handler
Handler.setLevel(lel):指定被处理的信息级别,低于lel级别的信息将被忽略
Handler.setFormatter():给这个handler选择一个格式
Handler.addFilter(filt)、Handler.removeFilter(filt):新增或删除一个filter对象
每个Logger可以附加多个Handler。接下来我们就来介绍一些常用的Handler:
1) logging.StreamHandler
使用这个Handler可以向类似与sys.stdout或者sys.stderr的任何文件对象(file object)输出信息。它的构造函数是:
StreamHandler([strm])
其中strm参数是一个文件对象。默认是sys.stderr
2) logging.FileHandler
和StreamHandler类似,用于向一个文件输出日志信息。不过FileHandler会帮你打开这个文件。它的构造函数是:
FileHandler(filename[,mode])
filename是文件名,必须指定一个文件名。
mode是文件的打开方式。参见Python内置函数open()的用法。默认是’a',即添加到文件末尾。
3) logging.handlers.RotatingFileHandler
这个Handler类似于上面的FileHandler,但是它可以管理文件大小。当文件达到一定大小之后,它会自动将当前日志文件改名,然后创建 一个新的同名日志文件继续输出。比如日志文件是chat.log。当chat.log达到指定的大小之后,RotatingFileHandler自动把 文件改名为chat.log.1。不过,如果chat.log.1已经存在,会先把chat.log.1重命名为chat.log.2。。。最后重新创建 chat.log,继续输出日志信息。它的构造函数是:
RotatingFileHandler( filename[, mode[, maxBytes[, backupCount]]])
其中filename和mode两个参数和FileHandler一样。
maxBytes用于指定日志文件的最大文件大小。如果maxBytes为0,意味着日志文件可以无限大,这时上面描述的重命名过程就不会发生。
backupCount用于指定保留的备份文件的个数。比如,如果指定为2,当上面描述的重命名过程发生时,原有的chat.log.2并不会被更名,而是被删除。
4) logging.handlers.TimedRotatingFileHandler
这个Handler和RotatingFileHandler类似,不过,它没有通过判断文件大小来决定何时重新创建日志文件,而是间隔一定时间就 自动创建新的日志文件。重命名的过程与RotatingFileHandler类似,不过新的文件不是附加数字,而是当前时间。它的构造函数是:
TimedRotatingFileHandler( filename [,when [,interval [,backupCount]]])
其中filename参数和backupCount参数和RotatingFileHandler具有相同的意义。
interval是时间间隔。
when参数是一个字符串。表示时间间隔的单位,不区分大小写。它有以下取值:
S 秒
M 分
H 小时
D 天
W 每星期(interval==0时代表星期一)
midnight 每天凌晨
3.filter:可以用来过滤日志信息(一般用不到)
4.formatter:就跟上面提到的fomat一样,用于决定输出格式
#这俩不难,就那么几个用法,就不多说了,接下来我们填一下坑,刚才说到如何同时屏幕输出和文件输出
1 import logging 2 logger = logging.getLogger("log") #定义一个logger 3 logger.setLevel(logging.DEBUG) #定义logger日志最小级别 4 5 delog = logging.StreamHandler() #定义屏幕输出handler 6 delog.setLevel(logging.DEBUG) 7 8 errlog = logging.FileHandler("error.txt") #定义文件输出 9 errlog.setLevel(logging.ERROR) 10 11 delog_format = logging.Formatter("%(asctime)s %(lineno)d %(message)s") #定义屏幕输出格式 12 errlog_format = logging.Formatter("%(asctime)s %(message)s") #定义文件输出格式 13 delog.setFormatter(delog_format) #给delog增加格式 14 errlog.setFormatter(errlog_format) #给errlog增加格式 15 16 logger.addHandler(delog) 17 logger.addHandler(errlog) #给logger增加handler 18 19 logger.warning("Hello World") 20 21 >>>2017-11-10 22:12:10,480 19 Hello World 22 23 #到这里大家会发现,只有屏幕输出了,为什么文件没有输出呢,因为我们定义的屏幕输出handler的最小级别为error,输出的级别是warning,所以没有输出,利用这个呢我们就可以将一些重大错误输出到文件且报警,而一些类似debug,warning的呢就可以输出到屏幕
#运维平常要看很多日志,可是如果把每一条记录进去,就会造成一个超大的文件,所以会定期删除日志,那么python有一个日志文件截断的功能
1 import logging 2 from logging import handlers 3 logger = logging.getLogger("log") #定义一个logger 4 logger.setLevel(logging.DEBUG) #定义logger日志最小级别 5 6 errlog = handlers.RotatingFileHandler(filename="error.log",maxBytes=10,backupCount=3) 7 #定义文件输出,到达10个字节则自动将error.log改名为error.log.1,有了.1则变成.2然后在重新创建一个error.log,但是呢最多备份3个 8 errlog.setLevel(logging.ERROR) 9 10 errlog_format = logging.Formatter("%(asctime)s %(message)s") #定义文件输出格式 11 errlog.setFormatter(errlog_format) #给errlog增加格式 12 13 logger.addHandler(errlog) #给logger增加handler 14 15 logger.error("Hello World") 16 logger.error("Hello World") 17 logger.error("Hello World" 18 #这样你就会发现你多了几个文件*.log.1,*.log.2,*.log.3 19 20 #上面呢是按文件大小来截断,还有一个按时间来截断 21 22 import logging,time 23 from logging import handlers 24 logger = logging.getLogger("log") #定义一个logger 25 logger.setLevel(logging.DEBUG) #定义logger日志最小级别 26 27 errlog = handlers.TimedRotatingFileHandler(filename="error.log",when="S",interval=1,backupCount=3,encoding="utf-8") #定义文件输出,文件名,时间单位为秒,1秒备份一次,最多三次 28 errlog.setLevel(logging.ERROR) 29 30 errlog_format = logging.Formatter("%(asctime)s %(message)s") #定义文件输出格式 31 errlog.setFormatter(errlog_format) #给errlog增加格式 32 33 logger.addHandler(errlog) #给logger增加handler 34 35 logger.error("你好世界1") 36 time.sleep(2) 37 logger.error("你好世界2") 38 time.sleep(2) 39 logger.error("你好世界3") 40 time.sleep(2) 41 logger.error("你好世界4") 42 time.sleep(2) 43 logger.error("你好世界5") 44 #多了三个带时间格式的error.log.2017...
5.re正则表达式模块
熟悉linux的知道很多用来匹配的语句,比如grep,awk,sed等,那么python中也有一个re也可以做到
举一些常用的匹配字符:
1 "." # 默认匹配除\n之外的任意一个字符,若指定flag DOTALL,则匹配任意字符,包括换行 2 re.search(".{10}","name:Daniel age:18") 3 >>><_sre.SRE_Match object; span=(0, 10), match='name:Danie'> 4 5 "^" #匹配字符开头,若指定flags MULTILINE,这种也可以匹配上 6 (r"^a","\nabc\neee",flags=re.MULTILINE) 7 re.search("^name","name:Daniel age:18") 8 >>><_sre.SRE_Match object; span=(0, 4), match='name'> 9 10 "$" # 匹配字符结尾,或re.search("foo$","bfoo\nsdfsf",flags=re.MULTILINE).group()也可以 11 re.search("18$","name:Daniel age:18") 12 >>><_sre.SRE_Match object; span=(16, 18), match='18'> 13 14 "*" #匹配*号前的字符0次或多次,re.findall("ab*","cabb3abcbbac") 结果为['abb', 'ab', 'a'] 15 >>>re.findall("ab*","abbb2abb2ab2") 16 ['abbb', 'abb', 'ab'] 17 18 "+" #匹配前一个字符1次或多次 19 >>>re.findall("a+","abbb2abb2ab2") 20 ['a', 'a', 'a'] 21 22 "?" #匹配前一个字符1次或0次 23 >>>re.findall("a?","abbb2abb2ab2") 24 ['a', '', '', '', '', 'a', '', '', '', 'a', '', '', ''] 25 26 "{n}" #匹配前一个字符n次 27 re.findall("ab{3}","abbb2abb2ab2") 28 ['abbb'] 29 30 "{n,m}" #匹配前一个字符n到m次 31 re.findall("ab{1,3}","abbb2abb2ab2") 32 ['abbb', 'abb', 'ab'] 33 34 "|" #匹配|左或|右的字符 35 re.findall("AB|ab","ABabbb2abb2ab2") 36 ['AB', 'ab', 'ab', 'ab'] 37 38 '(```)' #分组匹配 39 re.findall("(\d+)(a|b)","ABabbb2abb2ab2") 40 >>>[('2', 'a'), ('2', 'a')] 41 re.search("(?P<name>[A-z]{1,6})(?P<age>[0-9]{2})","Daniel18").groupdict() 42 >>>{'name': 'Daniel', 'age': '18'} 43 44 45 "\A" #从头开始匹配 46 re.findall("\AAB","ABabbb2abb2ab2") 47 >>>['AB'] 48 49 '\Z' #匹配字符结尾,同$ 50 re.findall("2\Z","ABabbb2abb2ab2") 51 >>>['2'] 52 53 "\d" #匹配数字 54 re.findall("\d","ABabbb2abb2ab2") 55 ['2', '2', '2'] 56 57 "\D" #匹配非数字 58 re.findall("\D","ABabbb2abb2ab2") 59 >>>['A', 'B', 'a', 'b', 'b', 'b', 'a', 'b', 'b', 'a', 'b'] 60 61 "\w" #匹配[A-Za-z0-9] 62 re.findall("\w","ABabbb2abb2ab2") 63 >>>['A', 'B', 'a', 'b', 'b', 'b', '2', 'a', 'b', 'b', '2', 'a', 'b', '2'] 64 65 "\W" #匹配非[A-Za-z0-9] 66 re.findall("\W","ABabbb2abb2ab2$!##@") 67 >>>['$', '!', '#', '#', '@'] 68 69 's' 匹配空白字符、\t、\n、\r , 70 re.search("\s+","1\n2\t3\n4\n5\6").group() 71 >>>'\n'
大家从上面可以看到re.findall,re.search,这些是什么意思呢?还有没有别的呢?
1 re.match 从头开始匹配 2 re.match("a+","sa") #这里就匹配不到 3 re.match("a+","asa") 4 >>><_sre.SRE_Match object; span=(0, 1), match='a'> #只匹配开头有的 5 6 re.search 匹配包含 7 re.search("a+","sa") 8 >>><_sre.SRE_Match object; span=(1, 2), match='a'> #匹配所有的,一般我们都用这个 9 10 re.findall 把所有匹配到的字符放到以列表中的元素返回 11 re.findall("a+","asaa") 12 >>>['a', 'aa'] #返回所匹配到的元素 13 14 re.split 以匹配到的字符当做列表分隔符 15 re.split("\d+","a1b2c3d4e5") 16 ['a', 'b', 'c', 'd', 'e', ''] #把数字都给分割 17 18 re.sub 匹配字符并替换 19 re.sub("\d","~","a1b2c3d4e5f6") 20 'a~b~c~d~e~f~' #将数字替换成~
反斜杠的困扰
与大多数编程语言相同,正则表达式里使用"\"作为转义字符,这就可能造成反斜杠困扰。假如你需要匹配文本中的字符"\",那么使用编程语言表示的正则表达式里将需要4个反斜杠"\\\\":前两个和后两个分别用于在编程语言里转义成反斜杠,转换成两个反斜杠后再在正则表达式里转义成一个反斜杠。Python里的原生字符串很好地解决了这个问题,这个例子中的正则表达式可以使用r"\\"表示。同样,匹配一个数字的"\\d"可以写成r"\d"。有了原生字符串,你再也不用担心是不是漏写了反斜杠,写出来的表达式也更直观。
re.split("\\\\",r"usr\local\python") >>>['usr', 'local', 'python'] re.split("\\\\",r"usr\\local\python") >>>['usr', '', 'local', 'python'] re.split("\\/",r"usr\\local\python") >>>['usr\\\\local\\python'] re.split("\/",r"/usr\\local\python") >>>['', 'usr\\\\local\\python'] #四个\匹配一个\
到这里你会问了还有吗,还有一点点你需要稍微知道的,当然忘掉也无所谓
1 re.I(re.IGNORECASE): 忽略大小写 2 re.findall("a+","abAbAbab",flags=re.I) 3 >>>['a', 'A', 'A', 'a'] 4 5 M(MULTILINE): 多行模式,改变'^'和'$'的行为 6 re.findall("^a","abc\nabc\nabc",flags=re.M) 7 >>>['a', 'a', 'a'] #匹配多行 8 9 re.findall("c$","abc\nabc\nabc",flags=re.M) 10 >>>['c', 'c', 'c'] 11 12 S(DOTALL):改变"."的行为 13 re.findall(".","abc\nabc\nabc") 14 >>> ['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c'] #"."原本是匹配不到'\n' 15 16 re.findall(".","abc\nabc\nabc",flags=re.S) 17 >>> ['a', 'b', 'c', '\n', 'a', 'b', 'c', '\n', 'a', 'b', 'c'] #现在就匹配到