用 Python 替代 Bash 脚本 已翻译 100%

葱油拌面 投递于 2013/04/09 09:28 (共 14 段, 翻译完成于 04-10)
阅读 10958
收藏 30
6
加载中

对于Linux用户来说,命令行的名声相当的高。不像其他操作系统,命令行是一个可怕的命题,但是对于Linux社区中那些经验丰富的大牛,命令行却是最值得推荐鼓励使用的。通常,命令行对比图形用户界面,更能提供更优雅和更高效的解决方案。

命令行伴随着Linux社区的成长,UNIX shells,例如 bash和zsh,已经成长为一个强大的工具,也是UNIX shell的重要组成部分。使用bash和其他类似的shells,可以得到一些很有用的功能,例如,管道,文件名通配符和从文件中读取命令,也就是脚本。

enixyu
enixyu
翻译于 2013/04/10 08:57
5

让我们在实际操作中来介绍命令行的强大功能吧。每当用户登陆某服务后,他们的用户名都被记录到一个文本文件。例如,我们来看看有多少独立用户曾经使用过该服务。

以下一系列的命令展现了由一个个小的命令串接起来后所实现的强大功能:

$ cat names.log | sort | uniq | wc -l

管道符号(|)把一个命令的标准输出传送给另外一个命令的标准输入。在这个例子中,把cat names.log的输出传送给sort命令的输入。sort命令是把每一行按字母顺序重新排序。接下来,管道把输出传送至uniq命令,它可以删除重复名字。最后,uniq的输出又传送给wc命令。wc是一个字符计数命令,使用-l参数,可以返回行的数量。管道可以让你把一系列的命令串接在一起。
enixyu
enixyu
翻译于 2013/04/10 09:05
2

但是,有时候需求会很复杂,串接命令会变得十分笨重。在这个情况下,shell脚本可以解决这个问题。shell脚本就是一系列的命令,被shell程序所读取,并按顺序执行。Shell脚本同样支持一些编程语言的特性,例如变量,流程控制和数据结构。shell脚步对于经常重复运行的批处理程序非常有用。但是,shell脚本也有一些弱点:

  • shell脚本很容易变为复杂的代码,导致开发人员难于阅读和修改它们。
  • 通常,它的语法和解释都不是那么灵活,而且不直观。
  • 它代码通常不能被其他脚本使用。脚本中的代码重用率很低,并且脚本通常是解决一些很具体的问题。
  • 它们一般不支持库特性,例如HTML解释器或者处理HTTP请求库,因为库一般都只出现在流行的语言和脚本语言中。
enixyu
enixyu
翻译于 2013/04/10 09:13
2

这些问题通常会导致脚本变得不灵活,并且浪费开发人员大量的时间。而Python语言作为它的替代品,是相当不错的选择。使用python作为shell脚本的替代,通常有很多优势:

  • python在主流的linux发行版本中都被默认安装。打开命令行,输入python就可以立刻进入python的世界。这个特性,让它可以成为大多脚本任务的最好选择。
  • python非常容易阅读,语法容易理解。它的风格注重编写简约和干净的代码,允许开发人员编写适合shell脚本的风格代码。
  • python是一个解释性语言,这意味着,不需要编译。这让python成为最理想的脚本语言。python同时还是读取,演绎,输出的循环风格,这允许开发人员可以快速的通过解释器尝试新的代码。开发人员无需重新编写整个程序,就可以实现自己的一些想法。
  • python是一个功能齐全的编程语言。代码重用非常简单,因为python模块可以在脚本中方便的导入和使用。脚本可以轻易的扩展。
  • python可以访问优秀的标准库,还有大量的实现多种功能的第三方库。例如解释器和请求库。例如,python的标准库包含时间库,允许我们把时间转换为我们想要的各种格式,而且可以和其他日期做比较。
  • python可以是命令链中的一部分。python不能完全代替bash。python程序可以像UNIX风格那样(从标准输入读取,从标准输出中输出),所以python程序可以实现一些shell命令,例如cat和sort。
enixyu
enixyu
翻译于 2013/04/10 09:30
2

让我们基于文章前面提到问题,重新使用python构建。除了已完成的工作,还让我们来看看某个用户登陆系统到底有多少次。uniq命令只是简单的删除重复记录,而没有提示到底这些重复记录重复了多少次。我们使用python脚本替代uniq命令,而且脚本可以作为命令链中的一部分。以下是python程序实现这个功能(在这个例子中,脚本叫做namescount.py):

