全连接层转换为卷积层(步幅为2),得到了3*3*2的输出,为什么

如果看了此文你还不懂傅里叶变换,那就过来掐死我吧【完整版】 - 文章 - 伯乐在线
& 如果看了此文你还不懂傅里叶变换,那就过来掐死我吧【完整版】
作 者:韩 昊
知 乎:Heinrich
微 博:@花生油工人
知乎专栏:与时间无关的故事
谨以此文献给大连海事大学的吴楠老师,柳晓鸣老师,王新年老师以及张晶泊老师。
转载的同学请保留上面这句话,谢谢。如果还能保留文章来源就更感激不尽了。
——更新于,想直接看更新的同学可以直接跳到第四章————
12345678910
作 者:韩 昊知 乎:Heinrich微 博:@花生油工人知乎专栏:与时间无关的故事&谨以此文献给大连海事大学的吴楠老师,柳晓鸣老师,王新年老师以及张晶泊老师。&转载的同学请保留上面这句话,谢谢。如果还能保留文章来源就更感激不尽了。&——更新于,想直接看更新的同学可以直接跳到第四章————
我保证这篇文章和你以前看过的所有文章都不同,这是 2012 年还在果壳的时候写的,但是当时没有来得及写完就出国了……于是拖了两年,嗯,我是拖延症患者……
这篇文章的核心思想就是:
要让读者在不看任何数学公式的情况下理解傅里叶分析。
傅里叶分析不仅仅是一个数学工具,更是一种可以彻底颠覆一个人以前世界观的思维模式。但不幸的是,傅里叶分析的公式看起来太复杂了,所以很多大一新生上来就懵圈并从此对它深恶痛绝。老实说,这么有意思的东西居然成了大学里的杀手课程,不得不归咎于编教材的人实在是太严肃了。(您把教材写得好玩一点会死吗?会死吗?)所以我一直想写一个有意思的文章来解释傅里叶分析,有可能的话高中生都能看懂的那种。所以,不管读到这里的您从事何种工作,我保证您都能看懂,并且一定将体会到通过傅里叶分析看到世界另一个样子时的快感。至于对于已经有一定基础的朋友,也希望不要看到会的地方就急忙往后翻,仔细读一定会有新的发现。
————以上是定场诗————
下面进入正题:
抱歉,还是要啰嗦一句:其实学习本来就不是易事,我写这篇文章的初衷也是希望大家学习起来更加轻松,充满乐趣。但是千万!千万不要把这篇文章收藏起来,或是存下地址,心里想着:以后有时间再看。这样的例子太多了,也许几年后你都没有再打开这个页面。无论如何,耐下心,读下去。这篇文章要比读课本要轻松、开心得多……
一、什么是频域
从我们出生,我们看到的世界都以时间贯穿,股票的走势、人的身高、汽车的轨迹都会随着时间发生改变。这种以时间作为参照来观察动态世界的方法我们称其为时域分析。而我们也想当然的认为,世间万物都在随着时间不停的改变,并且永远不会静止下来。但如果我告诉你,用另一种方法来观察世界的话,你会发现世界是永恒不变的,你会不会觉得我疯了?我没有疯,这个静止的世界就叫做频域。
先举一个公式上并非很恰当,但意义上再贴切不过的例子:
在你的理解中,一段音乐是什么呢?
这是我们对音乐最普遍的理解,一个随着时间变化的震动。但我相信对于乐器小能手们来说,音乐更直观的理解是这样的:
好的!下课,同学们再见。
是的,其实这一段写到这里已经可以结束了。上图是音乐在时域的样子,而下图则是音乐在频域的样子。所以频域这一概念对大家都从不陌生,只是从来没意识到而已。
现在我们可以回过头来重新看看一开始那句痴人说梦般的话:世界是永恒的。
将以上两图简化:
在时域,我们观察到钢琴的琴弦一会上一会下的摆动,就如同一支股票的走势;而在频域,只有那一个永恒的音符。
你眼中看似落叶纷飞变化无常的世界,实际只是躺在上帝怀中一份早已谱好的乐章。
抱歉,这不是一句鸡汤文,而是黑板上确凿的公式:傅里叶同学告诉我们,任何周期函数,都可以看作是不同振幅,不同相位正弦波的叠加。在第一个例子里我们可以理解为,利用对不同琴键不同力度,不同时间点的敲击,可以组合出任何一首乐曲。
而贯穿时域与频域的方法之一,就是传中说的傅里叶分析。傅里叶分析可分为傅里叶级数(Fourier Serie)和傅里叶变换(Fourier Transformation),我们从简单的开始谈起。
二、傅里叶级数(Fourier Series)的频谱
还是举个栗子并且有图有真相才好理解。
如果我说我能用前面说的正弦曲线波叠加出一个带 90 度角的矩形波来,你会相信吗?你不会,就像当年的我一样。但是看看下图:
第一幅图是一个郁闷的正弦波 cos(x)
第二幅图是 2 个卖萌的正弦波的叠加 cos (x) +a.cos (3x)
第三幅图是 4 个发春的正弦波的叠加
第四幅图是 10 个便秘的正弦波的叠加
随着正弦波数量逐渐的增长,他们最终会叠加成一个标准的矩形,大家从中体会到了什么道理?
(只要努力,弯的都能掰直!)
随着叠加的递增,所有正弦波中上升的部分逐渐让原本缓慢增加的曲线不断变陡,而所有正弦波中下降的部分又抵消了上升到最高处时继续上升的部分使其变为水平线。一个矩形就这么叠加而成了。但是要多少个正弦波叠加起来才能形成一个标准 90 度角的矩形波呢?不幸的告诉大家,答案是无穷多个。(上帝:我能让你们猜着我?)
不仅仅是矩形,你能想到的任何波形都是可以如此方法用正弦波叠加起来的。这是没有接触过傅里叶分析的人在直觉上的第一个难点,但是一旦接受了这样的设定,游戏就开始有意思起来了。
还是上图的正弦波累加成矩形波,我们换一个角度来看看:
在这几幅图中,最前面黑色的线就是所有正弦波叠加而成的总和,也就是越来越接近矩形波的那个图形。而后面依不同颜色排列而成的正弦波就是组合为矩形波的各个分量。这些正弦波按照频率从低到高从前向后排列开来,而每一个波的振幅都是不同的。一定有细心的读者发现了,每两个正弦波之间都还有一条直线,那并不是分割线,而是振幅为 0 的正弦波!也就是说,为了组成特殊的曲线,有些正弦波成分是不需要的。
这里,不同频率的正弦波我们成为频率分量。
好了,关键的地方来了!!
如果我们把第一个频率最低的频率分量看作“1”,我们就有了构建频域的最基本单元。
对于我们最常见的有理数轴,数字“1”就是有理数轴的基本单元。
(好吧,数学称法为——基。在那个年代,这个字还没有其他奇怪的解释,后面还有正交基这样的词汇我会说吗?)
时域的基本单元就是“1 秒”,如果我们将一个角频率为的正弦波 cos(t)看作基础,那么频域的基本单元就是。
有了“1”,还要有“0”才能构成世界,那么频域的“0”是什么呢?cos(0t)就是一个周期无限长的正弦波,也就是一条直线!所以在频域,0 频率也被称为直流分量,在傅里叶级数的叠加中,它仅仅影响全部波形相对于数轴整体向上或是向下而不改变波的形状。
接下来,让我们回到初中,回忆一下已经死去的八戒,啊不,已经死去的老师是怎么定义正弦波的吧。
正弦波就是一个圆周运动在一条直线上的投影。所以频域的基本单元也可以理解为一个始终在旋转的圆
想看动图的同学请戳这里:
以及这里:
点出去的朋友不要被 wiki 拐跑了,wiki 写的哪有这里的文章这么没节操是不是。
介绍完了频域的基本组成单元,我们就可以看一看一个矩形波,在频域里的另一个模样了:
这是什么奇怪的东西?
这就是矩形波在频域的样子,是不是完全认不出来了?教科书一般就给到这里然后留给了读者无穷的遐想,以及无穷的吐槽,其实教科书只要补一张图就足够了:频域图像,也就是俗称的频谱,就是——
再清楚一点:
可以发现,在频谱中,偶数项的振幅都是0,也就对应了图中的彩色直线。振幅为 0 的正弦波。
动图请戳:
老实说,在我学傅里叶变换时,维基的这个图还没有出现,那时我就想到了这种表达方法,而且,后面还会加入维基没有表示出来的另一个谱——相位谱。
但是在讲相位谱之前,我们先回顾一下刚刚的这个例子究竟意味着什么。记得前面说过的那句“世界是静止的”吗?估计好多人对这句话都已经吐槽半天了。想象一下,世界上每一个看似混乱的表象,实际都是一条时间轴上不规则的曲线,但实际这些曲线都是由这些无穷无尽的正弦波组成。我们看似不规律的事情反而是规律的正弦波在时域上的投影,而正弦波又是一个旋转的圆在直线上的投影。那么你的脑海中会产生一个什么画面呢?
我们眼中的世界就像皮影戏的大幕布,幕布的后面有无数的齿轮,大齿轮带动小齿轮,小齿轮再带动更小的。在最外面的小齿轮上有一个小人——那就是我们自己。我们只看到这个小人毫无规律的在幕布前表演,却无法预测他下一步会去哪。而幕布后面的齿轮却永远一直那样不停的旋转,永不停歇。这样说来有些宿命论的感觉。说实话,这种对人生的描绘是我一个朋友在我们都是高中生的时候感叹的,当时想想似懂非懂,直到有一天我学到了傅里叶级数……
三、傅里叶级数(Fourier Series)的相位谱
上一章的关键词是:从侧面看。这一章的关键词是:从下面看。
在这一章最开始,我想先回答很多人的一个问题:傅里叶分析究竟是干什么用的?这段相对比较枯燥,已经知道了的同学可以直接跳到下一个分割线。
先说一个最直接的用途。无论听广播还是看电视,我们一定对一个词不陌生——频道。频道频道,就是频率的通道,不同的频道就是将不同的频率作为一个通道来进行信息传输。下面大家尝试一件事:
先在纸上画一个sin(x),不一定标准,意思差不多就行。不是很难吧。
好,接下去画一个sin(3x)+sin(5x)的图形。
别说标准不标准了,曲线什么时候上升什么时候下降你都不一定画的对吧?
好,画不出来不要紧,我把sin(3x)+sin(5x)的曲线给你,但是前提是你不知道这个曲线的方程式,现在需要你把sin(5x)给我从图里拿出去,看看剩下的是什么。这基本是不可能做到的。
但是在频域呢?则简单的很,无非就是几条竖线而已。
所以很多在时域看似不可能做到的数学操作,在频域相反很容易。这就是需要傅里叶变换的地方。尤其是从某条曲线中去除一些特定的频率成分,这在工程上称为滤波,是信号处理最重要的概念之一,只有在频域才能轻松的做到。
再说一个更重要,但是稍微复杂一点的用途——求解微分方程。(这段有点难度,看不懂的可以直接跳过这段)微分方程的重要性不用我过多介绍了。各行各业都用的到。但是求解微分方程却是一件相当麻烦的事情。因为除了要计算加减乘除,还要计算微分积分。而傅里叶变换则可以让微分和积分在频域中变为乘法和除法,大学数学瞬间变小学算术有没有。
傅里叶分析当然还有其他更重要的用途,我们随着讲随着提。
————————————————————————————————————
下面我们继续说相位谱:
通过时域到频域的变换,我们得到了一个从侧面看的频谱,但是这个频谱并没有包含时域中全部的信息。因为频谱只代表每一个对应的正弦波的振幅是多少,而没有提到相位。基础的正弦波A.sin(wt+θ)中,振幅,频率,相位缺一不可,不同相位决定了波的位置,所以对于频域分析,仅仅有频谱(振幅谱)是不够的,我们还需要一个相位谱。那么这个相位谱在哪呢?我们看下图,这次为了避免图片太混论,我们用7个波叠加的图。
鉴于正弦波是周期的,我们需要设定一个用来标记正弦波位置的东西。在图中就是那些小红点。小红点是距离频率轴最近的波峰,而这个波峰所处的位置离频率轴有多远呢?为了看的更清楚,我们将红色的点投影到下平面,投影点我们用粉色点来表示。当然,这些粉色的点只标注了波峰距离频率轴的距离,并不是相位。
这里需要纠正一个概念:时间差并不是相位差。如果将全部周期看作2Pi或者360度的话,相位差则是时间差在一个周期中所占的比例。我们将时间差除周期再乘2Pi,就得到了相位差。
在完整的立体图中,我们将投影得到的时间差依次除以所在频率的周期,就得到了最下面的相位谱。所以,频谱是从侧面看,相位谱是从下面看。下次偷看女生裙底被发现的话,可以告诉她:“对不起,我只是想看看你的相位谱。”
注意到,相位谱中的相位除了0,就是Pi。因为cos(t+Pi)=-cos(t),所以实际上相位为Pi的波只是上下翻转了而已。对于周期方波的傅里叶级数,这样的相位谱已经是很简单的了。另外值得注意的是,由于cos(t+2Pi)=cos(t),所以相位差是周期的,pi和3pi,5pi,7pi都是相同的相位。人为定义相位谱的值域为(-pi,pi],所以图中的相位差均为Pi。
最后来一张大集合:
四、傅里叶变换(Fourier Tranformation)
相信通过前面三章,大家对频域以及傅里叶级数都有了一个全新的认识。但是文章在一开始关于钢琴琴谱的例子我曾说过,这个栗子是一个公式错误,但是概念典型的例子。所谓的公式错误在哪里呢?
傅里叶级数的本质是将一个周期的信号分解成无限多分开的(离散的)正弦波,但是宇宙似乎并不是周期的。曾经在学数字信号处理的时候写过一首打油诗:
往昔连续非周期,
回忆周期不连续,
任你ZT、DFT,
还原不回去。
(请无视我渣一样的文学水平……)
在这个世界上,有的事情一期一会,永不再来,并且时间始终不曾停息地将那些刻骨铭心的往昔连续的标记在时间点上。但是这些事情往往又成为了我们格外宝贵的回忆,在我们大脑里隔一段时间就会周期性的蹦出来一下,可惜这些回忆都是零散的片段,往往只有最幸福的回忆,而平淡的回忆则逐渐被我们忘却。因为,往昔是一个连续的非周期信号,而回忆是一个周期离散信号。
是否有一种数学工具将连续非周期信号变换为周期离散信号呢?抱歉,真没有。
比如傅里叶级数,在时域是一个周期且连续的函数,而在频域是一个非周期离散的函数。这句话比较绕嘴,实在看着费事可以干脆回忆第一章的图片。
而在我们接下去要讲的傅里叶变换,则是将一个时域非周期的连续信号,转换为一个在频域非周期的连续信号。
算了,还是上一张图方便大家理解吧:
或者我们也可以换一个角度理解:傅里叶变换实际上是对一个周期无限大的函数进行傅里叶变换。
所以说,钢琴谱其实并非一个连续的频谱,而是很多在时间上离散的频率,但是这样的一个贴切的比喻真的是很难找出第二个来了。
因此在傅里叶变换在频域上就从离散谱变成了连续谱。那么连续谱是什么样子呢?
你见过大海么?
为了方便大家对比,我们这次从另一个角度来看频谱,还是傅里叶级数中用到最多的那幅图,我们从频率较高的方向看。
以上是离散谱,那么连续谱是什么样子呢?
尽情的发挥你的想象,想象这些离散的正弦波离得越来越近,逐渐变得连续……
直到变得像波涛起伏的大海:
很抱歉,为了能让这些波浪更清晰的看到,我没有选用正确的计算参数,而是选择了一些让图片更美观的参数,不然这图看起来就像屎一样了。
不过通过这样两幅图去比较,大家应该可以理解如何从离散谱变成了连续谱的了吧?原来离散谱的叠加,变成了连续谱的累积。所以在计算上也从求和符号变成了积分符号。
不过,这个故事还没有讲完,接下去,我保证让你看到一幅比上图更美丽壮观的图片,但是这里需要介绍到一个数学工具才能然故事继续,这个工具就是——
五、宇宙耍帅第一公式:欧拉公式
虚数i这个概念大家在高中就接触过,但那时我们只知道它是-1 的平方根,可是它真正的意义是什么呢?
这里有一条数轴,在数轴上有一个红色的线段,它的长度是1。当它乘以 3 的时候,它的长度发生了变化,变成了蓝色的线段,而当它乘以-1 的时候,就变成了绿色的线段,或者说线段在数轴上围绕原点旋转了 180 度。
我们知道乘-1 其实就是乘了两次 i 使线段旋转了 180 度,那么乘一次 i 呢——答案很简单——旋转了 90 度。
同时,我们获得了一个垂直的虚数轴。实数轴与虚数轴共同构成了一个复数的平面,也称复平面。这样我们就了解到,乘虚数i的一个功能——旋转。
现在,就有请宇宙第一耍帅公式欧拉公式隆重登场——
这个公式在数学领域的意义要远大于傅里叶分析,但是乘它为宇宙第一耍帅公式是因为它的特殊形式——当x等于 Pi 的时候。
经常有理工科的学生为了跟妹子表现自己的学术功底,用这个公式来给妹子解释数学之美:”石榴姐你看,这个公式里既有自然底数e,自然数 1 和0,虚数i还有圆周率 pi,它是这么简洁,这么美丽啊!“但是姑娘们心里往往只有一句话:”臭屌丝……“
这个公式关键的作用,是将正弦波统一成了简单的指数形式。我们来看看图像上的涵义:
欧拉公式所描绘的,是一个随着时间变化,在复平面上做圆周运动的点,随着时间的改变,在时间轴上就成了一条螺旋线。如果只看它的实数部分,也就是螺旋线在左侧的投影,就是一个最基础的余弦函数。而右侧的投影则是一个正弦函数。
关于复数更深的理解,大家可以参考:
这里不需要讲的太复杂,足够让大家理解后面的内容就可以了。
六、指数形式的傅里叶变换
有了欧拉公式的帮助,我们便知道:正弦波的叠加,也可以理解为螺旋线的叠加在实数空间的投影。而螺旋线的叠加如果用一个形象的栗子来理解是什么呢?
高中时我们就学过,自然光是由不同颜色的光叠加而成的,而最著名的实验就是牛顿师傅的三棱镜实验:
所以其实我们在很早就接触到了光的频谱,只是并没有了解频谱更重要的意义。
但不同的是,傅里叶变换出来的频谱不仅仅是可见光这样频率范围有限的叠加,而是频率从 0 到无穷所有频率的组合。
这里,我们可以用两种方法来理解正弦波:
第一种前面已经讲过了,就是螺旋线在实轴的投影。
另一种需要借助欧拉公式的另一种形式去理解:
将以上两式相加再除2,得到:
这个式子可以怎么理解呢?
我们刚才讲过,e^(it)可以理解为一条逆时针旋转的螺旋线,那么e^(-it)则可以理解为一条顺时针旋转的螺旋线。而 cos (t)则是这两条旋转方向不同的螺旋线叠加的一半,因为这两条螺旋线的虚数部分相互抵消掉了!
举个例子的话,就是极化方向不同的两束光波,磁场抵消,电场加倍。
这里,逆时针旋转的我们称为正频率,而顺时针旋转的我们称为负频率(注意不是复频率)。
好了,刚才我们已经看到了大海——连续的傅里叶变换频谱,现在想一想,连续的螺旋线会是什么样子:
想象一下再往下翻:
是不是很漂亮?
你猜猜,这个图形在时域是什么样子?
哈哈,是不是觉得被狠狠扇了一个耳光。数学就是这么一个把简单的问题搞得很复杂的东西。
顺便说一句,那个像大海螺一样的图,为了方便观看,我仅仅展示了其中正频率的部分,负频率的部分没有显示出来。
如果你认真去看,海螺图上的每一条螺旋线都是可以清楚的看到的,每一条螺旋线都有着不同的振幅(旋转半径),频率(旋转周期)以及相位。而将所有螺旋线连成平面,就是这幅海螺图了。
好了,讲到这里,相信大家对傅里叶变换以及傅里叶级数都有了一个形象的理解了,我们最后用一张图来总结一下:
好了,傅里叶的故事终于讲完了,下面来讲讲我的故事:
这篇文章第一次被卸下来的地方你们绝对猜不到在哪,是在一张高数考试的卷子上。当时为了刷分,我重修了高数(上),但是后来时间紧压根没复习,所以我就抱着裸考的心态去了考场。但是到了考场我突然意识到,无论如何我都不会比上次考的更好了,所以干脆写一些自己对于数学的想法吧。于是用了一个小时左右的时间在试卷上洋洋洒洒写了本文的第一草稿。
你们猜我的了多少分?
没错,就是这个数字。而这 6 分的成绩是因为最后我实在无聊,把选择题全部填上了C,应该是中了两道,得到了这宝贵的 6 分。说真的,我很希望那张卷子还在,但是应该不太可能了。
那么你们猜猜我第一次信号与系统考了多少分呢?
没错,刚刚够参加补考的。但是我心一横没去考,决定重修。因为那个学期在忙其他事情,学习真的就抛在脑后了。但是我知道这是一门很重要的课,无论如何我要吃透它。说真的,信号与系统这门课几乎是大部分工科课程的基础,尤其是通信专业。
在重修的过程中,我仔细分析了每一个公式,试图给这个公式以一个直观的理解。虽然我知道对于研究数学的人来说,这样的学习方法完全没有前途可言,因为随着概念愈加抽象,维度越来越高,这种图像或者模型理解法将完全丧失作用。但是对于一个工科生来说,足够了。
后来来了德国,这边学校要求我重修信号与系统时,我彻底无语了。但是没办法,德国人有时对中国人就是有种藐视,觉得你的教育不靠谱。所以没办法,再来一遍吧。
这次,我考了满分,而及格率只有一半。
老实说,数学工具对于工科生和对于理科生来说,意义是完全不同的。工科生只要理解了,会用,会查,就足够了。但是很多高校却将这些重要的数学课程教给数学系的老师去教。这样就出现一个问题,数学老师讲得天花乱坠,又是推理又是证明,但是学生心里就只有一句话:学这货到底干嘛用的?
缺少了目标的教育是彻底的失败。
在开始学习一门数学工具的时候,学生完全不知道这个工具的作用,现实涵义。而教材上有只有晦涩难懂,定语就二十几个字的概念以及看了就眼晕的公式。能学出兴趣来就怪了!
好在我很幸运,遇到了大连海事大学的吴楠老师。他的课全程来看是两条线索,一条从上而下,一条从下而上。先将本门课程的意义,然后指出这门课程中会遇到哪样的问题,让学生知道自己学习的某种知识在现实中扮演的角色。然后再从基础讲起,梳理知识树,直到延伸到另一条线索中提出的问题,完美的衔接在一起!
这样的教学模式,我想才是大学里应该出现的。
最后,写给所有给我点赞并留言的同学。真的谢谢大家的支持,也很抱歉不能一一回复。因为知乎专栏的留言要逐次加载,为了看到最后一条要点很多次加载。当然我都坚持看完了,只是没办法一一回复。
本文只是介绍了一种对傅里叶分析新颖的理解方法,对于求学,还是要踏踏实实弄清楚公式和概念,学习,真的没有捷径。但至少通过本文,我希望可以让这条漫长的路变得有意思一些。
最后,祝大家都能在学习中找到乐趣…
可能感兴趣的话题
写的确实不错,原作者真的是用心了。连画的图都是自己画的,有自己的独特分析。好!
关于伯乐在线博客
在这个信息爆炸的时代,人们已然被大量、快速并且简短的信息所包围。然而,我们相信:过多“快餐”式的阅读只会令人“虚胖”,缺乏实质的内涵。伯乐在线内容团队正试图以我们微薄的力量,把优秀的原创文章和译文分享给读者,为“快餐”添加一些“营养”元素。
新浪微博:
推荐微信号
(加好友请注明来意)
– 好的话题、有启发的回复、值得信赖的圈子
– 分享和发现有价值的内容与观点
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 翻译传播优秀的外文文章
– 国内外的精选文章
– UI,网页,交互和用户体验
– 专注iOS技术分享
– 专注Android技术分享
– JavaScript, HTML5, CSS
– 专注Java技术分享
– 专注Python技术分享
& 2018 伯乐在线探索 Pexpect,第 2 部分:Pexpect 的实例分析
概述通过本系列第一部分《探索 Pexpect,第 1 部分:剖析 Pexpect 》(请参阅参考资料)的介绍,相信大家已经对 Pexpect 的用法已经有了比较全面的了解,知道 Pexpect 是个纯 Python 语言实现的模块,使用其可以轻松方便的实现与 ssh、ftp、passwd 和 telnet 等程序的自动交互,但是读者的理解还可能只是停留在理论基础上,本文将从实际例子入手具体介绍 Pexpect 的使用场景和使用心得体验,实例中的代码读者都可以直接拿来使用,相信会对大家产生比较大的帮助。
以下是本文所要介绍的所有 Pexpect 例子标题:例 1:ftp 的使用(注:spawn、expect 和 sendline 的使用)例 2:记录 log(注:logfile、logfile_send和logfile_read的使用)例 3:ssh 的使用例 4:pxssh 的使用例 5:telnet 的使用(注:interact 的使用)pexpect 使用 tips调试 pexpect 程序的 tipspexpect 不会解释 shell 中的元字符EOF 异常和 TIMEOUT 异常使用 run() 来替代某些的 spawn 的使用expect_exact() 的使用expect() 中正则表达式的使用 tipsisalive() 的使用 tipsdelaybeforesend 的使用 tips例 1:ftp 的使用本例实现了如下功能:ftp 登录到 develperWorks.ibm.com 主机上,并用二进制传输模式下载一个名叫 rmall的文件。清单 1. ftp 的例子代码#!/usr/bin/env python
import pexpect
# 即将 ftp 所要登录的远程主机的域名
ipAddress = 'develperWorks.ibm.com'
# 登录用户名
loginName = 'root'
# 用户名密码
loginPassword = 'passw0rd'
# 拼凑 ftp 命令
cmd = 'ftp ' + ipAddress
# 利用 ftp 命令作为 spawn 类构造函数的参数,生成一个 spawn 类的对象
child = pexpect.spawn(cmd)
# 期望具有提示输入用户名的字符出现
index = child.expect(["(?i)name", "(?i)Unknown host", pexpect.EOF, pexpect.TIMEOUT])
# 匹配到了 "(?i)name",表明接下来要输入用户名
if ( index == 0 ):
# 发送登录用户名 + 换行符给子程序.
child.sendline(loginName)
# 期望 "(?i)password" 具有提示输入密码的字符出现.
index = child.expect(["(?i)password", pexpect.EOF, pexpect.TIMEOUT])
# 匹配到了 pexpect.EOF 或 pexpect.TIMEOUT,表示超时或者 EOF,程序打印提示信息并退出.
if (index != 0):
print "ftp login failed"
child.close(force=True)
# 匹配到了密码提示符,发送密码 + 换行符给子程序.
child.sendline(loginPassword)
# 期望登录成功后,提示符 "ftp&" 字符出现.
index = child.expect( ['ftp&', 'Login incorrect', 'Service not available',
pexpect.EOF, pexpect.TIMEOUT])
# 匹配到了 'ftp&',登录成功.
if (index == 0):
print 'Congratulations! ftp login correct!'
# 发送 'bin'+ 换行符给子程序,表示接下来使用二进制模式来传输文件.
child.sendline("bin")
print 'getting a file...'
# 向子程序发送下载文件 rmall 的命令.
child.sendline("get rmall")
# 期望下载成功后,出现 'Transfer complete.*ftp&',其实下载成功后,
# 会出现以下类似于以下的提示信息:
200 PORT command successful.
150 Opening data connection for rmall (548 bytes).
226 Transfer complete.
548 bytes received in 0.00019 seconds (2.8e+03 Kbytes/s)
# 所以直接用正则表达式 '.*' 将 'Transfer complete' 和提示符 'ftp&' 之间的字符全省去.
index = child.expect( ['Transfer complete.*ftp&', pexpect.EOF, pexpect.TIMEOUT] )
# 匹配到了 pexpect.EOF 或 pexpect.TIMEOUT,表示超时或者 EOF,程序打印提示信息并退出.
if (index != 0):
print "failed to get the file"
child.close(force=True)
# 匹配到了 'Transfer complete.*ftp&',表明下载文件成功,打印成功信息,并输入 'bye',结束 ftp session.
print 'successfully received the file'
child.sendline("bye")
# 用户名或密码不对,会先出现 'Login incorrect',然后仍会出现 'ftp&',但是 pexpect 是最小匹配,不是贪婪匹配,
# 所以如果用户名或密码不对,会匹配到 'Login incorrect',而不是 'ftp&',然后程序打印提示信息并退出.
elif (index == 1):
print "You entered an invalid login name or password. Program quits!"
child.close(force=True)
# 匹配到了 'Service not available',一般表明 421 Service not available, remote server has
# closed connection,程序打印提示信息并退出.
# 匹配到了 pexpect.EOF 或 pexpect.TIMEOUT,表示超时或者 EOF,程序打印提示信息并退出.
print "ftp login failed! index = " + index
child.close(force=True)
# 匹配到了 "(?i)Unknown host",表示 server 地址不对,程序打印提示信息并退出
elif index == 1 :
print "ftp login failed, due to unknown host"
child.close(force=True)
# 匹配到了 pexpect.EOF 或 pexpect.TIMEOUT,表示超时或者 EOF,程序打印提示信息并退出
print "ftp login failed, due to TIMEOUT or EOF"
child.close(force=True)注:运行后,输出结果为:Congratulations! ftp login correct!
getting a file...
successfully received the file
本例 expect 函数中的 pattern 使用了 List,并包含了 pexpect.EOF和pexpect.TIMEOUT,这样出现了超时或者 EOF,不会抛出 expection 。(关于 expect() 函数的具体使用,请参阅参考资料)
如果程序运行中间出现了错误,如用户名密码错误,超时或者 EOF,远程 server 连接不上,都会使用 c hild.close(force=True) 关掉 ftp 子程序。调用 close 可以用来关闭与子程序的 connection 连接,如果你不仅想关闭与子程序的连接,还想确保子程序是真的被 terminate 终止了,设置参数 force=True,其最终会调用 c hild.kill(signal.SIGKILL) 来杀掉子程序。例 2:记录 log本例实现了如下功能:运行一个命令,并将该命令的运行输出结果记录到 log 文件中
./command.py [-a] [-c command] {logfilename}
-c 后接的是要运行的命令的名字,默认是“ls -l”; logfilename 是记录命令运行结果的 log 文件名,默认是“command.log”;指定 -a 表示命令的输出结果会附加在 logfilename 后,如果 logfilename 之前已经存在的话。清单 2. 记录 log 的例子代码#!/usr/bin/env python
This run a user specified command and log its result.
./command.py [-a] [-c command] {logfilename}
logfilename : This is the name of the log file. Default is command.log.
-a : Append to log file. Default is to overwrite log file.
-c : spawn command. Default is the command 'ls -l'.
This will execute the command 'pwd' and append to the log named my_session.log:
./command.py -a -c 'pwd' my_session.log
import os, sys, getopt
import traceback
import pexpect
# 如果程序中间出错,打印提示信息后退出
def exit_with_usage():
print globals()['__doc__']
os._exit(1)
def main():
######################################################################
# Parse the options, arguments, get ready, etc.
######################################################################
optlist, args = getopt.getopt(sys.argv[1:], 'h?ac:', ['help','h','?'])
# 如果指定的参数不是’ -a ’ , ‘ -h ’ , ‘ -c ’ , ‘ -? ’ , ‘ --help ’ ,
#‘ --h ’或’ --? ’时,会抛出 exception,
# 这里 catch 住,然后打印出 exception 的信息,并输出 usage 提示信息.
except Exception, e:
print str(e)
exit_with_usage()
options = dict(optlist)
# 最多只能指定一个 logfile,否则出错.
if len(args) & 1:
exit_with_usage()
# 如果指定的是 '-h','--h','-?','--?' 或 '--help',只输出 usage 提示信息.
if [elem for elem in options if elem in ['-h','--h','-?','--?','--help']]:
print "Help:"
exit_with_usage()
# 获取 logfile 的名字.
if len(args) == 1:
script_filename = args[0]
# 如果用户没指定,默认 logfile 的名字是 command.log
script_filename = "command.log"
# 如果用户指定了参数 -a,如果之前该 logfile 存在,那么接下来的内容会附加在原先内容之后,
# 如果之前没有该
logfile,新建一个文件,并且接下来将内容写入到该文件中.
if '-a' in options:
fout = open (script_filename, "ab")
# 如果用户没指定参数 -a,默认按照用户指定 logfile 文件名新建一个文件,然后将接下来将内容写入到该文件中.
fout = open (script_filename, "wb")
# 如果用户指定了 -c 参数,那么运行用户指定的命令.
if '-c' in options:
command = options['-c']
# 如果用户没有指定 -c 参数,那么默认运行命令'ls – l'
command = "ls -l"
# logfile 文件的 title
fout.write ('==========Log Tile: IBM developerWorks China==========\n')
# 为接下来的运行命令生成一个 pexpect 的 spawn 类子程序的对象.
p = pexpect.spawn(command)
# 将之前 open 的 file 对象指定为 spawn 类子程序对象的 log 文件.
p.logfile = fout
# 命令运行完后,expect EOF 出现,这时会将 spawn 类子程序对象的输出写入到 log 文件.
p.expect(pexpect.EOF)
#open 完文件,使用完毕后,需关闭该文件.
fout.close()
if __name__ == "__main__":
except SystemExit, e:
except Exception, e:
print "ERROR"
print str(e)
traceback.print_exc()
os._exit(1)注:运行:./command.py -a -c who cmd.log运行结束后,cmd.log 的内容为:
IBM developerWorks China
22:40 (:0.0)
18:55 (9.77.180.94)logfile:只能通过 spawn 类的构造函数指定。在 spawn 类的构造函数通过参数指定 logfile 时,表示开启或关闭 logging 。所有的子程序的 input 和 output 都会被 copy 到指定的 logfile 中。设置 logfile 为 None 表示停止 logging,默认就是停止 logging 。设置 logfile 为 sys.stdout,会将所有东西 echo 到标准输出。logfile_read和logfile_send:logfile_read:只用来记录 python 主程序接收到 child 子程序的输出,有的时候你不想看到写给 child 的所有东西,只希望看到 child 发回来的东西。 logfile_send:只用来记录 python 主程序发送给 child 子程序的输入
logfile、logfile_read 和 logfile_send 何时被写入呢?
logfile、logfile_read 和 logfile_send 会在每次写 write 和 send 操作后被 flush 。调用 send 后,才会往 logfile 和 logfile_send 中写入,sendline/sendcontrol/sendoff/write/writeline 最终都会调用 send,所以 sendline 后 logfile 中一定有内容了,只要此时 logfile 没有被 close 。调用 read_nonblocking 后,才会往 logfile 和 logfile_read 中写入,expect_loop 会调用 read_nonblocking,而 expect_exact 和 expect_list 都会调用 expect_loop,expect 会调用 expect_list,所以 expect 后 logfile 中一定有内容了,只要此时 logfile 没有被 close 。如果调用的函数最终都没有调用 send 或 read_nonblocking,那么 logfile 虽然被分配指定了一个 file,但其最终结果是:内容为空。见下例:清单 3. log 内容为空的例子代码import pexpect
p = pexpect.spawn( ‘ ls -l ’ )
fout = open ('log.txt', "w")
p.logfile = fout
fout.close()运行该脚本后,你会发现其实 log.txt 是空的,没有记录 ls -l 命令的内容,原因是没有调用 send 或 read_nonblocking,真正的内容没有被 flush 到 log 中。如果在 fout.close() 之前加上 p.expect(pexpect.EOF),log.txt 才会有 ls -l 命令的内容。例 3:ssh 的使用本例实现了如下功能:ssh 登录到某个用户指定的主机上,运行某个用户指定的命令,并输出该命令的结果。清单 4. ssh 的例子代码#!/usr/bin/env python
This runs a command on a remote host using SSH. At the prompts enter hostname,
user, password and the command.
import pexpect
import getpass, os
#user: ssh 主机的用户名
#host:ssh 主机的域名
#password:ssh 主机的密码
#command:即将在远端 ssh 主机上运行的命令
def ssh_command (user, host, password, command):
This runs a command on the remote host. This could also be done with the
pxssh class, but this demonstrates what that class does at a simpler level.
This returns a pexpect.spawn object. This handles the case when you try to
connect to a new host and ssh asks you if you want to accept the public key
fingerprint and continue connecting.
ssh_newkey = 'Are you sure you want to continue connecting'
# 为 ssh 命令生成一个 spawn 类的子程序对象.
child = pexpect.spawn('ssh -l %s %s %s'%(user, host, command))
i = child.expect([pexpect.TIMEOUT, ssh_newkey, 'password: '])
# 如果登录超时,打印出错信息,并退出.
if i == 0: # Timeout
print 'ERROR!'
print 'SSH could not login. Here is what SSH said:'
print child.before, child.after
return None
# 如果 ssh 没有 public key,接受它.
if i == 1: # SSH does not have the public key. Just accept it.
child.sendline ('yes')
child.expect ('password: ')
i = child.expect([pexpect.TIMEOUT, 'password: '])
if i == 0: # Timeout
print 'ERROR!'
print 'SSH could not login. Here is what SSH said:'
print child.before, child.after
return None
# 输入密码.
child.sendline(password)
return child
def main ():
# 获得用户指定 ssh 主机域名.
host = raw_input('Hostname: ')
# 获得用户指定 ssh 主机用户名.
user = raw_input('User: ')
# 获得用户指定 ssh 主机密码.
password = getpass.getpass()
# 获得用户指定 ssh 主机上即将运行的命令.
command = raw_input('Enter the command: ')
child = ssh_command (user, host, password, command)
# 匹配 pexpect.EOF
child.expect(pexpect.EOF)
# 输出命令结果.
print child.before
if __name__ == '__main__':
except Exception, e:
print str(e)
traceback.print_exc()
os._exit(1)注:运行后,输出结果为:Hostname: develperWorks.ibm.com
User: root
Enter the command: ls -l
drwxr-xr-x
512 Jun 14 2006
drwxrwxr-x
512 Sep 23 2008
-rwx------
1855 Jun 14 2006
-rwx------
806 Sep 16 2008
-rwx------
60 Jun 14 2006
drwx------
512 Jan 18 2007
drwxr-x---
512 Apr 15 00:04 223002
-rwxr-xr-x
120 Jan 16 2007
-rwx------
10419 Jun 14 2006
drwxr-x---
512 Oct 25 2007
-rw-------
3203 Apr 04 2008
-rw-r--r--
0 Jun 14 2006
-rw-r--r--
0 Jun 14 2006
使用了 getpass.getpass() 来获得用户输入的密码,与 raw_input 不同的是,getpass.getpass() 不会将用户输入的密码字符串 echo 回显到 stdout 上。(更多 python 相关技术,请参阅参考资料)例 4:pxssh 的使用本例实现了如下功能:使用 pexpect 自带的 pxssh 模块实现 ssh 登录到某个用户指定的主机上,运行命令’ uptime ’和’ ls -l ’,并输出该命令的结果。清单 5. 使用 pxssh 的例子代码#!/usr/bin/env python
import pxssh
import getpass
# 调用构造函数,创建一个 pxssh 类的对象.
s = pxssh.pxssh()
# 获得用户指定 ssh 主机域名.
hostname = raw_input('hostname: ')
# 获得用户指定 ssh 主机用户名.
username = raw_input('username: ')
# 获得用户指定 ssh 主机密码.
password = getpass.getpass('password: ')
# 利用 pxssh 类的 login 方法进行 ssh 登录,原始 prompt 为'$' , '#'或'&'
s.login (hostname, username, password, original_prompt='[$#&]')
# 发送命令 'uptime'
s.sendline ('uptime')
# 匹配 prompt
s.prompt()
# 将 prompt 前所有内容打印出,即命令 'uptime' 的执行结果.
print s.before
# 发送命令 ' ls -l '
s.sendline ('ls -l')
# 匹配 prompt
s.prompt()
# 将 prompt 前所有内容打印出,即命令 ' ls -l ' 的执行结果.
print s.before
# 退出 ssh session
s.logout()
except pxssh.ExceptionPxssh, e:
print "pxssh failed on login."
print str(e)运行后,输出结果为:hostname: develperWorks.ibm.com
username: root
up 292 days,
load average: 0.01, 0.02, 0.01
drwxr-xr-x
512 Jun 14 2006
drwxrwxr-x
512 Sep 23 2008
-rwx------
1855 Jun 14 2006
-rwx------
806 Sep 16 2008
-rwx------
60 Jun 14 2006
drwx------
512 Jan 18 2007
drwxr-x---
512 Apr 15 00:04 223002
-rwxr-xr-x
120 Jan 16 2007
-rwx------
10419 Jun 14 2006
drwxr-x---
512 Oct 25 2007
-rw-------
3203 Apr 04 2008
-rw-r--r--
0 Jun 14 2006
-rw-r--r--
0 Jun 14 2006
pxssh 是 pexpect 中 spawn 类的子类,增加了 login, logout 和 prompt 几个方法,使用其可以轻松实现 ssh 连接,而不用自己调用相对复杂的 pexpect 的方法来实现。 pxssh 做了很多 tricky 的东西来处理 ssh login 过程中所可能遇到的各种情况。比如:如果这个 session 是第一次 login,pxssh 会自动接受远程整数 remote certificate ;如果你已经设置了公钥认证,pxssh 将不会再等待 password 的提示符。(更多 ssh 相关知识,请参阅参考资料)
pxssh 使用 shell 的提示符来同步远程主机的输出,为了使程序更加稳定,pxssh 还可以设置 prompt 为更加唯一的字符串,而不仅仅是“ $ ”和“ # ”。login 方法 login (self,server,username,password='',terminal_type='ansi',
iginal_prompt=r"[#$]",login_timeout=10,port=None,auto_prompt_reset=True):
使用原始 original_prompt 来找到 login 后的提示符(这里默认 original_prompt 是“$”或“#”,但是有时候可能也是别的 prompt,这时就需要在 login 时手动指定这个特殊的 prompt,见上例,有可能是“ & ”),如果找到了,立马使用更容易匹配的字符串来重置该原始提示符(这是由 pxssh 自己自动做的,通过命令 "PS1='[PEXPECT]\$ '" 重置原始提示符,然后每次 expect 匹配 \[PEXPECT\][\$\#])。原始提示符是很容易被混淆和胡弄的,为了阻止错误匹配,最好根据特定的系统,指定更加精确的原始提示符,例如 "Message Of The Day" 。
有些情况是不允许重置原始提示符的,这时就要设置 auto_prompt_reset 为 False 。而且此时需要手动设置 PROMPT 域为某个正则表达式来 match 接下来要出现的新提示符,因为 prompt() 函数默认是 expect 被重置过的 PROMPT 的。
prompt方法prompt (self, timeout=20):
匹配新提示符(不是 original_prompt)。注:这只是匹配提示符,不能匹配别的 string,如果要匹配特殊 string,需直接使用父类 spawn 的 expect 方法。 prompt 方法相当于是 expect 方法的一个快捷方法。如果auto_prompt_reset 为 False,这时需要手动设置 PROMPT 域为某个正则表达式来 match 接下来要出现的 prompt,因为 prompt() 函数默认是 expect 被重置过的 PROMPT 的。logout方法logout (self):发送'exit'给远程 ssh 主机,如果有 stopped jobs,会发送'exit'两次。例 5:telnet 的使用本例实现了如下功能:telnet 登录到某远程主机上,输入命令“ls -l”后,将子程序的执行权交还给用户,用户可以与生成的 telnet 子程序进行交互。清单 6. telnet 的例子代码#!/usr/bin/env python
import pexpect
# 即将 telnet 所要登录的远程主机的域名
ipAddress = 'develperWorks.ibm.com'
# 登录用户名
loginName = 'root'
# 用户名密码
loginPassword = 'passw0rd'
# 提示符,可能是’ $ ’ , ‘ # ’或’ & ’
loginprompt = '[$#&]'
# 拼凑 telnet 命令
cmd = 'telnet ' + ipAddress
# 为 telnet 生成 spawn 类子程序
child = pexpect.spawn(cmd)
# 期待'login'字符串出现,从而接下来可以输入用户名
index = child.expect(["login", "(?i)Unknown host", pexpect.EOF, pexpect.TIMEOUT])
if ( index == 0 ):
# 匹配'login'字符串成功,输入用户名.
child.sendline(loginName)
# 期待 "[pP]assword" 出现.
index = child.expect(["[pP]assword", pexpect.EOF, pexpect.TIMEOUT])
# 匹配 "[pP]assword" 字符串成功,输入密码.
child.sendline(loginPassword)
# 期待提示符出现.
child.expect(loginprompt)
if (index == 0):
# 匹配提示符成功,输入执行命令 'ls -l'
child.sendline('ls -l')
# 立马匹配 'ls -l',目的是为了清除刚刚被 echo 回显的命令.
child.expect('ls -l')
# 期待提示符出现.
child.expect(loginprompt)
# 将 'ls -l' 的命令结果输出.
print child.before
print "Script recording started. Type ^] (ASCII 29) to escape from the script
# 将 telnet 子程序的执行权交给用户.
child.interact()
print 'Left interactve mode.'
# 匹配到了 pexpect.EOF 或 pexpect.TIMEOUT,表示超时或者 EOF,程序打印提示信息并退出.
print "telnet login failed, due to TIMEOUT or EOF"
child.close(force=True)
# 匹配到了 pexpect.EOF 或 pexpect.TIMEOUT,表示超时或者 EOF,程序打印提示信息并退出.
print "telnet login failed, due to TIMEOUT or EOF"
child.close(force=True)运行后,输出结果为:total 60
drwxr-xr-x
512 Jun 14 2006
drwxrwxr-x
512 Sep 23 2008
-rwx------
1855 Jun 14 2006
-rwx------
806 Sep 16 2008
-rwx------
60 Jun 14 2006
drwx------
512 Jan 18 2007
drwxr-x---
512 Apr 15 00:04 223002
-rwxr-xr-x
120 Jan 16 2007
-rwx------
10419 Jun 14 2006
drwxr-x---
512 Oct 25 2007
-rw-------
3203 Apr 04 2008
-rw-r--r--
0 Jun 14 2006
-rw-r--r--
0 Jun 14 2006
Script recording started. Type ^] (ASCII 29) to escape from the script shell.
此时程序会 block 住,等待用户的输入,比如用户输入’ pwd ’,输出/home/root
接下来用户敲入 ctrl+] 结束子程序interact方法interact(self, escape_character = chr(29), input_filter = None, output_filter = None)通常一个 python 主程序通过 pexpect.spawn 启动一个子程序,一旦该子程序启动后,python 主程序就可以通过 child.expect 和 child.send/child.sendline 来和子程序通话,python 主程序运行结束后,子程序也就死了。比如 python 主程序通过 pexpect.spawn 启动了一个 telnet 子程序,在进行完一系列的 telnet 上的命令操作后,python 主程序运行结束了,那么该 telnet session(telnet 子程序)也会自动退出。但是如果调用 child.interact,那么该子程序(python 主程序通过 pexpect.spawn 衍生成的)就可以在运行到 child.interact 时,将子程序的控制权交给了终端用户(the human at the keyboard),用户可以通过键盘的输入来和子程序进行命令交互,管理子程序的生杀大权,用户的键盘输入 stdin 会被传给子程序,而且子程序的 stdout 和 stderr 输出也会被打印出来到终端。
默认 ctrl + ] 退出 interact() 模式,把子程序的执行权重新交给 python 主程序。参数 escape_character 指定了交互模式的退出字符,例如 child.interact(chr(26)) 接下来就会变成 ctrl + z 退出 interact() 模式。pexpect 使用 tips调试 pexpect 程序的 tips获得 pexpect.spawn 对象的字符串 value值,将会给 debug 提供很多有用信息。清单 7. 打印 pexpect.spawn 对象的字符串 value 值的例子代码try:
i = child.expect ([pattern1, pattern2, pattern3, etc])
print "Exception was thrown"
print "debug information:"
print str(child)将子程序的 input 和 output 打 log 到文件中或者直接打 log 到屏幕上也非常有用清单 8. 记录 log 的例子代码# 打开 loggging 功能并将结果输出到屏幕上
child = pexpect.spawn (foo)
child.logfile = sys.stdoutpexpect 不会解释 shell 中的元字符pexpect 不会解释 shell 的元字符,如重定向 redirect,管道 pipe,和通配符 wildcards( “ & ” , “ | ”和“ * ”等 )
如果想用的话,必须得重新启动一个新 shell(在 spawn 的参数 command 中是不会解释他们的,视其为 command string 的一个普通字符)清单 9. 重新启动一个 shell 来规避 pexpect 对元字符的不解释child = pexpect.spawn('/bin/bash -c "ls -l | grep LOG & log_list.txt"')
child.expect(pexpect.EOF)如果想在 spawn 出来的新子程序中使用重定向 redirect,管道 pipe,和通配符 wildcards( “ & ” , “ | ”和“ * ”等 ),好像没有好的方法,只能不使用这些字符,先利用 expect 匹配命令提示符,从而在 before 中可以拿到之前命令的结果,然后在分析 before 的内容达到使用重定向 redirect, 管道 pipe, 和通配符 wildcards 的目的。TIMEOUT 异常如果子程序没有在指定的时间内生成任何 output,那么 expect() 和 read() 都会产生 TIMEOUT 异常。超时默认是 30s,可以在 expect() 和 spawn 构造函数初始化时指定为其它时间,如:child.expect('password:', timeout=120) # 等待 120s如果你想让 expect() 和 read() 忽略超时限制,即无限期阻塞住直到有 output 产生,设置 timeout 参数为 None。清单 10. 忽略 timeout 超时限制的例子代码child = pexpect.spawn( "telnet develperWorks.ibm.com" )
child.expect( "login", timeout=None )EOF 异常可能会有两种 EOF 异常被抛出,但是他们除了显示的信息不同,其实本质上是相同的。为了实用的目的,不需要区分它们,他们只是给了些关于你的 python 程序到底运行在哪个平台上的额外信息,这两个显示信息是:End Of File (EOF) in read(). Exception style platform.
End Of File (EOF) in read(). Empty string style platform.
有些 UNIX 平台,当你读取一个处于 EOF 状态的文件描述符时,会抛出异常,其他 UNIX 平台,却只会静静地返回一个空字符串来表明该文件已经达到了状态。使用 run() 来替代某些的 spawn 的使用pexpect 模块除了提供 spawn 类以外,还提供了 run() 函数,使用其可以取代一些 spawn 的使用,而且更加简单明了。清单 11. 使用 run() 来替代 spawn 的使用的例子代码# 使用 spawn 的例子
from pexpect import *
child = spawn('scp foo myname@host.example.com:.')
child.expect ('(?i)password')
child.sendline (mypassword)
# 以上功能,相当于以下 run 函数:
from pexpect import *
run ('scp foo myname@host.example.com:.', events={'(?i)password': mypassword})run (command, timeout=-1, withexitstatus=False, events=None, extra_args=None, logfile=None, cwd=None, env=None):
command:执行一个命令,然后返回结果,run() 可以替换 os.system()(更多 os.system() 知识,请参阅参考资料),因为 os.system() 得不到命令输出的结果
返回的 output 是个字符串,STDERR 也会包括在 output 中,如果全路径没有被指定,那么 path 会被 search
timeout:单位 s 秒,每隔 timeout 生成一个 pexpect.TIMEOUT 异常
每行之间被 CR/LF (\\r\\n) 相隔,即使在 Unix 平台上也是 CR/LF,因为 Pexpect 子程序是伪 tty 设备
withexitstatus:设置为 True,则返回一个 tuple,里面包括 (command_output, exitstatus),如果其为 False,那么只是仅仅返回 command_output
events:是个 dictionary,里面存放 {pattern:response} 。无论什么时候 pattern 在命令的结果中出现了,会出现以下动作:
发送相应的 response String 。如果需要回车符“ Enter ”的话,“ \\n ”也必须得出现在 response 字符串中。
response 同样也可以是个回调函数,不过该回调函数有特殊要求,即它的参数必须是个 dictionary,该 dictionary 的内容是:包含所有在 run() 中定义的局部变量,从而提供了方法可以访问 run() 函数中 spawn 生成的子程序和 run() 中定义的其他局部变量,其中 event_count, child, 和 extra_args 最有用。回调函数可能返回 True,从而阻止当前 run() 继续执行,否则 run() 会继续执行直到下一个 event 。回调函数也可能返回一个字符串,然后被发送给子程序。 'extra_args' 不是直接被 run() 使用,它只是提供了一个方法可以通过 run() 来将数据传入到回调函数中(其实是通过 run() 定义的局部变量 dictionary 来传)清单 12. 其它一些使用 run() 的例子代码# 在 local 机器上启动 apache 的 daemon
from pexpect import *
run ("/usr/local/apache/bin/apachectl start")
# 使用 SVN check in 文件
from pexpect import *
run ("svn ci -m 'automatic commit' my_file.py")
# 运行一个命令并捕获 exit status
from pexpect import *
command_output, exitstatus) = run ('ls -l /bin', withexitstatus=1)
# 运行 SSH,并在远程机器上执行’ ls -l ’,如果 pattern '(?i)password' 被匹配住,密码 'secret'
# 将会被发送出去
run ("ssh username@machine.example.com 'ls -l'", events={'(?i)password':'secret\\n'})
# 启动 mencoder 来 rip 一个 video,同样每 5s 钟显示进度记号
from pexpect import *
def print_ticks(d):
print d['event_count']
run ("mencoder dvd://1 -o video.avi -oac copy -ovc copy", events={TIMEOUT:print_ticks})expect_exact() 的使用
expect_exact(self, pattern_list, timeout = -1, searchwindowsize = -1);
expect_exact() 与 expect() 类似,但是 pattern_list 只能是字符串或者是一个字符串的 list,不能是正则表达式,其匹配速度会快于 expect(),原因有两个:一是字符串的 search 比正则表达式的匹配要快,另一个则是可以限制只从输入缓冲的结尾来寻找匹配的字符串。还有当你觉得每次要 escape 正则表达式中的特殊字符为普通字符时很烦,那么你也可以使用 expect_exact() 来取代 expect()。清单 13. expect_exact() 的例子代码import pexpect
child = pexpect.spawn('ls -l')
child.expect_exact('pexpect.txt')
print child.afterexpect() 中正则表达式的使用 tipsexpect() 中的正则表达式不是贪婪匹配 greedy match,而是最小匹配,即只匹配缓冲区中最早出现的第一个字符串。
因为是依次读取一个字符的 stream 流来判断是否和正则表达式所表达的模式匹配,所以如果参数 pattern 是个 list,而且不止一次匹配,那么缓冲区中最早出现的第一个匹配的字符串才算数。清单 14. expect() 的最小匹配例子代码# 如果输入是 'foobar'
index = p.expect (['bar', 'foo', 'foobar'])
#index 返回是 1 ('foo') 而不是 2 ('foobar'),即使 'foobar' 是个更好的匹配。原因是输入是个流 stream,
# 当收到 foo 时,第 1 个 pattern ('foo') 就被匹配了,不会等到’ bar ’的出现了,所以返回 1
“$”不起任何作用,匹配一行的结束 (end of line),必须得匹配 CR/LF正则表达式中,'$'可以匹配一行的结束(具体'$'正则表达式的使用,请参阅参考资料),但是 pexpect 从子程序中一次只读取一个字符,而且每个字符都好像是一行的结束一样,pexpect 不能在子程序的输出流去预测。匹配一行结束的方法必须是匹配 "\r\n" (CR/LF) 。即使是 Unix 系统,也是匹配 "\r\n" (CR/LF),因为 pexpect 使用一个 Pseudo-TTY 设备与子程序通话,所以当子程序输出 "\n" 你仍然会在 python 主程序中看到 "\r\n" 。原因是 TTY 设备更像 windows 操作系统,每一行结束都有个 "\r\n" (CR/LF) 的组合,当你从 TTY 设备去解释一个 Unix 的命令时,你会发现真正的输出是 "\r\n" (CR/LF),一个 Unix 命令只会写入一个 linefeed (\n),但是 TTY 设备驱动会将其转换成 "\r\n" (CR/LF) 。清单 15. 匹配一行结束 1child.expect ('\r\n')如果你只是想跳过一个新行,直接 expect('\n') 就可以了,但是如果你想在一行的结束匹配一个具体的 pattern 时,就必须精确的寻找 (\r),见下例:清单 16. 匹配一行结束 2# 成功在一行结束前匹配一个单词
child.expect ('\w+\r\n')
# 以下两种情况都会失败
child.expect ('\w+\n')
child.expect ('\w+$')这个问题其实不只是 pexpect 会有,如果你在一个 stream 流上实施正则表达式匹配时,都会遇到此问题。正则表达式需要预测,stream 流中很难预测,因为生成这个流的进程可能还没有结束,所以你很难知道是否该进程是暂时性的暂停还是已经彻底结束。当 '.' 和 '*' 出现在最后时
child.expect ('.+');
因为是最小匹配,所以只会返回一个字符,而不是一个整个一行(虽然 pexpect 设置了 re.DOTALL,会匹配一个新行。
child.expect ('.*');
每次匹配都会成功,但是总是没有字符返回,因为 '*' 表明前面的字符可以出现 0 次 , 在 pexpect 中,一般来说,任何 '*' 都会尽量少的匹配。isalive() 的使用 tipsisalive(self)测试子程序是否还在运行。这个方法是非阻塞的,如果子程序被终止了,那么该方法会去读取子程序的 exitstatus 或 signalstatus 这两个域。返回 True 表明子程序好像是在运行,返回 False 表示不再运行。当平台是 Solaris 时,可能需要几秒钟才能得到正确的状态。
当子程序退出后立马执行 isalive() 有时可能会返回 1 (True),这是一个 race condition,原因是子程序已经关闭了其文件描述符,但是在 isalive() 执行前还没有完全的退出。增加一个小小的延时会对 isalive() 的结果有效性有帮助。清单 17. isalive() 的例子代码# 以下程序有时会返回 1 (True)
child = pexpect.spawn('ls')
child.expect(pexpect.EOF)
print child.isalive()
# 但是如果在 isalive() 之前加个小延时,就会一直返回 0 (False)
child = pexpect.spawn('ls')
child.expect(pexpect.EOF)
time.sleep(0.1)
# 之前要 import time,单位是秒 s
print child.isalive()delaybeforesend 的使用 tipsspawn 类的域 delaybeforesend 可以帮助克服一些古怪的行为。比如,经典的是,当一个用户使用 expect() 期待 "Password:" 提示符时,如果匹配,立马 sendline() 发送密码给子程序,但是这个用户会看到他们的密码被 echo back 回显回来了。这是因为,通常许多应用程序都会在打印出 "Password:" 提示符后,立马关掉 stdin 的 echo,但是如果你发送密码过快,在程序关掉 stdin 的 echo 之前就发送密码出去了,那么该密码就会被 echo 出来。清单 18. delaybeforesend 的例子代码child.expect ('[pP]assword:')
child.sendline (my_password)
# 在 expect 之后,某些应用程序,如 SSH,会做如下动作:
#1. SSH 打印 "password:" 提示符给用户
#2. SSH 关闭 echo.
#3. SSH 等待用户输入密码
# 但是现在第二条语句 sendline 可能会发生在 1 和 2 之间,即在 SSH 关掉 echo 之前输入了 password 给子程序 , 从
# 而在 stdout,该 password 被 echo 回显出来,出现了 security 的问题
# 所以此时可以通过设置 delaybeforesend 来在将数据写(发送)给子程序之前增加一点点的小延时,因为该问题经
# 常出现,所以默认就 sleep 50ms. 许多 linux 机器必须需要 0.03s 以上的 delay
self.delaybeforesend = 0.05 # 单位秒
访问 ,了解 pexpect 基础知识。访问
的主页,了解更多 Expect 的内容。访问
官方网页,了解更多 Python 相关知识。有关正则表达式方面相关知识,请访问。更多 SSH 相关知识,请访问 。更多 os.system() 的介绍,请访问 。 在
寻找为 Linux 开发人员(包括 )准备的更多参考资料,查阅我们 。 在 developerWorks 上查阅所有
添加或订阅评论,请先或。
有新评论时提醒我
static.content.url=http://www.ibm.com/developerworks/js/artrating/SITE_ID=10Zone=Linux, Open sourceArticleID=423621ArticleTitle=探索 Pexpect,第 2 部分:Pexpect 的实例分析publish-date=

我要回帖

更多关于 cnn卷积层得到了什么 的文章

 

随机推荐