2016.11.7
2 Scripting and the Shell
Good system administrators write scripts. Scripts standardize and automate the performance of administrative chores and free up admins’ time for more important and more interesting tasks. In a sense, scripts are also a kind of low-rent documentation in that they act as an authoritative outline of the steps needed to complete a particular task.
好的系統管理者都要寫腳本。腳本以标準和自動的方式履行系統管理者的繁雜事務,藉此把管理者的時間節省出來,以花在更重要和更有意思的任務上。從某種意思上講,腳本也是一種低品質的文檔,因為它們充當了一種權威提綱,提綱裡列出完成特殊任務所需的步驟。
In terms of complexity, administrative scripts run the gamut from simple ones that encapsulate a few static commands to major software projects that manage host configurations and administrative data for an entire site. In this book we’re primarily interested in the smaller, day-to-day scripting projects that sysadmins normally encounter, so we don’t talk much about the support functions (e.g., bug tracking and design review) that are needed for larger projects.
從複雜性來看,系統管理腳本的範圍很廣,簡單得隻封裝幾條靜态指令,大到一處重要的軟體項目,為整個站點管理主機配置和管理性資料。在本書裡,我們所感興趣的主要是系統管理者通常會碰到的較小的日常腳本項目。是以,對于大的項目才需要的支援功能(例如,bug追蹤和設計評審),我們不會講得太多。
Administrative scripts should emphasize programmer efficiency and code clarity rather than computational efficiency. This is not an excuse to be sloppy, but simply a recognition that it rarely matters whether a script runs in half a second or two seconds. Optimization can have an amazingly low return on investment, even for scripts that run regularly out of cron.
系統管理腳本應該注重兩點,即程式設計人員的開發效率和代碼的清晰可讀性。計算效率不應該成為關注重點,但這不應該成為草率行事的借口,而是要認識到,很少需要在意一個腳本是在半秒還是兩秒内運作完。優化腳本獲得的回報都非常低,甚至對通過cron定期運作的腳本來說也不例外。
For a long time, the standard language for administrative scripts was the one defined by the shell. Most systems’ default shell is bash (the “Bourne-again” shell),but sh (the original Bourne shell) and ksh (the Korn shell) are used on a few UNIX systems. Shell scripts are typically used for light tasks such as automating a sequence of commands or assembling several filters to process data.
長期以來,編寫系統管理腳本的标準語言是shell所定義的語言。在大多數系統上,預設的shell都是由bash(即“Bourne agin”shell),但是在幾種不多的UNIX系統上,也用sh(最初的Bourne shell)和ksh(Korn shell)。shell腳本一般用于輕量級的任務,如自動執行一系列指令,或者把幾個過濾器組合起來處理資料。
The shell is always available, so shell scripts are relatively portable and have few dependencies other than the commands they invoke. Whether or not you choose the shell, the shell may choose you: most environments include a hefty complement of existing sh scripts, and those scripts frequently need to be read, understood, and tweaked by administrators.
各種作業系統上都有shell,是以shell腳本可移值性相當好,除了它們調用的指令之外,要依賴的東西也不多。無論是否選擇shell來編寫腳本,都會碰到shell;大多數環境都包括已有sh腳本的強大補充,系統管理者會頻頻閱讀、了解和調整這些腳本。
For more sophisticated scripts, it’s advisable to jump to a real programming language such as Perl or Python, both of which are well suited for administrative work. These languages incorporate a couple of decades’ worth of language design advancements relative to the shell, and their text processing facilities (invaluable to administrators) are so powerful that sh can only weep and cower in shame.
對于更為複雜高端的腳本來說,建議轉而采用一種真正的程式設計語言來寫,像perl或者python這樣的語言,它們兩者都很适合于系統管理工作。這兩種語言融入的設計理念比shell領先20年,它們的字處理功能(對于系統管理者來說,價值難以估量)如此強大,sh在它們面前黯然失色。
The main drawback to Perl and Python is that their environments can be a bit
fussy to set up, especially when you start to use third-party libraries that have compiled components. The shell skirts this particular issue by having no module structure and no third-party libraries.
Perl和Python的主要缺點在于,建立它們的環境要麻煩一點,尤其是要用到的第三方庫,而庫裡又包含已經編譯好的部件的時候。shell沒有子產品結構,也沒有第三方的庫,是以避開了這個特殊的問題。
This chapter takes a quick look at bash, Perl, and Python as languages for scripting, along with regular expressions as a general technology.
本章簡要介紹了bash、Perl和Python作為腳本程式設計語言的用法,以及正規表達式這種通用的技術。
2.1 SHELL BASICS
Before we take up shell scripting, let’s review some of the basic features and syntax of the shell. The material in this section applies to all major shells in the sh lineage (including bash and ksh, but not csh or tcsh), regardless of the exact platform you are using. Try out the forms you’re not familiar with, and experiment!
2.1 shell的基礎知識
在我們開始介紹Shell的腳本編輯之前,讓我們先看看shell的一此基本特性和文法。不管讀者正在使用的是何種平台,本節都适用于sh大家庭裡的所有主流shell(包括bash和ksh,但不包括csh或者tesh)。嘗試一下自己不熟悉的sh形式,做做實驗吧!
2016.11.11 15:10
Command editing
We’ve watched way too many people edit command lines with the arrow keys. You
wouldn’t do that in your text editor, right?
If you like emacs, all the basic emacs commands are available to you when you’re editing history. <Control-E> goes to the end of the line and <Control-A> to the beginning. <Control-P> steps backward through recently executed commands and recalls them for editing. <Control-R> searches incrementally through your history to find old commands.
If you like vi, put your shell’s command-line editing into vi mode like this:
$ set -o vi
As in vi, editing is modal; however, you start in input mode. Type <Esc> to leave input mode and “i” to reenter it. In edit mode, “w” takes you forward a word, “fX”finds the next X in the line, and so on. You can walk through past command history entries with <Esc> k. Want emacs editing mode back again?
$ set -o emacs
2.1.1 編輯指令
我們已經注意到一點,太多人都用方向鍵來編輯指令行。但讀者朋友不會在文字編輯器裡這麼做,對嗎?
如果喜歡emacs,那麼在編輯指令曆史的時候,所有emacs基本指令都用得上,用<Control-E>到行尾,用<Control-A>到指令行的開頭,用<Control-P>一條一條回退到最近執行過的指令,重新把它們調出來進行編輯。用<Control-R>增量搜尋指令曆史找出老指令。
如果喜歡vi,那麼用下面的指令就能讓shell的指令行編輯進入vi模式:
和在vi裡一樣,編輯操作也是有模式的;不過,一開始會進入輸入模式。按ESC鍵開始離開輸入模式,按“i”鍵重新進入輸入模式。在編輯模式下“w”鍵向前進一個單詞,“FX”在本行裡找到下一個X,等等。用<ESC>k可以周遊過去輸入的指令。想要再次回到emacs編輯代碼,輸入:
Pipes and redirection
Every process has at least three communication channels available to it: “standard input” (STDIN), “standard output” (STDOUT), and “standard error” (STDERR). The kernel sets up these channels on the process’s behalf, so the process itself doesn’t necessarily know where they lead. They might connect to a terminal window, a file, a network connection, or a channel belonging to another process, to name a few possibilities.
2.1.2 管理和重定向
每個程序都至少有3個資訊:“标準輸入”、标準輸出、和“标準出錯”。核心給每個程序都設定了3個信道,是以程序本身不必知道這三個信道通到哪裡。舉例來說,它們可能連接配接到一個終端視窗、一個網絡連接配接,或者屬于另一個程序的信道。
UNIX has a unified I/O model in which each channel is named with a small integer called a file descriptor. The exact number assigned to a channel is not usually significant, but STDIN, STDOUT, and STDERR are guaranteed to correspond to file descriptors 0, 1, and 2, so it’s safe to refer to these channels by number. In the context of an interactive terminal window, STDIN normally reads from the keyboard and both STDOUT and STDERR write their output to the screen.
UNIX有一個統一的I/O模型,在這個模型中,每個信道都以一個整數來命名,它叫做檔案描述符。配置設定給一個信道整數值到底是哪個,通常而主沒有意義,但要保證STDIN、STDOUT和STDERR對應檔案描述符0,1和2,是以保險的做法是,用數字來引用這三個信道。在互動式的終端視窗裡,STDIN一般讀取鍵盤的輸入,而STDOUT和STDERR把它們的輸出寫到螢幕上。
Most commands accept their input from STDIN and write their output to STDOUT. They write error messages to STDERR. This convention lets you string commands together like building blocks to create composite pipelines.
大多數指令都接受從STDIN來輸入,并且把自己的輸出寫到STDOUT,而把錯資訊寫到STDERR。有了這樣的約定,使用者就能把指令像積木一樣串起來,建立出混合管道。
The shell interprets the symbols <, >, and >> as instructions to reroute a command’s input or output to or from a file. A < symbol connects the command’s STDIN to the contents of an existing file. The > and >> symbols redirect STDOUT; > replaces the file’s existing contents, and >> appends to them. For example, the command
$ echo "This is a test message." > /tmp/mymessage
shell将<、>和》解釋成指令,用來把一條指令的輸入或者輸出重新定向到一個檔案。<這個符号把這條指令的STDIN和已有的某個檔案的内容聯系起來。符号>和》則重定向STDOUT;>會替換檔案的現有内容,而》則給檔案追加内容。例如,下面的指令
stores a single line in the file /tmp/mymessage, creating the file if necessary. The command below emails the contents of that file to user johndoe.
$ mail -s "Mail test" johndoe < /tmp/mymessage
在/tmp/mymessage這個檔案裡存入一行内容,如果必要,還會建立這個檔案。下面的指令把該檔案的内容用電子郵件發給使用者johndoe。
To redirect both STDOUT and STDERR to the same place, use the >& symbol. To
redirect STDERR only, use 2>.
為了把STDOUT和STDERR都重定向到同一個地方,可以用>&這個符号,僅僅重定向STDERR的話,則用2>。
The find command illustrates why you might want to handle STDOUT and
STDERR separately because it tends to produce output on both channels, especially when run as an unprivileged user. For example, a command such as
$ find / -name core
指令find示範了想要分開處理STDOUT和STDERR的原因,因為它會在兩個信道提供輸出,特别是以非特權使用者身份運作的時候。例如,像下面這條指令
usually results in so many “permission denied” error messages that genuine hits get lost in the clutter. To discard all the error messages, you can use
$ find / -name core 2> /dev/null
通常會導緻很多“permission denied”這樣的出錯資訊,進而把真正的結果淹沒在混亂的輸出裡了。要消除所有出錯消息,可以用這條指令
In this version, only real matches (where the user has read permission on the parent directory) come to the terminal window. To save the list of matching paths to a file, try
$ find / -name core > /tmp/corefiles 2> /dev/null
在這個版本的指令裡,隻有真正比對的結果(該使用者對父目錄有讀權的地方)才會出現在終端視窗中。要把比對路徑的清單儲存在一個檔案裡,可以試試下面的指令
This command line sends matching paths to /tmp/corefiles, discards errors, and sends nothing to the terminal window.
這一行指令把比對的路徑發到/tmpcorefile這個檔案,丢棄出錯消息,向終端視窗什麼都不發。
To connect the STDOUT of one command to the STDIN of another, use the | symbol, commonly known as a pipe. Some examples:
$ ps -ef | grep httpd
$ cut -d: -f7 < /etc/passwd | sort -u
要把一條指令的STDOUT連接配接到另一條指令的STDIN上,可以用|這個符号,它常叫做管道。下面是一些例子
The first example runs ps to generate a list of processes and pipes it through the grep command to select lines that contain the word httpd. The output of grep is not redirected, so the matching lines come to the terminal window.
第一個例子運作ps産生一份程序清單,由管道送給grep指令選出包含httpd這個詞的若幹行。grep指令的輸出沒有重定向,是以比對的結果都出現在終端視窗裡。
The cut command in the second example picks out the path to each user’s shell
from /etc/passwd. The list of shells is then sent through sort -u to produce a
sorted list of unique values.
第二個例子用cut指令從/etc/passwd檔案裡把每個使用者的shell的路徑選出來。接着,列出的shell的路徑都通過sort -u進行處理,産生的清單中,路徑名不但依次排序,且路徑名隻出現一次。
To execute a second command only if its precursor completes successfully, you
can separate the commands with an && symbol. For example,
$ lpr /tmp/t2 && rm /tmp/t2
要讓第二條指令隻有在第一條指令成功完成之後才執行,可以用一個&&符号把兩條指令隔開。例如
removes /tmp/t2 if and only if it is successfully queued for printing. Here, the success of the lpr command is defined as its yielding an exit code of zero, so the use of a symbol that suggests “logical AND” for this purpose may be confusing if you’re used to short-circuit evaluation in other programming languages. Don’t think about it too much; just accept it as a shell idiom.
這條指令當且僅當/tmp/t2成功送入列印隊列之後,才會删除/tmp/t2。在這裡,lpr指令産生的退出碼為0的話,就算它執行成功,是以,如果讀者已經習慣了其他程式設計語言中的“短路”計算,而這裡用一個表示“邏輯與”的符号,那麼就可能造成混亂。不要想得太多;僅僅把它當做一個shell的習慣用法就行了。
Conversely, the || symbol executes the following command only if the preceding
command fails (produces a nonzero exit status).
In a script, you can use a backslash to break a command onto multiple lines, helping to distinguish the error-handling code from the rest of the command pipeline:
cp --preserve --recursive /etc/* /spare/backup \
|| echo "Did NOT make backup"
For the converse effect—multiple commands combined onto one line—you can
use a semicolon as a statement separator.
相反,||這個符号表明,隻有前一條指令執行不成功(産生了一個非零的退出碼)時,才執行後面的腳本。
在一個腳本裡,可以用反斜線把一條指令分成多行來寫,進而把出錯處理代碼和指令管道的其他部分區分開來。
要實作相反的效果--将多條指令整合在一行裡--可以用分号作為語句分隔符。
本文轉自cix123 51CTO部落格,原文連結:http://blog.51cto.com/zhaodongwei/1870335,如需轉載請自行聯系原作者