Linux -> Linux下的Python脚本编程

本文介绍了使用Python进行Linux脚本编程的方法和技术,包括Python脚本的格式、字符串处理、字符编码、文件目录操作等内容。

Linux -> Linux下的Python脚本编程

 

撰写Linux使用的 Pythonscript

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

 

1 Linux、指令稿与Python

对Linux来说,指令稿(script)是至为重要的部分。

在主要的Linux distribution中间,从系统的启动到运作,都离不开shell指令稿撰写。在我的主机上面执行一下:

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

2585

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

267

看,可以找到267个shell指令稿程序,超过/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是该版Python的major version和minor version。譬如Python 2.3就会有python和python2.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看不懂的字符编码(就是指华文世界用的Big5和GB),程序虽然仍可执行,但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" ) is str: print True

...

True

>>> if type( "Another string" ) is str(): print True

...

type()是Python的内建函式,用来取得变量的型态。我们可以从这三个指令看出来,字符串"", "I am a string"都是str类别的对象。

查看Python的在线文件,会发现有两组关于字符串处理的链接库;一组是string模块里的函式,另一组则是字符串对象专用的方法(String Methods)。两者虽有一些差别,但功能的重复性相当高;我们讨论的重点在字符串方法。

我们常常会需要分析档案中的字符串:把字符串拆解开来,依照给定的逻辑来判断字符串数据的意义。

因此,最常用的字符串方法就是我们上一期有用到的split()

split()传回的是列表(list),可以用索引值(以0起始)来存取列表中的各个项

再来示范一下:

>>>tokens = "This is a sample string used 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

Python的tuple也是一种可以包含其它对象的数据结构,以整数索引存取其中的对象,但其行为与列表不尽相同。在语法上,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安装相关的外加套件:cjkcodecs与iconvcodecs;前者是中日韩专用的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 档案系统操作

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

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

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

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

Note

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

6 因特网通讯

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

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

from optparse 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 /

not params.options.username or /

not params.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()

程式前半段在處理命令行參數 (classparameters),而在 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 書籍,你也可以在網路上找到中文譯本。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值