#!/usr/bin/env python
import sys

if __name__ == "__main__":
    # 初始化一个names的字典,内容为空
    # 字典中为name和出现数量的键值对
    names = {}
    # sys.stdin是一个文件对象。 所有引用于file对象的方法,
    # 都可以应用于sys.stdin.
    for name in sys.stdin.readlines():
            # 每一行都有一个newline字符做结尾
            # 我们需要删除它
            name = name.strip()
            if name in names:
                    names[name] += 1
            else:
                    names[name] = 1

    # 迭代字典,
    # 输出名字,空格,接着是该名字出现的数量
    for name, count in names.iteritems():
            sys.stdout.write("%d\t%s\n" % (count, name))

让我们来看看python脚本如何在命令链中起作用的。首先,它从标准输入sys.stdin对象读取数据。所有的输出都写到sys.stdout对象里面,这个对象是python里面的标准输出的实现。然后使用python字典(在其他语言中,叫做哈希表)来保存名字和重复次数的映射。要读取所有用户的登陆次数,只需执行下面的命令:
$ cat names.log | python namescount.py
enixyu
enixyu
翻译于 2013/04/10 12:59
3

这里会输出某用户出现的次数还有他的名字,使用tab作为分隔符。接下来的事情就是,以用户登陆次数的降序顺序输出。这可以在python中实现,但是让我们使用UNIX的命令来实现吧。前面已经提到,使用sort命令可以按字母顺序排序。如果sort命令接收一个-rn参数,那么它就会按照数字的降序方式做排序。因为python脚本输出到标准输出,所以我们可以使用管道链接sort命令,获取该输出:

$ cat names.log | python namescount.py | sort -rn

这个例子使用了python作为命令链中的一部分。使用python的优势是:
  • 可以跟例如cat和sort这样的命令链接在一起。简单的工具(读取文件,给文件按数字排序),可以使用成熟的UNIX命令。这些命令都是一行一行的读取,这意味着这些命令可以兼容大容量的文件,而且它们的效率很高。
  • 如果命令链条中某部分很难实现,很清晰,我们可以使用python脚本,这可以让我们做我们想做的,然后减轻链条一下个命令的负担。
  • python是一个可重用的模块,虽然这个例子是指定了names,如果你需要处理重复行的其他输入,你可以输出每一行,还有该行的重复次数。让python脚本模块化,这样你就可以把它应用到其他地方。
enixyu
enixyu
翻译于 2013/04/10 13:10
2

为了演示python脚本中结合模块和管道风格的强大力量,让我们扩展一下这个问题。让我们来找出使用服务最多的前5位用户。head命令可以让我们指定需要输出的行数。在命令链中加入这个命令:

$ cat names.log | python namescount.py | sort -rn | head -n 5

这个命令只会列出前5位用户。类似的,获取使用该服务最少的5位用户,你可以使用tail命令,这个命令使用同样的参数。python命令的结果输出到标准输出,这样可以允许你扩展和构建它的功能。


为了演示脚本的模块化特性,我们又来扩展一下问题。该服务同样生成一个以逗号分割的csv的日志文件,其中包含,一个email地址列表,还有该地址对我们服务的评价。如下是其中一个例子:

"email@example.com", "This service is great."

enixyu
enixyu
翻译于 2013/04/10 13:16
2

这个任务是,提供一个途径,来发送一个感谢信息给使用该服务最多的前10位用户。首先,我们需要一个脚本读取csv和输出其中某一个字段。python提供一个标准的csv读取模块。以下的python脚本实现了这个功能:

#!/usr/bin/env python
# CSV module that comes with the Python standard library
import csv
import sys


if __name__ == "__main__":
    # CSV模块使用一个reader对象作为输入
    # 在这个例子中,就是 sys.stdin.
    csvfile = csv.reader(sys.stdin)

    # 这个脚本必须接收一个参数,指定列的序号
    # 使用sys.argv获取参数.
    column_number = 0
    if len(sys.argv) > 1:
            column_number = int(sys.argv[1])

    # CSV文件的每一行都是用逗号作为字段的分隔符
    for row in csvfile:
            print row[column_number]

