Linux下的Python脚本编程

来源:互联网 发布:淘宝企业店铺是c店吗 编辑:程序博客网 时间:2024/05/23 00:40

TraceBack:http://blog.seety.org/everydaywork/2008/4/9/1012/

撰写 Linux使用的 Pythonscript

这篇文章写于两年前,主题锁定在以PythonLinuxscript。讨论了Python script.的惯用写法、字符串处理、字符编码、档案与目录处理、呼叫外部程序,以及利用内建链接库进行网络通讯。

  • 1   Linux、指令稿与Python
  • 2   Python指令稿的格式
  • 3   字符串处理
    • 3.1   转换字符编码
  • 4   档案系统与目录
    • 4.1   档案系统操作
    • 4.2   路径的处理
  • 5   外部程序呼叫
    • 5.1   管线
  • 6   因特网通讯
  • 7   结语

1   Linux、指令稿与Python

Linux来说,指令稿(script)是至为重要的部分。在主要的Linux distribution中间,从系统的启动到运作,都离不开shell指令稿撰写。在我的主机上面执行一下:

$ ls /usr/bin/* /bin/* | wc -l

2585

$ file /usr/bin/* /bin/* | grep "shellscript" | wc -l

267

看,可以找到267shell指令稿程序,超过/usr/bin/usr目录下所有(程序)档案的十分之一。这还只是shell指令稿的部分而已。

在一个像Linux这样以档案为操作导向的操作系统上面,script.的活跃是理所当然的事情。绝大部分的系统设定都以字符串的形式写在组态文件里面,而操作系统的执行期信息也存在档案系统之中(/proc);直接处理这些字符串就能管理系统,用指令稿语言来进行自动化是非常合适的。

Python这种指令稿语言因为开发快速的关系,能够很快地制作出我们想要的系统管理功能出来。除了开发快速之外,Python也具有容易维护的特性。相比之下,Perl程序虽然可以写得更短,但也更不容易看懂;shell指令稿则不是完整的开发环境Python是撰写系统管理指令稿的理想工具

2   Python指令稿的格式

Python指令稿与其它语言的指令稿的基本格式完全一样,本身都是纯文字文件,而在文件头要以#!指定直译程序的位置:

#!/usr/bin/python

print "Hello, world!"

这是我们上一期写过的hello.py程序,不要忘记chmod a+x hello.py,如此便可以在指令行下执行这个指令稿:

$ ./hello.py

Hello, world

我们习惯上会给Python程序取个扩展名.py,但Linux的指令稿并不需要缀上扩展名;把hello.py改成hello,程序一样会正常执行。.py扩展名对Python仍有特别的意义,但只在撰写Python模块的时候才有用处。

对于指定Python直译器标头,我们一般有两种作法。像以上的hello.py这种写为绝对路径的方式其实并非必要,我们可以改用相对路径的方式来指定:

#!/usr/bin/env python

于是会以/usr/bin/env程序来叫用python直译器,处理Python程序档案。这么作的好处是当系统中安装有许多个不同的Python直译器时,会采用路径在最前面的那一个。如此一来,若使用者另外安装了一版Python (例如装在自己的家目录),又把自己的Python放到路径设定(PATH环境变量)的最前面,即会采用使用者自己安装的Python

每一版Python除了有python这个执行档之外,还会附有内容完全相同的pythonX.Y这个执行档,X.Y是该版Pythonmajor versionminor version。譬如Python 2.3就会有pythonpython2.3这两个直译器,用起来是完全一样的。如果我们写的指令稿程序必须要使用某一个版本的Python,可以偷偷在指令稿标头上动手脚来进行限制;以Python 2.3为例,就把标头写成:

#!/usr/bin/env python2.3

Note

Python提供了一套正统的方法来检查所使用Python及所有相关环境的信息。在指令稿标头上动手脚虽然方便,但不是保险的正统作法;只是,若程序本身就没多长(譬如说二三十行),的确不必浪费时间去写一串检查程序。

当指令稿只使用了主流版号的标准链接库时(这是一般的状况),通常就不必指定Python的版本。

若写成hello.py里那种绝对路径的标头,就会限定使用安装在某一个位置的Python。通常我们都会指定在/usr/bin/python/usr/bin/pythonX.Y (看要指定哪一版),即系统所安装的Python;写成这样的话,使用者就不好改用自己安装的版本了。

Python直译器还会读取另一组格式为# -*- setting -*-的标头(通常接在第一行以后),其中常用的是:

# -*- coding: UTF-8 -*-

用途是指定「指令稿档案内纯文字的字符编码(UTF-8)」。如果你想要写中文批注,这就非常重要;Python自己有一套字符编码转换的机制,实作在codecs模块里面,但直到Python 2.4之前,繁体中文常用的Big5编码并未进入标准的codecs模块。如果指令稿档案使用了Python看不懂的字符编码(就是指华文世界用的Big5GB),程序虽然仍可执行,但Python直译器会送出警告。如果想用中文撰写批注,最好把指令稿档案转为UTF-8 Unicode,并如上指定编码。

上一期已经提过了,Python也是以#当作单行批注符号的(shell script.一样);所有在这个符号之后的文字都是批注。顺带一提,如果你习惯以VIM编辑Python指令稿,可以在文件尾加上VIM的设定字符串:

# vim:set nu et ts=4 sw=4 cino=>4:

设定显示行号(nu)、展开跳格键(et,对Python程序来说,跳格键Tab是最要不得的东西),指定跳格字符为4 (ts=4)、偏移字符宽为4 (sw=4)C式缩排为>4 (cino=>4);然后再打开语法标示(syntax highlighting,这个在.vimrc里设定比较合适)。使用这样的编辑环境,对撰写Python程序来说会很方便。

Python直译器会依出现顺序来执行程序代码档案里的指令。如果我们想撰写比较具组织性的指令稿,可以把平铺直述的:

print "some operations"

改成这样的程序代码结构:

def main():

   print"some operations"

 

if __name__ == '__main__':

   main()

亦即自行制作一个「进入点」main()函式。当指令稿比较长(超过一百行以上),以及将来在扩充指令稿的时候,就会比较方便。

总结来说,一个Python指令稿的常见格式应为:

#!/usr/bin/env python

# -*- coding: UTF-8 -*-

 

def main():

   print"Hello, world"

 

if __name__ == '__main__':

   main()

 

# vim:set nu et ts=4 sw=4 cino=>4:

3   字符串处理

在管理Linux系统时,(纯文字)设定档案以及其中的字符串处理是至为核心的部分;让我们来看看Python如何进行这些工作。因为我们在上一期已经用Python处理过字符串和档案了,所以在这里,我们应该对字符串处理作深入一点的介绍。

首先我们要知道的是,字符串在Python里面是一种对象。打开Python交互式环境(shell去执行python即可进入),执行以下动作:

>>> print type( "" )

<type 'str'>

>>> if type( "I am a string" ) isstr: print True

...

True

>>> if type( "Another string" ) isstr(): print True

...

type()Python的内建函式,用来取得变量的型态。我们可以从这三个指令看出来,字符串"","I am a string"都是str类别的对象。查看Python的在线文件,会发现有两组关于字符串处理的链接库;一组是string模块里的函式,另一组则是字符串对象专用的方法(String Methods)。两者虽有一些差别,但功能的重复性相当高;我们讨论的重点在字符串方法。

我们常常会需要分析档案中的字符串:把字符串拆解开来,依照给定的逻辑来判断字符串数据的意义。因此,最常用的字符串方法就是我们上一期有用到的split()split()传回的是列表(list),可以用索引值(0起始)来存取列表中的各个项目。再来示范一下:

>>> tokens = "This is a sample stringused to demo split()".split()

>>> len(tokens)

9

>>> print tokens

['This', 'is', 'a', 'sample', 'string', 'used', 'to','demo', 'split()']

>>> print tokens[0], tokens[2]

This a

>>> print tokens[-1], tokens[-2]

split() demo

>>> print tokens[2:5]

['a', 'sample', 'string']

第一个指令把我们的字符串切成了9个字符串,存在tokens这个列表里。len()是个内建函式,用来量测像列表这种可以存放其它东西的对象的长度(传回所包含的项目个数)。列表只要是整数就可以了,但最大不能到项目个数;可以给入负值,表示从列表尾端开始计算。索引值-1即为列表的最后一个项目。

有办法切开字符串进行判断了之后,我们常常还需要把分析结果给输出出来,那么就得接合字符串;以字符串的格式化操作(string format operations)就能完成这件工作。我们可以写出以下的表示式:

>>> "%d %f %s" % (1, 1.2,"string")

'1 1.200000 string'

这就是字符串格式化操作。以带有特别转换字符(conversion character)的格式化字符串,后接%运算子,再接一个tuple作为参数,就能把tuple里的数据填进格式化字符串里去。常用的%d代表有号整数、%f代表浮点数、%s代表字符串,完整的转换字符表请参考Python的在线文件。

Note

Pythontuple也是一种可以包含其它对象的数据结构,以整数索引存取其中的对象,但其行为与列表不尽相同。在语法上,tuple(1, 2, 3)来宣告,而列表用[1, 2, 3]来宣告。如果tuple中只有一个对象,则要写成(1,),不要忘记右括号前的逗号。在字符串格式化操作时,若转换字符只有一个,%操作数后的tuple也可以用单一变量来代替。

字符串对象另有一个叫作join()的方法可以用来结合字符串,用法如下:

>>> "".join([ "a","b", "c" ])

'abc'

>>> "-".join([ "a","b", "c" ])

'a-b-c'

在处理字符串时,最后要注意的是,Python的字符串不可变。也就是说,想变更字符串中的某一个字符,不能直接设:

>>> a = "write"

>>> a[2] = "o"

Traceback (most recent call last):

 File"<stdin>", line 1, in ?

TypeError: object doesn't support item assignment

那是不合法的。那该怎么办呢?可以这样作:

>>> print a[:2]+"o"+a[3:]

wrote

字符串的内容虽然不能变更,但字符串本身可以加起来(串接)a[:2]表示取出a字符串到索引2为止的部分;a[3:]表示取出a字符串从索引3开始到结尾的部分;然后在中间接入"o"。最后我们还是可以得到wrote字符串。这种操作索引的技巧,也可以用在一般的列表上。

Python同样具有常规表示式(regular expression)的操作能力,实作在re模块里面。用来执行字符串取代是非常方便的。

3.1   转换字符编码

Python有一套处理字符编码的codecs模块;我们以之即可自由地将字符转换为各种不同的编码,这是我们在处理多国语言数据时常需处理的问题。然而,字符串对象本身就提供有encode()decode()方法,我们不必汇入codecs模块就可以使用这两个方法为我们提供的codecs能力。

此处我们得要注意一个事实,那就是Python拥有两种字符串对象。其一是我们刚刚一直在处理的str字符串,而另一种呢,就是对多国语言处理非常重要的unicode字符串。一般我们用引号或双引号表示的都是普通的字符串(str),而用u"string"表示的呢,就是unicode字符串。decode()能把普通字符串译码成unicode对象,而encode()则能把unicode对象编码成各种支持的字符集。

在处理中文编码之前,我们要为Python 2.3安装相关的外加套件:cjkcodecsiconvcodecs;前者是中日韩专用的codecs对象,而后者允许Python直接使用GNU iconv工具所提供的编码,作为codecs对象。假设我们得把原本是Big5的编码重编为UTF-8,那么可以这样作:

>>> f = open( "file.big5" )

>>> s = f.read()

>>> f.close()

>>> sp = s.decode('Big5').encode('UTF-8')

你可以在计算机上找一个内容是Big5编码的档案,把locale改成UTF-8,然后在Python交互式环境下执行以上的指令(该改的地方请改一下)。最后再用print s, sp比较一下转换前后的字符串。

4   档案系统与目录

Linux系统中复制、搬移、删除档案与目录也是管理时常见的动作。Python提供的os模块能处理操作系统所支持的大部分档案系统操作,另外还有shutil模块,提供更高阶的操作。

4.1   档案系统操作

档案系统与档案内容是不一样的议题。我们在进行档案系统操作时,处理的是搬移(更名)、复制与删除,比较没有机会直接新增档案。这些动作在osshutil模块里几乎都有提供;我们应该先汇入这两个模块。

若要复制档案,我们可以这样作:

>>> shutil.copy('data.txt', 'data.new.txt')

删除档案则用os.unlink()

>>> os.unlink('data.new.txt')

搬移(更名)有两种方法:

>>> os.rename('data.txt', 'data.alter.txt')

>>> shutil.move('data.alter.txt', 'data.txt')

第一种方法,若来源档(第一个参数)与目的档不在同一个档案系统内(分割区),此动作可能会失效(不同的Unix有不同的处理方法)。第二种方法比较高阶,无论来源档与目的档是否在相同的档案系统内,都可以使用。

4.2   路径的处理

管理系统的时候多半不会只处理当前目录内的档案,所以常要对路径字符串进行处理。os.path模块提供了处理路径的函式,常用的有:

  • abspath():接受一个路径字符串,传回该路径所代表的绝对路径。
  • realpath():接受一个路径字符串,计算该路径中包含的符号连结(symbolic link),传回所代表的真正路径。
  • split(), dirname(), basename()split()接受一个路径字符串,从最后一个路径项目前切开,分成包含该项目的目录与该项目名本身,以tuple传回。dirname()split()传回值的第一个元素;basename()是第二个元素。
  • join():接受一个路径列表,把该列表中的每个元素接成一个完整路径字符串后传回。
  • splitext():接受一个路径字符串,分开其扩展名,将主档名与扩展名用一个tuple传回。
  • exists():测试传入的路径字符串是否存在,传回布尔值。
  • isfile(), isdir(), islink(), isabs():分别用来测试所传入的路径字符串是否为档案、目录、符号连结或绝对路径;传回布尔值。

实际要使用的时候,大概会像是这样子:

>>> os.path.split( "a/b/c" )

('a/b', 'c')

>>> os.path.join( "a","b", "c" )

'a/b/c'

>>> os.path.splitext("dir/file.ext" )

('dir/file', '.ext')

你可以在你的目录结构里,用真正的路径来试试看!

5   外部程序呼叫

许多在shell指令稿中要靠呼叫外部程序才能完成的作业,都能用Python的内建模块来完成,例如上面提到的字符串处理、档案处理、目录处理等等。而若遇到Python不足的地方,或是有非常特别的操作,当然也可以呼叫外部的程序。

os模块有一个system()函式可以用来呼叫外部程序:

>>> os.system( 'ls' )

weekly20051204.doc

weekly20051211.doc

0

>>> 

最后显示出来的0不是ls程序的输出,而是其传回值。

os.system()函式能进行最简单的外部程序呼叫,不能对该程序的输出入数据进一步处理;如果我们只想简单执行程序,os.system()函式将是最佳的选择

5.1   管线

当我们也需要对外部程序的输出入数据进行处理的时候,os.system()就不够用了。Python另外有popen2模块,可以让我们管理外部程序子行程的输出入管线(pipe)。在popen2模块里有popen2(), popen3()popen4()三个工具函式,分别会重导向子行程的标准输出入、标准输出入及错误输出、标准输出合并错误输出及标准输入。

简单用范例来说明最常用的popen2() (别忘了先import popen2)

>>> stdout, stdin =popen2.popen2("ls")

>>> str = stdout.read()

>>> print ostr

weekly20051204.doc

weekly20051211.doc

 

>>> 

popen2.popen2()会传回连结到ls程序输出入的两个档案对象,我们取名为stdoutstdin。呼叫了popen2.popen2()之后,外部程序马上就会执行,然后我们就能从stdout档案对象里读出该外部程序的标准输出数据了。如此一来,该程序的执行结果就不会直接显示在终端机上,我们可以在Python里面先处理过以后,再决定该怎么办。

如果我们想呼叫的程序也会进行错误输出(stderr),而我们想要处理的话,就改用popen3()popen4()函式。popen3()的错误输出会连接至一个独立的档案对象,而popen4()则会把错误输出一起放到标准输出所连结的档案对象里;你可以视需要使用。

Note

Python 2.4里有一个新的subprocess模块,可以执行所有的外部程序呼叫功能。所以在Python 2.4里不再需要ospopen2模块里的相关函式了;当然,旧模块不会消失,所以在Python 2.4里还是可以用popen2,我们的旧程序不会出问题。

6   因特网通讯

Python内建的链接库里就具备相当方便的因特网通讯功能,不必呼叫外部程序。

因特网通讯是个大范围,其中最常用到的大概数全球信息网了;我们举Zope应用程序服务器来作例子。Zope使用ZODB对象数据库来储存数据,这个系统会把存取动作纪录下来,当使用者删除其中的数据时,数据不会实际删除,要等到手动压缩(pack)数据库的时候,才会真正把数据删除。这个压缩功能的动作选项是放在web-basedZMI里面,没有指令行接口;如果我们不想手动连进ZMI来执行压缩,就得写一个能进行HTTP操作的指令稿。

我们要写的程序应该具有以下的命令列接口:

packzope.py -u<URL of Zope server> -d<day>-U<username> -P<password>

这个packzope.py程序要负责用HTTP和服务器沟通,把从命令列取得的使用者名称和密码提供给Zope服务器,并且用GET方法把要压缩的天数(舍弃指定天数前的数据)告诉Zope服务器。以下是写好的程序:

#!/usr/bin/env python

 

import sys

import urllib

 

class parameters:

 def__init__(self):

   fromoptparse import OptionParser, OptionGroup

   op =OptionParser(

       usage ="usage: %prog -u URL -d DAYS -U USERNAME -P PASSWORD",

       version= "%prog, " + "%s" % __revision__

       )

   op.add_option("-u", action="store",type="string", /

       dest="url", /

       help="URL of site to open"

       )

   op.add_option("-d", action="store",type="int", /

       dest="days", default=1, /

       help="erase days before"

       )

   op.add_option("-U", action="store",type="string", /

       dest="username", /

       help="username"

       )

   op.add_option("-P", action="store",type="string", /

       dest="password",/

       help="password"

       )

   self.op = op

   (self.options, self.args) = self.op.parse_args()

params = parameters()

 

if not params.options.url or /

  notparams.options.username or /

  notparams.options.password :

 params.op.print_help()

 sys.exit(1)

 

url ="%s/Control_Panel/Database/manage_pack?days:float=%d" % /

    (params.options.url, params.options.days)

 

def main():  try:    f = MyOpener().open(url).read()    print "Successfully packed ZODB on host %s" % params.options.url  except:    print "Cannot open URL %s, aborted" % url    raiseif __name__ == '__main__':  main()

程式前半段在處理命令行參數 (class parameters),而在 main() 函式裡實際進行連線動作。packzope.py 利用 urllib 模組來連結 Zope 伺服器,並利用 subclassing urllib.FancyURLopener 類別來自訂使用者名稱與密碼的輸入。壓縮完畢之後,程式會輸出以下的字樣:

Successfully packed ZODB on host http://someplace:port

我們可以把 packzope.py 放到 crontab 裡定期執行。這就是一種自動化網路操作。

7   結語

本文藉由討論以 Python 進行 Linux 操作自動化的技巧,對 Python 的應用作了進一步的介紹。當然,在進行任何種類的 Python 程式開發時,都可以參考 Python 的線上說明文件。Dive into Python 是一本容易上手的自由 Python 書籍,你也可以在網路上找到中文譯本。

原创粉丝点击