對(duì)于Linux用戶來(lái)說(shuō),命令行的名聲相當(dāng)?shù)母摺2幌衿渌僮飨到y(tǒng),命令行是一個(gè)可怕的命題,但是對(duì)于Linux社區(qū)中那些經(jīng)驗(yàn)豐富的大牛,命令行卻是最值得推薦鼓勵(lì)使用的。通常,命令行對(duì)比圖形用戶界面,更能提供更優(yōu)雅和更高效的解決方案。
命令行伴隨著Linux社區(qū)的成長(zhǎng),UNIX shells,例如 bash和zsh,已經(jīng)成長(zhǎng)為一個(gè)強(qiáng)大的工具,也是UNIX shell的重要組成部分。使用bash和其他類似的shells,可以得到一些很有用的功能,例如,管道,文件名通配符和從文件中讀取命令,也就是腳本。
讓我們?cè)趯?shí)際操作中來(lái)介紹命令行的強(qiáng)大功能吧。每當(dāng)用戶登陸某服務(wù)后,他們的用戶名都被記錄到一個(gè)文本文件。例如,我們來(lái)看看有多少獨(dú)立用戶曾經(jīng)使用過(guò)該服務(wù)。
以下一系列的命令展現(xiàn)了由一個(gè)個(gè)小的命令串接起來(lái)后所實(shí)現(xiàn)的強(qiáng)大功能:
?
$ cat names.log | sort | uniq | wc -l
管道符號(hào)(|)把一個(gè)命令的標(biāo)準(zhǔn)輸出傳送給另外一個(gè)命令的標(biāo)準(zhǔn)輸入。在這個(gè)例子中,把cat names.log的輸出傳送給sort命令的輸入。sort命令是把每一行按字母順序重新排序。接下來(lái),管道把輸出傳送至uniq命令,它可以刪除重復(fù)名字。最后,uniq的輸出又傳送給wc命令。wc是一個(gè)字符計(jì)數(shù)命令,使用-l參數(shù),可以返回行的數(shù)量。管道可以讓你把一系列的命令串接在一起。
但是,有時(shí)候需求會(huì)很復(fù)雜,串接命令會(huì)變得十分笨重。在這個(gè)情況下,shell腳本可以解決這個(gè)問(wèn)題。shell腳本就是一系列的命令,被shell程序所讀取,并按順序執(zhí)行。Shell腳本同樣支持一些編程語(yǔ)言的特性,例如變量,流程控制和數(shù)據(jù)結(jié)構(gòu)。shell腳步對(duì)于經(jīng)常重復(fù)運(yùn)行的批處理程序非常有用。但是,shell腳本也有一些弱點(diǎn):
- ??? shell腳本很容易變?yōu)閺?fù)雜的代碼,導(dǎo)致開(kāi)發(fā)人員難于閱讀和修改它們。
- ??? 通常,它的語(yǔ)法和解釋都不是那么靈活,而且不直觀。
- ??? 它代碼通常不能被其他腳本使用。腳本中的代碼重用率很低,并且腳本通常是解決一些很具體的問(wèn)題。
- ??? 它們一般不支持庫(kù)特性,例如HTML解釋器或者處理HTTP請(qǐng)求庫(kù),因?yàn)閹?kù)一般都只出現(xiàn)在流行的語(yǔ)言和腳本語(yǔ)言中。
這些問(wèn)題通常會(huì)導(dǎo)致腳本變得不靈活,并且浪費(fèi)開(kāi)發(fā)人員大量的時(shí)間。而Python語(yǔ)言作為它的替代品,是相當(dāng)不錯(cuò)的選擇。使用python作為shell腳本的替代,通常有很多優(yōu)勢(shì):
- ??? python在主流的linux發(fā)行版本中都被默認(rèn)安裝。打開(kāi)命令行,輸入python就可以立刻進(jìn)入python的世界。這個(gè)特性,讓它可以成為大多腳本任務(wù)的最好選擇。
- ??? python非常容易閱讀,語(yǔ)法容易理解。它的風(fēng)格注重編寫(xiě)簡(jiǎn)約和干凈的代碼,允許開(kāi)發(fā)人員編寫(xiě)適合shell腳本的風(fēng)格代碼。
- ??? python是一個(gè)解釋性語(yǔ)言,這意味著,不需要編譯。這讓python成為最理想的腳本語(yǔ)言。python同時(shí)還是讀取,演繹,輸出的循環(huán)風(fēng)格,這允許開(kāi)發(fā)人員可以快速的通過(guò)解釋器嘗試新的代碼。開(kāi)發(fā)人員無(wú)需重新編寫(xiě)整個(gè)程序,就可以實(shí)現(xiàn)自己的一些想法。
- ??? python是一個(gè)功能齊全的編程語(yǔ)言。代碼重用非常簡(jiǎn)單,因?yàn)閜ython模塊可以在腳本中方便的導(dǎo)入和使用。腳本可以輕易的擴(kuò)展。
- ??? python可以訪問(wèn)優(yōu)秀的標(biāo)準(zhǔn)庫(kù),還有大量的實(shí)現(xiàn)多種功能的第三方庫(kù)。例如解釋器和請(qǐng)求庫(kù)。例如,python的標(biāo)準(zhǔn)庫(kù)包含時(shí)間庫(kù),允許我們把時(shí)間轉(zhuǎn)換為我們想要的各種格式,而且可以和其他日期做比較。
- ??? python可以是命令鏈中的一部分。python不能完全代替bash。python程序可以像UNIX風(fēng)格那樣(從標(biāo)準(zhǔn)輸入讀取,從標(biāo)準(zhǔn)輸出中輸出),所以python程序可以實(shí)現(xiàn)一些shell命令,例如cat和sort。
讓我們基于文章前面提到問(wèn)題,重新使用python構(gòu)建。除了已完成的工作,還讓我們來(lái)看看某個(gè)用戶登陸系統(tǒng)到底有多少次。uniq命令只是簡(jiǎn)單的刪除重復(fù)記錄,而沒(méi)有提示到底這些重復(fù)記錄重復(fù)了多少次。我們使用python腳本替代uniq命令,而且腳本可以作為命令鏈中的一部分。以下是python程序?qū)崿F(xiàn)這個(gè)功能(在這個(gè)例子中,腳本叫做namescount.py):
?
#!/usr/bin/env python
import sys
if __name__ == "__main__":
# 初始化一個(gè)names的字典,內(nèi)容為空
# 字典中為name和出現(xiàn)數(shù)量的鍵值對(duì)
names = {}
# sys.stdin是一個(gè)文件對(duì)象。 所有引用于file對(duì)象的方法,
# 都可以應(yīng)用于sys.stdin.
for name in sys.stdin.readlines():
# 每一行都有一個(gè)newline字符做結(jié)尾
# 我們需要?jiǎng)h除它
name = name.strip()
if name in names:
names[name] += 1
else:
names[name] = 1
# 迭代字典,
# 輸出名字,空格,接著是該名字出現(xiàn)的數(shù)量
for name, count in names.iteritems():
sys.stdout.write("%d\t%s\n" % (count, name))
讓我們來(lái)看看python腳本如何在命令鏈中起作用的。首先,它從標(biāo)準(zhǔn)輸入sys.stdin對(duì)象讀取數(shù)據(jù)。所有的輸出都寫(xiě)到sys.stdout對(duì)象里面,這個(gè)對(duì)象是python里面的標(biāo)準(zhǔn)輸出的實(shí)現(xiàn)。然后使用python字典(在其他語(yǔ)言中,叫做哈希表)來(lái)保存名字和重復(fù)次數(shù)的映射。要讀取所有用戶的登陸次數(shù),只需執(zhí)行下面的命令:
?
$ cat names.log | python namescount.py
這里會(huì)輸出某用戶出現(xiàn)的次數(shù)還有他的名字,使用tab作為分隔符。接下來(lái)的事情就是,以用戶登陸次數(shù)的降序順序輸出。這可以在python中實(shí)現(xiàn),但是讓我們使用UNIX的命令來(lái)實(shí)現(xiàn)吧。前面已經(jīng)提到,使用sort命令可以按字母順序排序。如果sort命令接收一個(gè)-rn參數(shù),那么它就會(huì)按照數(shù)字的降序方式做排序。因?yàn)閜ython腳本輸出到標(biāo)準(zhǔn)輸出,所以我們可以使用管道鏈接sort命令,獲取該輸出:
?
$ cat names.log | python namescount.py | sort -rn
這個(gè)例子使用了python作為命令鏈中的一部分。使用python的優(yōu)勢(shì)是:
- ??? 可以跟例如cat和sort這樣的命令鏈接在一起。簡(jiǎn)單的工具(讀取文件,給文件按數(shù)字排序),可以使用成熟的UNIX命令。這些命令都是一行一行的讀取,這意味著這些命令可以兼容大容量的文件,而且它們的效率很高。
- ??? 如果命令鏈條中某部分很難實(shí)現(xiàn),很清晰,我們可以使用python腳本,這可以讓我們做我們想做的,然后減輕鏈條一下個(gè)命令的負(fù)擔(dān)。
- ??? python是一個(gè)可重用的模塊,雖然這個(gè)例子是指定了names,如果你需要處理重復(fù)行的其他輸入,你可以輸出每一行,還有該行的重復(fù)次數(shù)。讓python腳本模塊化,這樣你就可以把它應(yīng)用到其他地方。
為了演示python腳本中結(jié)合模塊和管道風(fēng)格的強(qiáng)大力量,讓我們擴(kuò)展一下這個(gè)問(wèn)題。讓我們來(lái)找出使用服務(wù)最多的前5位用戶。head命令可以讓我們指定需要輸出的行數(shù)。在命令鏈中加入這個(gè)命令:
?
$ cat names.log | python namescount.py | sort -rn | head -n 5
這個(gè)命令只會(huì)列出前5位用戶。類似的,獲取使用該服務(wù)最少的5位用戶,你可以使用tail命令,這個(gè)命令使用同樣的參數(shù)。python命令的結(jié)果輸出到標(biāo)準(zhǔn)輸出,這樣可以允許你擴(kuò)展和構(gòu)建它的功能。
為了演示腳本的模塊化特性,我們又來(lái)擴(kuò)展一下問(wèn)題。該服務(wù)同樣生成一個(gè)以逗號(hào)分割的csv的日志文件,其中包含,一個(gè)email地址列表,還有該地址對(duì)我們服務(wù)的評(píng)價(jià)。如下是其中一個(gè)例子:
?
"email@example.com", "This service is great."
這個(gè)任務(wù)是,提供一個(gè)途徑,來(lái)發(fā)送一個(gè)感謝信息給使用該服務(wù)最多的前10位用戶。首先,我們需要一個(gè)腳本讀取csv和輸出其中某一個(gè)字段。python提供一個(gè)標(biāo)準(zhǔn)的csv讀取模塊。以下的python腳本實(shí)現(xiàn)了這個(gè)功能:
?
#!/usr/bin/env python
# CSV module that comes with the Python standard library
import csv
import sys
if __name__ == "__main__":
# CSV模塊使用一個(gè)reader對(duì)象作為輸入
# 在這個(gè)例子中,就是 sys.stdin.
csvfile = csv.reader(sys.stdin)
# 這個(gè)腳本必須接收一個(gè)參數(shù),指定列的序號(hào)
# 使用sys.argv獲取參數(shù).
column_number = 0
if len(sys.argv) > 1:
column_number = int(sys.argv[1])
# CSV文件的每一行都是用逗號(hào)作為字段的分隔符
for row in csvfile:
print row[column_number]
這個(gè)腳本可以把csv轉(zhuǎn)換并返回參數(shù)指定的字段的文本。它使用print代替sys.stout.write,因?yàn)閜rint默認(rèn)使用標(biāo)準(zhǔn)輸出最為它的輸出文件。
讓我們把這個(gè)腳步添加到命令鏈中。新的腳本跟其他命令組合在一起,實(shí)現(xiàn)輸出評(píng)論最多的email地址。(假設(shè).csv 文件名稱為emailcomments.csv,新的腳本為csvcolumn.py)
接下來(lái),你需要一個(gè)發(fā)送郵件的方法,在Python 函數(shù)標(biāo)準(zhǔn)庫(kù)中,你可以導(dǎo)入smtplib 庫(kù),這是一個(gè)用來(lái)連接SMTP服務(wù)器并發(fā)送郵件的模塊。讓我們寫(xiě)一個(gè)簡(jiǎn)單的Python腳本,使用這個(gè)模塊發(fā)送一個(gè)郵件給每個(gè)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)
這個(gè)python腳本能夠連接任何的SMTP服務(wù)器,不管是在本地還是遠(yuǎn)程。為便于使用,我使用了Gmail的SMTP服務(wù)器,正常情況下,應(yīng)該提供你連接Gmail的密碼口令,這個(gè)腳本使用了smtp庫(kù)中的函數(shù)發(fā)送郵件。再一次證明使用Python腳本的強(qiáng)大之處,類似SMTP這樣的交互操作使用python來(lái)寫(xiě)的話是比較簡(jiǎn)單易讀的。相同的shell腳本的話,可能是比較復(fù)雜并且像SMTP這樣的庫(kù)是基本沒(méi)有的。
為了發(fā)送電子郵件給評(píng)論頻率最高的前十名用戶,首先必須單獨(dú)得到電子郵件列的內(nèi)容。要取出某一列,在Linux中你可以使用cut命令。在下面的例子中,命令是在兩個(gè)單獨(dú)的串。為了便于使用,我寫(xiě)輸出到一個(gè)臨時(shí)文件,其中可以加載到第二串命令中。這只是讓過(guò)程更具可讀性(Python發(fā)送郵件腳本簡(jiǎn)稱為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作為一種實(shí)用工具如bash命令鏈的真正威力。編寫(xiě)的腳本從標(biāo)準(zhǔn)輸入接受 數(shù)據(jù)并且將任何輸出寫(xiě)入到標(biāo)準(zhǔn)輸出,允許開(kāi)發(fā)者串起這些命令, 鏈中的這些快速,簡(jiǎn)單的命令以及Python程序。這種只為一個(gè)目的設(shè)計(jì)小程序的哲學(xué)非常適用于這里所使用的命令流方式。
通常在命令行中使用的Python腳本,當(dāng)他們運(yùn)行某個(gè)命令時(shí),參數(shù)由用戶來(lái)選擇。例如,head命令取得一個(gè)-n的參數(shù)標(biāo)志和它后面的數(shù)字,然后只打印這個(gè)數(shù)字大小的行數(shù)。Python腳本的每一個(gè)參數(shù)都是通過(guò)sys.argv數(shù)組提供,可在import sys后來(lái)訪問(wèn)。下面的代碼顯示了如何使用單個(gè)詞語(yǔ)作為參數(shù)。此程序是一個(gè)簡(jiǎn)單的加法器,它有兩個(gè)數(shù)字參數(shù),將它們相加,并打印輸出給用戶。然而,這種命令行參數(shù)使用方式是非常基礎(chǔ)的。這也是很容易出錯(cuò)誤的 ――例如,輸入兩個(gè)字符串,如hello和world,這個(gè)命令,你會(huì)一開(kāi)始就得到錯(cuò)誤:
?
#!/usr/bin/env python
import sys
if __name__ == "__main__":
# The first argument of sys.argv is always the filename,
# meaning that the length of system arguments will be
# more than one, when command-line arguments exist.
if len(sys.argv) > 2:
num1 = long(sys.argv[1])
num2 = long(sys.argv[2])
else:
print "This command takes two arguments and adds them"
print "Less than two arguments given."
sys.exit(1)
print "%s" % str(num1 + num2)
慶幸的是,Python有很多處理有關(guān)命令行參數(shù)的模塊。我個(gè)人比較喜歡OptionParser。OptionParser是標(biāo)準(zhǔn)庫(kù)提供的optparse模塊的一部分。OptionParser允許你對(duì)命令行參數(shù)做一系列非常有用的操作。
- ??? 如果沒(méi)有提供具體的參數(shù),可以指定默認(rèn)的參數(shù)
- ??? 它支持參數(shù)標(biāo)志(顯示或不顯示)和參數(shù)值(-n 10000)。
- ??? 它支持傳遞參數(shù)的不同格式――例如,有差別的-n=100000和-n 100000。
我們來(lái)用OptionParser來(lái)改進(jìn)sending-mail腳本。原來(lái)的腳本有很多的變量硬編碼的地方,比如SMTP細(xì)節(jié)和用戶的登錄憑據(jù)。在下面提供的代碼,在這些變量是用來(lái)傳遞命令行參數(shù):
?
#!/usr/bin/env python
import smtplib
import sys
from optparse import OptionParser
def initialize_smtp_server(smtpserver, smtpport, email, pwd):
'''
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(smtpserver, smtpport)
smtpserver.ehlo()
smtpserver.starttls()
smtpserver.ehlo()
smtpserver.login(email, pwd)
return smtpserver
def send_thank_you_mail(email, smtpserver):
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.sendmail(from_email, to_email, content)
if __name__ == "__main__":
usage = "usage: %prog [options]"
parser = OptionParser(usage=usage)
parser.add_option("--email", dest="email",
help="email to login to smtp server")
parser.add_option("--pwd", dest="pwd",
help="password to login to smtp server")
parser.add_option("--smtp-server", dest="smtpserver",
help="smtp server url", default="smtp.gmail.com")
parser.add_option("--smtp-port", dest="smtpserverport",
help="smtp server port", default=587)
options, args = parser.parse_args()
if not (options.email or options.pwd):
parser.error("Must provide both an email and a password")
smtpserver = initialize_smtp_server(options.stmpserver,
options.smtpserverport, options.email, options.pwd)
# for every line of input.
for email in sys.stdin.readlines():
send_thank_you_mail(email, smtpserver)
smtpserver.close()
這個(gè)腳本顯示OptionParser 的作用。它提供了一個(gè)簡(jiǎn)單、易于使用的接口給命令行參數(shù), 允許你為每個(gè)命令行選項(xiàng)定義某些屬性。它還允許你指定默認(rèn)值。如果沒(méi)有給出某些參數(shù),它可以給你報(bào)出特定錯(cuò)誤。
現(xiàn)在你學(xué)到了多少?并不是使用一個(gè)python腳本替代所有的bash命令,我們更推薦讓python完成其中某些困難的任務(wù)。這需要更多的模塊化和重用的腳本,還要好好利用python的強(qiáng)大功能。
使用stdin作為文件對(duì)象,這可以允許python讀取輸入,這個(gè)輸入是由管道傳輸其他命令的輸出給它的,而把輸出輸出到stout,可以允許python把信息傳遞到管道系統(tǒng)的下一環(huán)節(jié)。結(jié)合這些功能,可以實(shí)現(xiàn)強(qiáng)大的程序。在這里提到的例子,就是要實(shí)現(xiàn)一個(gè)處理服務(wù)的日志文件。
在實(shí)際應(yīng)用中,我最近在處理一個(gè)GB級(jí)別的CSV文件,我需要使用python腳本轉(zhuǎn)換一個(gè)包含插入數(shù)據(jù)的SQL命令。了解我需要處理的文件,并在一個(gè)表中處理這些數(shù)據(jù),腳本需要23個(gè)小時(shí)來(lái)執(zhí)行并生成20GB的SQL文件。使用文章提到的python編程風(fēng)格的優(yōu)勢(shì)在于,我們不需要把這個(gè)文件讀取到內(nèi)存中。這意味著整個(gè)20GB+的文件可以一行一行的處理。而且我們更清晰的分解每一個(gè)步驟(讀取,排序,維護(hù)和輸出)為一些邏輯步驟。還有我們得到這些命令的保障,其中這些命令都是UNIX類型的環(huán)境的核心工具,它們十分高效和穩(wěn)定,可以幫助我們構(gòu)建穩(wěn)定安全的程序。
另外一個(gè)優(yōu)點(diǎn)在于,我們不需要硬編碼文件名。這樣可以使得程序更靈活,只需傳遞一個(gè)參數(shù)。例如,如果腳本在某個(gè)文件在20000中斷了,我們不需要重新運(yùn)行腳本,我們可以使用tail來(lái)指定失敗的行數(shù),來(lái)讓腳本在這個(gè)位置繼續(xù)運(yùn)行。
python在shell中的應(yīng)用范圍很廣,不局限于本文所述,例如os模塊和subprocess模塊。os模塊是一個(gè)標(biāo)準(zhǔn)庫(kù),可以執(zhí)行很多操作系統(tǒng)級(jí)別的操作,例如列出目錄的結(jié)構(gòu),文件的統(tǒng)計(jì)信息,還有一個(gè)優(yōu)秀的os.path子模塊,可以處理規(guī)范目錄路徑。subprocess模塊允許python程序運(yùn)行系統(tǒng)命令和其他高級(jí)命令,例如,上文提到的使用python代碼和spawned進(jìn)程之間的管道處理。如果你需要編寫(xiě)python的shell腳本,這些庫(kù)都值得去研究的。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元

