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模式:
$ set -o vi
和在vi里一样,编辑操作也是有模式的;不过,一开始会进入输入模式。按ESC键开始离开输入模式,按“i”键重新进入输入模式。在编辑模式下“w”键向前进一个单词,“FX”在本行里找到下一个X,等等。用<ESC>k可以遍历过去输入的命令。想要再次回到emacs编辑代码,输入:
$ set -o 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;>会替换文件的现有内容,而》则给文件追加内容。例如,下面的命令
$ echo "This is a test message." > /tmp/mymessage
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。
$ mail -s "Mail test" johndoe < /tmp/mymessage
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的原因,因为它会在两个信道提供输出,特别是以非特权用户身份运行的时候。例如,像下面这条命令
$ find / -name core
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”这样的出错信息,从而把真正的结果淹没在混乱的输出里了。要消除所有出错消息,可以用这条命令
$ find / -name core 2> /dev/null
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
在这个版本的命令里,只有真正匹配的结果(该用户对父目录有读权的地方)才会出现在终端窗口中。要把匹配路径的清单保存在一个文件里,可以试试下面的命令
$ 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上,可以用|这个符号,它常叫做管道。下面是一些例子
$ ps -ef | grep httpd
$ cut -d: -f7 < /etc/passwd | sort -u
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
要让第二条命令只有在第一条命令成功完成之后才执行,可以用一个&&符号把两条命令隔开。例如
$ 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.
相反,||这个符号表明,只有前一条命令执行不成功(产生了一个非零的退出码)时,才执行后面的脚本。
在一个脚本里,可以用反斜线把一条命令分成多行来写,从而把出错处理代码和命令管道的其他部分区分开来。
cp --preserve --recursive /etc/* /spare/backup \
|| echo "Did NOT make backup"
要实现相反的效果--将多条命令整合在一行里--可以用分号作为语句分隔符。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。