这个脚本可以把csv转换并返回参数指定的字段的文本。它使用print代替sys.stout.write,因为print默认使用标准输出最为它的输出文件。

让我们把这个脚步添加到命令链中。新的脚本跟其他命令组合在一起,实现输出评论最多的email地址。(假设.csv 文件名称为emailcomments.csv,新的脚本为csvcolumn.py)

enixyu
enixyu
翻译于 2013/04/10 13:25
2

接下来,你需要一个发送邮件的方法,在Python 函数标准库中,你可以导入smtplib 库,这是一个用来连接SMTP服务器并发送邮件的模块。让我们写一个简单的Python脚本,使用这个模块发送一个邮件给每个top 10 的用户。

#!/usr/bin/env python
import smtplib
import sys


GMAIL_SMTP_SERVER = "smtp.gmail.com"
GMAIL_SMTP_PORT = 587

GMAIL_EMAIL = "Your Gmail Email Goes Here"
GMAIL_PASSWORD = "Your Gmail Password Goes Here"


def initialize_smtp_server():
    '''
    This function initializes and greets the smtp server.
    It logs in using the provided credentials and returns 
    the smtp server object as a result.
    '''
    smtpserver = smtplib.SMTP(GMAIL_SMTP_SERVER, GMAIL_SMTP_PORT)
    smtpserver.ehlo()
    smtpserver.starttls()
    smtpserver.ehlo()
    smtpserver.login(GMAIL_EMAIL, GMAIL_PASSWORD)
    return smtpserver


def send_thank_you_mail(email):
    to_email = email
    from_email = GMAIL_EMAIL
    subj = "Thanks for being an active commenter"
    # The header consists of the To and From and Subject lines
    # separated using a newline character
    header = "To:%s\nFrom:%s\nSubject:%s \n" % (to_email,
            from_email, subj)
    # Hard-coded templates are not best practice.
    msg_body = """
    Hi %s,

    Thank you very much for your repeated comments on our service.
    The interaction is much appreciated.

    Thank You.""" % email
    content = header + "\n" + msg_body
    smtpserver = initialize_smtp_server()
    smtpserver.sendmail(from_email, to_email, content)
    smtpserver.close()


if __name__ == "__main__":
    # for every line of input.
    for email in sys.stdin.readlines():
            send_thank_you_mail(email)
这个python脚本能够连接任何的SMTP服务器,不管是在本地还是远程。为便于使用,我使用了Gmail的SMTP服务器,正常情况下,应该提供你连接Gmail的密码口令,这个脚本使用了smtp库中的函数发送邮件。再一次证明使用Python脚本的强大之处,类似SMTP这样的交互操作使用python来写的话是比较简单易读的。相同的shell脚本的话,可能是比较复杂并且像SMTP这样的库是基本没有的。

葱油拌面
葱油拌面
翻译于 2013/04/10 10:02
4

为了发送电子邮件给评论频率最高的前十名用户,首先必须单独得到电子邮件列的内容。要取出某一列,在Linux中你可以使用cut命令。在下面的例子中,命令是在两个单独的串。为了便于使用,我写输出到一个临时文件,其中可以加载到第二串命令中。这只是让过程更具可读性(Python发送邮件脚本简称为sendemail.py):

$ cat emailcomments.csv | python csvcolumn.py | 
 ↪python namescount.py | sort -rn > /tmp/comment_freq
$ cat /tmp/comment_freq | head -n 10 | cut -f2 | 
 ↪python sendemail.py

这表明Python作为一种实用工具如bash命令链的真正威力。编写的脚本从标准输入接受 数据并且将任何输出写入到标准输出,允许开发者串起这些命令, 链中的这些快速,简单的命令以及Python程序。这种只为一个目的设计小程序的哲学非常适用于这里所使用的命令流方式。
showme
showme
翻译于 2013/04/10 12:45
2
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(2)

HenryGuo
HenryGuo

引用来自“JackeyGao”的评论

从面向过程的shell 到面向对象的python, 不只是语言的转变 , 经历过那么的过程之后 , 我能有对象吗 ?

能..
JackeyGao
JackeyGao
从面向过程的shell 到面向对象的python, 不只是语言的转变 , 经历过那么的过程之后 , 我能有对象吗 ?
返回顶部
顶部