Chapter 11. Introduction to Shell Scripts(第 11 章 Shell 脚本简介 Shell 脚本简介)

If you can enter commands into the shell, you can write shell scripts (also known as Bourne shell scripts). A shell script is a series of commands written in a file; the shell reads the commands from the file just as it would if you typed them into a terminal.

如果你能在 shell 中输入命令,你就能编写 shell 脚本(也称为 Bourne shell 脚本)。

shell 脚本是写在文件中的一系列命令;shell 会从文件中读取这些命令,就像在终端中输入命令一样。

11.1 Shell Script Basics(Shell 脚本基础)

Bourne shell scripts generally start with the following line, which indicates that the /bin/sh program should execute the commands in the script file. (Make sure that no whitespace appears at the beginning of the script file.)

Bourne shell 脚本一般以下面一行开始,表示 /bin/sh 程序应执行脚本文件中的命令。

(确保脚本文件开头没有空白)。

#!/bin/sh

The #! part is called a shebang; you’ll see it in other scripts in this book. You can list any commands that you want the shell to execute following the #!/bin/sh line. For example:

在本书的其他脚本中,你会看到 #! 你可以在 #!/bin/sh 行后列出任何希望 shell 执行的命令。

例如

#!/bin/sh
#
# Print something, then run ls
echo About to run the ls command.
ls

NOTE A # character at the beginning of a line indicates that the line is a comment; that is, the shell ignores anything on a line after a #. Use comments to explain parts of your scripts that are difficult to understand.

注意 一行开头的 # 字符表示该行是注释;也就是说,shell 会忽略 # 之后一行的任何内容。使用注释来解释脚本中难以理解的部分。

After creating a shell script and setting its permissions, you can run it by placing the script file in one of the directories in your command path and then running the script name on the command line. You can also run ./script if the script is located in your current working directory, or you can use the full pathname.

创建 shell 脚本并设置其权限后,将脚本文件放在命令路径下的某个目录中,然后在命令行上运行脚本名称,即可运行该脚本。

如果脚本位于当前工作目录下,也可以运行 ./script,或者使用完整路径名。

As with any program on Unix systems, you need to set the executable bit for a shell script file, but you must also set the read bit in order for the shell to read the file. The easiest way to do this is as follows:

与 Unix 系统上的任何程序一样,您需要为 shell 脚本文件设置可执行位,但同时也必须设置读取位,以便 shell 读取该文件。

最简单的方法如下:

$ chmod +rx script

This chmod command allows other users to read and execute script. If you don’t want that, use the absolute mode 700 instead (and refer to 2.17 File Modes and Permissions for a refresher on permissions).

该 chmod 命令允许其他用户读取和执行脚本。

如果不希望这样,请使用绝对模式 700(有关权限的复习,请参阅 2.17 文件模式和权限)。

With the basics behind us, let’s look at some of the limitations of shell scripts

有了基础知识,我们再来看看 shell 脚本的一些限制

11.1.1 Limitations of Shell Scripts

The Bourne shell manipulates commands and files with relative ease. In 2.14 Shell Input and Output, you saw the way the shell can redirect output, one of the important elements of shell script programming. However, the shell script is only one tool for Unix programming, and although scripts have considerable power, they also have limitations.

Bourne shell可以相对容易地操作命令和文件。

在2.14 Shell输入和输出中,您看到了shell可以重定向输出的方式,这是shell脚本编程的重要元素之一。

然而,shell脚本只是Unix编程的一种工具,虽然脚本具有相当的功能,但也有其局限性。

One of the main strengths of shell scripts is that they can simplify and automate tasks that you can otherwise perform at the shell prompt, like manipulating batches of files. But if you’re trying to pick apart strings, perform repeated arithmetic computations, or access complex databases, or if you want functions and complex control structures, you’re better off using a scripting language like Python, Perl, or awk, or perhaps even a compiled language like C. (This is important, so we’ll repeat it throughout the chapter.)

Shell脚本的主要优势之一是可以简化和自动化一些在shell提示符下进行的任务,比如批量操作文件。

但是,如果您要解析字符串、执行重复的算术计算、访问复杂的数据库,或者需要函数和复杂的控制结构,最好使用像Python、Perl或awk这样的脚本语言,或者甚至使用像C这样的编译语言。

(这一点很重要,我们将在本章中多次强调。)

Finally, be aware of your shell script sizes. Keep your shell scripts short. Bourne shell scripts aren’t meant to be big (though you will undoubtedly encounter some monstrosities)

最后,请注意您的shell脚本的大小。保持您的shell脚本简短。

Bourne shell脚本不适合编写庞大的脚本(尽管您可能会遇到一些庞然大物)。

11.2 Quoting and Literals(引用和字面意义)

One of the most confusing elements of working with the shell and scripts is when to use quotation marks (or quotes) and other punctuation, and why it’s sometimes necessary to do so. Let’s say you want to print the string $100 and you do the following:

在使用shell和脚本时,最令人困惑的元素之一就是何时使用引号(或引用符号)和其他标点符号,以及为什么有时候需要这样做。

假设你想打印字符串$100,并执行以下操作:

$ echo $100
00

Why did this print 00? Because the shell saw $1, which is a shell variable (we’ll cover it soon). So you might think that if you surround it with double quotes, the shell will leave the $1 alone. But it still doesn’t work:

为什么会打印出00?因为shell看到了$1,这是一个shell变量(我们很快会介绍它)。

所以你可能会认为,如果你用双引号把它括起来,shell会保持$1不变。但是它仍然不起作用:

$ echo "$100"
00

Then you ask a friend, who says that you need to use single quotes instead:

然后你向一个朋友求助,他告诉你需要使用单引号代替双引号:

$ echo '$100'
$100

Why did this particular incantation work?

为什么这个咒语会起作用?

11.2.1 Literals(文字)

When you use quotes, you’re often trying to create a literal, a string that you want the shell to pass to the command line untouched. In addition to the $ in the example that you just saw, other similar circumstances include when you want to pass a * character to a command such as grep instead of having the shell expand it, and when you need to need to use a semicolon (;) in a command.

当你使用引号时,通常是为了创建一个字面量,即一个你希望shell原样传递给命令行的字符串。

除了刚才你看到的示例中的$符号之外,其他类似的情况包括当你想要将*字符传递给grep命令而不是让shell扩展它时,以及当你需要在命令中使用分号(;)时。

When writing scripts and working on the command line, just remember what happens whenever the shell runs a command:

在编写脚本和在命令行上工作时,只需记住每当shell运行一个命令时会发生什么:

  1. Before running the command, the shell looks for variables, globs, and other substitutions and performs the substitutions if they appear.
  2. The shell passes the results of the substitutions to the command.
  3. 在运行命令之前,shell会查找变量、通配符和其他替换,并执行替换(如果有的话)。
  4. shell将替换的结果传递给命令。

Problems involving literals can be subtle. Let’s say you’re looking for all entries in /etc/passwd that match the regular expression r.*t (that is, a line that contains an r followed by a t later in the line, which would enable you to search for usernames such as root and ruth and robot). You can run this command:

涉及字面量的问题可能会很微妙。

假设你想要查找/etc/passwd中与正则表达式r.*t(即包含r后跟在行中后面的t的行,这将使你能够搜索用户名如rootruthrobot)匹配的所有条目。

你可以运行以下命令:

$ grep r.*t /etc/passwd

It works most of the time, but sometimes it mysteriously fails. Why? The answer is probably in your current directory. If that directory contains files with names such as r.input and r.output, then the shell expands r.*t to r.input r.output and creates this command:

大部分时间它都能正常工作,但有时候会莫名其妙地失败。

为什么?答案可能就在你当前的目录中。

如果该目录包含名为r.input和r.output的文件,那么shell会将r.*t扩展为r.input和r.output,并创建这个命令:

$ grep r.input r.output /etc/passwd

The key to avoiding problems like this is to first recognize the characters that can get you in trouble and then apply the correct kind of quotes to protect the characters

避免类似问题的关键是首先识别可能导致麻烦的字符,然后应用正确的引号来保护这些字符。

11.2.2 Single Quotes(单引号)

The easiest way to create a literal and make the shell leave a string alone is to enclose the entire string in single quotes, as in this example with grep and the * character:

创建一个字面量并使shell保持字符串不变的最简单方法是将整个字符串用单引号括起来,就像这个例子中使用grep和*字符一样:

$ grep 'r.*t' /etc/passwd

As far as the shell is concerned, all characters between the two single quotes, including spaces, make up a single parameter. Therefore, the following command does not work, because it asks the grep command to search for the string r.*t /etc/passwd in the standard input (because there’s only one parameter to grep):

对于shell来说,两个单引号之间的所有字符,包括空格,在逻辑上组成一个单一的参数。

因此,下面的命令不起作用,因为它要求grep命令在标准输入中搜索字符串 r.*t /etc/passwd(因为grep只有一个参数):

$ grep 'r.*t /etc/passwd'

When you need to use a literal, you should always turn to single quotes first, because you’re guaranteed that the shell won’t try any substitutions. As a result, it’s a generally clean syntax. However, sometimes you need a little more flexibility, so you can turn to double quotes.

当你需要使用字面量时,应该首先使用单引号,因为你可以确保shell不会尝试进行任何替换。

因此,这是一种通常干净的语法。然而,有时你需要更灵活一点,这时可以使用双引号。

11.2.3 Double Quotes(双引号)

Double quotes (") work just like single quotes, except that the shell expands any variables that appear within double quotes. You can see the difference by running the following command and then replacing the double quotes with single quotes and running it again.

双引号(")的作用与单引号相同,但 shell 会展开双引号内的任何变量。

运行下面的命令,然后用单引号替换双引号并再次运行,就能看出其中的区别。

$ echo "There is no * in my path: $PATH"

When you run the command, notice that the shell substitutes for $PATH but does not substitute for the *.

运行该命令时,请注意 shell 会替换 $PATH,但不会替换 *。

NOTE If you’re using double quotes when printing large amounts of text, consider using a here document, as described in 11.9 Here Documents.

注意 如果在打印大量文本时使用双引号,请考虑使用 here 文档,如 11.9 Here 文档中所述。

11.2.4 Passing a Literal Single Quote(通过一个字面的单引号)

One tricky part to using literals with the Bourne shell comes when passing a literal single quote to a command. One way to do this is to place a backslash before the single quote character:

在使用Bourne shell的文字字面量时,有一个棘手的问题,就是当将一个文字字面量单引号传递给一个命令时。解决这个问题的一种方法是在单引号字符之前加上反斜杠:

$ echo I don\'t like contractions inside shell scripts.

The backslash and quote must appear outside any pair of single quotes, and a string such as 'don\'t results in a syntax error. Oddly enough, you can enclose the single quote inside double quotes, as shown in the following example (the output is identical to that of the preceding command):

反斜杠和引号必须出现在任何一对单引号之外,而像'don\'t这样的字符串会导致语法错误。

奇怪的是,你可以将单引号放在双引号中,如下面的例子所示(输出与前面的命令完全相同):

$ echo "I don't like contractions inside shell scripts."

If you’re in a bind and you need a general rule to quote an entire string with no substitutions, follow this procedure:

如果你遇到困境,需要一个通用规则来引用一个没有替换的整个字符串,请按照以下步骤进行:

  1. Change all instances of ' (single quote) to '\'' (single quote, backslash, single quote, single quote).
  2. Enclose the entire string in single quotes.
  3. 将所有的'(单引号)更改为'\''(单引号、反斜杠、单引号、单引号)。
  4. 用单引号将整个字符串括起来。

Therefore, you can quote an awkward string such as this isn't a forward slash: \ as follows:

因此,你可以引用一个尴尬的字符串,比如this isn't a forward slash: \,如下所示:

NOTE It’s worth repeating that when you quote a string, the shell treats everything inside the quotes as a single parameter. Therefore, a b c counts as three parameters, but a "b c" is only two.

注意 重申一下,当你引用一个字符串时,shell将引号内的所有内容视为一个单独的参数。

因此,a b c算作三个参数,但a "b c"只算作两个。

11.3 Special Variables(特殊变量)

Most shell scripts understand command-line parameters and interact with the commands that they run. To take your scripts from being just a simple list of commands to becoming more flexible shell script programs, you need to know how to use the special Bourne shell variables. These special variables are like any other shell variable as described in 2.8 Environment and Shell Variables, except that you cannot change the values of certain ones.

大多数shell脚本都能理解命令行参数,并与其运行的命令进行交互。

要将脚本从仅仅是一系列简单命令的列表转变为更灵活的shell脚本程序,你需要了解如何使用特殊的Bourne shell变量。

这些特殊变量与2.8环境和shell变量中描述的其他shell变量类似,只是你不能更改某些变量的值。

NOTE After reading the next few sections, you’ll understand why shell scripts accumulate many special characters as they are written. If you’re trying to understand a shell script and you come across a line that looks completely incomprehensible, pick it apart piece by piece.

注意:阅读接下来的几节后,你将明白为什么编写的shell脚本会积累许多特殊字符。

如果你试图理解一个shell脚本,并且遇到一行看起来完全无法理解的代码,请逐个部分进行分析。

11.3.1 Individual Arguments: $1, $2, ...(个别论点:$1,$2,...)

$1, $2, and all variables named as positive nonzero integers contain the values of the script parameters, or arguments. For example, say the name of the following script is pshow:

$1、$2和所有以正非零整数命名的变量都包含脚本参数或参数的值。

例如,假设以下脚本的名称是pshow:

#!/bin/sh
echo First argument: $1
echo Third argument: $3

Try running the script as follows to see how it prints the arguments:

尝试按照以下方式运行脚本,查看它如何打印参数:

$ ./pshow one two three
First argument: one
Third argument: three

The built-in shell command shift can be used with argument variables to remove the first argument ($1) and advance the rest of the arguments forward. Specifically, $2 becomes $1,

内置的shell命令shift可以与参数变量一起使用,以删除第一个参数($1)并将其余参数向前移动。

具体来说,$2变为$1,$3变为$2,依此类推。

例如,假设以下脚本的名称是shiftex:$3 becomes $2, and so on. For example, assume that the name of the following script is shiftex:

#!/bin/sh
echo Argument: $1
shift
echo Argument: $1
shift
echo Argument: $1

Run it like this to see it work:

像这样运行,看看它是否有效:

$ ./shiftex one two three
Argument: one
Argument: two
Argument: three

As you can see, shiftex prints all three arguments by printing the first, shifting the remaining arguments, and repeating.

正如您所看到的,shiftex 通过打印第一个参数、移位其余参数和重复操作来打印所有三个参数。

11.3.2 Number of Arguments: $ # (参数数: $)

The $# variable holds the number of arguments passed to a script and is especially important when running shift in a loop to pick through arguments. When $# is 0, no arguments remain, so $1 is empty. (See 11.6 Loops for a description of loops.)

$# 变量用于保存传递给脚本的参数个数,在循环运行移位以选择参数时尤为重要。

当 $# 为 0 时,没有参数,因此 $1 为空(有关循环的描述,请参阅 11.6 循环)。

11.3.3 All Arguments: $@(所有参数: $@)

The $@ variable represents all of a script’s arguments, and it is very useful for passing them to a command inside the script. For example, Ghostscript commands (gs) are usually long and complicated. Suppose you want a shortcut for rasterizing a PostScript file at 150 dpi, using the standard output stream, while also leaving the door open for passing other options to gs. You could write a script like this to allow for additional command-line options:

$@ 变量代表脚本的所有参数,在将参数传递给脚本内部的命令时非常有用。

例如,Ghostscript 命令 (gs) 通常又长又复杂。

假设你需要一个快捷方式,使用标准输出流以 150 dpi 光栅化 PostScript 文件,同时还可以向 gs 传递其他选项。

你可以写这样一个脚本,以允许额外的命令行选项:

#!/bin/sh
gs -q -dBATCH -dNOPAUSE -dSAFER -sOutputFile=- -sDEVICE=pnmraw $@

NOTE If a line in your shell script gets too long for your text editor, you can split it up with a backslash ( ). For example, you can alter the preceding script as follows:

注意:如果你的Shell脚本中的一行过长,超过了你的文本编辑器的限制,你可以使用反斜杠(\)将其分割为多行。

例如,你可以按照以下方式修改上述脚本:

#!/bin/sh
gs -q -dBATCH -dNOPAUSE -dSAFER \
 -sOutputFile=- -sDEVICE=pnmraw $@

11.3.4 Script Name: $0(脚本名称: $0)

The $0 variable holds the name of the script, and it is useful for generating diagnostic messages. For example, say your script needs to report an invalid argument that is stored in the $BADPARM variable. You can print the diagnostic message with the following line so that the script name appears in the error message:

$0变量保存脚本的名称,对于生成诊断消息非常有用。例如,假设您的脚本需要报告存储在$BADPARM变量中的无效参数。

您可以使用以下代码行打印诊断消息,以便脚本名称出现在错误消息中:

echo $0: bad option $BADPARM

All diagnostic error messages should go to the standard error. Recall from 2.14.1 Standard Error that 2>&1 redirects the standard error to the standard output. For writing to the standard error, you can reverse the process with 1>&2. To do this for the preceding example, use this:

所有的诊断错误消息都应该发送到标准错误输出。

回想一下2.14.1标准错误输出,2>&1将标准错误重定向到标准输出。

如果要将内容写入标准错误输出,可以使用1>&2来进行反向处理。对于上述示例,可以使用以下方法实现:

echo $0: bad option $BADPARM 1>&2

11.3.5 Process ID: $ $(进程 ID: $ $)

The $ $ variable holds the process ID of the shell.

$ $ 变量表示 shell 的进程 ID。

11.3.6 Exit Code: $?( 退出代码: $?)

The $? variable holds the exit code of the last command that the shell executed. Exit codes, which are critical to mastering shell scripts, are discussed next.

变量 $? 保存着 shell 执行的最后一条命令的退出代码。

退出代码是掌握 shell 脚本的关键,我们将在下文中讨论。

11.4 Exit Codes(退出代码)

When a Unix program finishes, it leaves an exit code for the parent process that started the program. The exit code is a number and is sometimes called an error code or exit value. When the exit code is zero (0), it typically means that the program ran without a problem. However, if the program has an error, it usually exits with a number other than 0 (but not always, as you’ll see next).

当一个Unix程序运行结束时,它会为启动该程序的父进程留下一个退出码。

退出码是一个数字,有时被称为错误码或退出值。

当退出码为零(0)时,通常意味着程序运行正常,没有问题。

然而,如果程序出现错误,它通常会以一个非零的数字退出(但并不总是,稍后会看到)。

The shell holds the exit code of the last command in the $? special variable, so you can check it out at your shell prompt:

Shell会将最后一个命令的退出码保存在特殊变量$?中,所以你可以在shell提示符下查看它。

$ ls / > /dev/null
$ echo $?
0
$ ls /asdfasdf > /dev/null
ls: /asdfasdf: No such file or directory
$ echo $?
1

You can see that the successful command returned 0 and the unsuccessful command returned 1 (assuming, of course, that you don’t have a directory named /asdfasdf on your system).

您可以看到,成功的命令返回0,而失败的命令返回1(当然,假设您的系统上没有名为/asdfasdf的目录)。

If you intend to use the exit code of a command, you must use or store the code immediately after running the command. For example, if you run echo $? twice in a row, the output of the second command is always 0 because the first echo command completes successfully.

如果您打算使用命令的退出代码,必须在运行命令后立即使用或存储该代码。

例如,如果连续两次运行echo $?,则第二个命令的输出始终为0,因为第一个echo命令成功完成。

When writing shell code that aborts a script abnormally, use something like exit 1 to pass an exit code of 1 back to whatever parent process ran the script. (You may want to use different numbers for different conditions.)

在编写异常中止脚本的shell代码时,请使用类似exit 1的方式将退出代码1传递回运行脚本的父进程。(您可能需要为不同的条件使用不同的数字。)

One thing to note is that some programs like diff and grep use nonzero exit codes to indicate normal conditions. For example, grep returns 0 if it finds something matching a pattern and 1 if it doesn’t. For these programs, an exit code of 1 is not an error; grep and diff use the exit code 2 for real problems. If you think a program is using a nonzero exit code to indicate success, read its manual page. The exit codes are usually explained in the EXIT VALUE or DIAGNOSTICS section.

需要注意的一点是,一些程序(如diff和grep)使用非零的退出代码来指示正常情况。

例如,如果grep找到与模式匹配的内容,则返回0,如果没有找到,则返回1。

对于这些程序,退出代码为1并不表示错误;grep和diff在真正出现问题时使用退出代码2。

如果您认为某个程序使用非零的退出代码表示成功,请阅读其手册页。

退出代码通常在“EXIT VALUE”或“DIAGNOSTICS”部分进行解释。

11.5 Conditionals(条件式)

The Bourne shell has special constructs for conditionals, such as if/then/ else and case statements. For example, this simple script with an if conditional checks to see whether the script’s first argument is hi:

Bourne shell 具有特殊的条件构造,如 if/then/ else 和 case 语句。

例如,这个带有 if 条件的简单脚本会检查脚本的第一个参数是否为 hi:

#!/bin/sh
if [ $1 = hi ]; then
 echo 'The first argument was "hi"'
else
 echo -n 'The first argument was not "hi" -- '
 echo It was '"'$1'"'
fi

The words if, then, else, and fi in the preceding script are shell keywords; everything else is a command. This distinction is extremely important because one of the commands is [ $ 1 = "hi" ] and the [ character is an actual program on a Unix system, not special shell syntax. (This is actually not quite true, as you’ll soon learn, but treat it as a separate command in your head for now.) All Unix systems have a command called [ that performs tests for shell script conditionals. This program is also known as test and careful examination of [ and test should reveal that they share an inode, or that one is a symbolic link to the other. Understanding the exit codes in 11.4 Exit Codes is vital, because this is how the whole process works:

在上述脚本中,if、then、else和fi是shell的关键字;其他所有内容都是命令。

这个区别非常重要,因为其中一个命令是[ $ 1 = "hi" ],而[字符是Unix系统上的一个实际程序,而不是特殊的shell语法。

(实际上,这并不完全正确,但暂时将其视为一个独立的命令)。所有的Unix系统都有一个名为[的命令,用于执行shell脚本条件测试。

这个程序也被称为test,仔细检查[和test应该会发现它们共享一个inode,或者一个是另一个的符号链接。

理解11.4退出码中的退出码非常重要,因为这是整个过程的工作原理:

  1. The shell runs the command after the if keyword and collects the exit code of that command.
  2. If the exit code is 0, the shell executes the commands that follow the then keyword, stopping when it reaches an else or fi keyword.
  3. If the exit code is not 0 and there is an else clause, the shell runs the commands after the else keyword.
  4. The conditional ends at fi.
  5. Shell运行if关键字后面的命令,并收集该命令的退出码。
  6. 如果退出码为0,则Shell执行紧随then关键字之后的命令,当遇到else或fi关键字时停止。
  7. 如果退出码不为0且存在else子句,则Shell运行else关键字之后的命令。
  8. 条件以fi结束。

11.5.1 Getting Around Empty Parameter Lists(绕过空参数列表)

There is a slight problem with the conditional in the preceding example due to a very common mistake: $1 could be empty, because the user might not enter a parameter. Without a parameter, the test reads [ = hi ], and the [ command aborts with an error. You can fix this by enclosing the parameter in quotes in one of two ways (both of which are common):

上例中的条件有一个小问题,这是一个很常见的错误:1 美元可能是空的,因为用户可能没有输入参数。在没有参数的情况下,测试读数为 [ = hi ],[ 命令会出错终止。有两种方法可以解决这个问题,一种是将参数用引号括起来(这两种方法都很常见):

if [ "$1" = hi ]; then
if [ x"$1" = x"hi" ]; then

11.5.2 Using Other Commands for Tests(使用其他命令进行测试)

The stuff following if is always a command. Therefore, if you want to put the then keyword on the same line, you need a semicolon (;) after the test command. If you skip the semicolon, the shell passes then as a parameter to the test command. (If you don’t like the semicolon, you can put the then keyword on a separate line.)

if 后面的内容总是命令。

因此,如果要将 then 关键字放在同一行,需要在测试命令后加上分号(;)。

如果省略分号,shell 会将 then 作为参数传递给测试命令。

(如果不喜欢分号,可以将 then 关键字放在单独一行)。

There are many possibilities for using other commands instead of the [ command. Here’s an example that uses grep:

使用其他命令代替 [ 命令的可能性很多。下面是一个使用 grep 的例子:

#!/bin/sh
if grep -q daemon /etc/passwd; then
 echo The daemon user is in the passwd file.
else
 echo There is a big problem. daemon is not in the passwd file.
fi

11.5.3 elif

There is also an elif keyword that lets you string if conditionals together, as shown below. But don’t get too carried away with elif, because the case construct that you’ll see in 11.5.6 Matching Strings with case is often more appropriate

还有一个 elif 关键字,可以将 if 条件串联起来,如下所示。

但不要太迷恋 elif,因为在 11.5.6 使用大小写匹配字符串中使用的大小写结构往往更合适

#!/bin/sh
if [ "$1" = "hi" ]; then
 echo 'The first argument was "hi"'
elif [ "$2" = "bye" ]; then
 echo 'The second argument was "bye"'
else
 echo -n 'The first argument was not "hi" and the second was not "bye"-
- '
 echo They were '"'$1'"' and '"'$2'"'
fi

11.5.4 && and || Logical Constructs(&& 和 || 逻辑结构)

There are two quick one-line conditional constructs that you may see from time to time: && (“and”) and || (“or”). The && construct works like this:

有两种快速的一行条件结构,你可能会偶尔看到:&&(“和”)和||(“或”)。&&结构的工作方式如下:

command1 && command2

Here, the shell runs command1, and if the exit code is 0, the shell also runs command2. The || construct is similar; if the command before a || returns a nonzero exit code, the shell runs the second command

在这里,shell运行command1,如果退出代码为0,则shell还会运行command2。

||结构类似;如果||之前的命令返回非零退出代码,则shell运行第二个命令。

The constructs && and || often find their way into use in if tests, and in both cases, the exit code of the last command run determines how the shell processes the conditional. In the case of the && construct, if the first command fails, the shell uses its exit code for the if statement, but if the first command succeeds, the shell uses the exit code of the second command for the conditional. In the case of the || construct, the shell uses the exit code of the first command if successful, or the exit code of the second if the first is unsuccessful. For example:

&&和||结构经常被用于if测试中,在两种情况下,最后一个运行的命令的退出代码决定了shell如何处理条件。

对于&&结构,如果第一个命令失败,shell使用它的退出代码作为if语句的条件,但如果第一个命令成功,shell使用第二个命令的退出代码作为条件。

对于||结构,如果第一个命令成功,shell使用第一个命令的退出代码,如果第一个命令失败,则使用第二个命令的退出代码。

例如:

#!/bin/sh
if [ "$1" = hi ] || [ "$1" = bye ]; then
 echo 'The first argument was "'$1'"'
fi

If your conditionals include the test ([) command, as shown here, you can use -a and -o instead of && and ||, as described in the next section

如果你的条件语句中包含测试命令([),如下所示,你可以使用-a和-o代替&&和||,如下一节所述。

11.5.5 Testing Conditions(测试条件)

You’ve seen how [ works: The exit code is 0 if the test is true and nonzero when the test fails. You also know how to test string equality with [ str1 = str2 ]. However, remember that shell scripts are well suited to operations on entire files because the most useful [ tests involve file properties. For example, the following line checks whether file is a regular file (not a directory or special file):

你已经看到了[的工作方式:如果测试为真,则退出代码为0,当测试失败时退出代码为非零。

你也知道如何使用[ str1 = str2 ]来测试字符串的相等性。

然而,请记住,Shell脚本非常适合对整个文件进行操作,因为最有用的[测试涉及文件属性。

例如,以下行检查文件是否为普通文件(而不是目录或特殊文件):

[ -f file ]

In a script, you might see the -f test in a loop similar to this next one, which tests all of the items in the current working directory (you’ll learn more about loops in general shortly):

在脚本中,你可能会在类似下图的循环中看到 -f 测试,该循环测试当前工作目录中的所有项目(稍后你将了解更多关于循环的一般知识):

for filename in *; do
 if [ -f $filename ]; then
 ls -l $filename
 file $filename
 else
 echo $filename is not a regular file.
 fi
done

You can invert a test by placing the ! operator before the test arguments. For example, [ ! -f file ] returns true if file is not a regular file. Furthermore, the -a and -o flags are the logical “and” and “or” operators (for example, [ -f file1 -a file2 ]).

你可以通过在测试参数之前放置!运算符来反转一个测试。

例如,[!-f文件]如果文件不是一个普通文件,则返回true。

此外,-a和-o标志是逻辑的“与”和“或”运算符(例如,[-f文件1 -a文件2])。

NOTE Because the test command is so widely used in scripts, many versions of the Bourne shell (including bash) incorporate the test command as a built-in. This can speed up scripts because the shell doesn’t have to run a separate command for each test.

注意因为测试命令在脚本中被广泛使用,许多版本的Bourne shell(包括bash)将测试命令作为内置命令。

这可以加快脚本的运行,因为shell不必为每个测试运行一个单独的命令。

There are dozens of test operations, all of which fall into three general categories: file tests, string tests, and arithmetic tests. The info manual contains complete online documentation, but the test(1) manual page is a fast reference. The following sections outline the main tests. (I’ve omitted some of the less common ones.)

有数十种测试操作,全部分为三个一般类别:文件测试、字符串测试和算术测试。

info手册包含完整的在线文档,但test(1)手册页是一个快速参考。

下面的部分概述了主要的测试(我省略了一些不常见的)。

File Tests(文件测试)

Most file tests, like -f, are called unary operations because they require only one argument: the file to test. For example, here are two important file tests:

大多数文件测试,如-f,被称为一元操作,因为它们只需要一个参数:要测试的文件。例如,这里有两个重要的文件测试:

o -e Returns true if a file exists
o -s Returns true if a file is not empty

o -e 如果文件存在则返回真
o -s 如果文件不为空则返回真

Several operations inspect a file’s type, meaning that they can determine whether something is a regular file, a directory, or some kind of special device, as listed in Table 11-1. There are also a number of unary operations that check a file’s permissions, as listed in Table 11-2. (See 2.17 File Modes and Permissions for an overview of permissions.)

几个操作可以检查文件的类型,即它们可以确定某个文件是普通文件、目录还是某种特殊设备,如表11-1所示。

还有一些一元操作可以检查文件的权限,如表11-2所示。

(有关权限的概述,请参见2.17文件模式和权限。)

Table 11-1. File Type Operators
image.png

NOTE The test command follows symbolic links (except for the -h test). That is, if link is a symbolic link to a regular file, [ -f link ] returns an exit code of true (0).

注意 测试命令会跟踪符号链接(-h 测试除外)。

也就是说,如果 link 是指向常规文件的符号链接,[ -f link ] 返回的退出代码为 true (0)。

Table 11-2. File Permissions Operators

表 11-2. 文件权限操作符

Table 11-2. File Permissions Operators

Finally, three binary operators (tests that need two files as arguments) are used in file tests, but they’re not terribly common. Consider this command that includes -nt (newer than):

最后,文件测试中还会用到三个二进制运算符(需要两个文件作为参数的测试),但它们并不常见。请看这条包含 -nt(新于)的命令:

[ file1 -nt file2 ]

This exits true if file1 has a newer modification date than file2. The -ot (older than) operator does the opposite. And if you need to detect identical hard links, -ef compares two files and returns true if they share inode numbers and devices.

如果文件 1 的修改日期比文件 2 新,则退出为 true。而 -ot(比旧)操作符的作用正好相反。

如果需要检测相同的硬链接,-ef 会比较两个文件,如果它们共享 inode 编号和设备,则返回 true。

String Tests(字符串测试)

You’ve seen the binary string operator = that returns true if its operands are equal. The != operator returns true if its operands are not equal. And there are two unary string operations:

我们已经见过二进制字符串运算符 =,如果其操作数相等,则返回 true。

如果操作数不相等,则 != 运算符返回 true。还有两个一元字符串运算:

o -z Returns true if its argument is empty ([ -z "" ] returns 0)

o -n Returns true if its argument is not empty ([ -n "" ] returns 1)

o -z 如果参数为空,则返回 true([ -z "" ] 返回 0)

o -n 如果参数不为空,则返回 true([ -n "" ]返回 1)

Arithmetic Tests(算术测试)

It’s important to recognize that the equal sign (=) looks for string equality, not numeric equality. Therefore, [ 1 = 1 ] returns 0 (true), but [ 01 = 1 ] returns false. When working with numbers, use -eq instead of the equal sign: [ 01 -eq 1 ] returns true. Table 11-3 provides the full list of numeric comparison operators.

重要的是要认识到,等号(=)查找的是字符串是否相等,而不是数字是否相等。

因此,[ 1 = 1 ] 返回 0(真),但 [ 01 = 1 ] 返回假。处理数字时,使用 -eq 代替等号:[ 01 -eq 1 ] 返回 true。

表 11-3 列出了数字比较运算符的完整列表。

Table 11-3. Arithmetic Comparison Operators

表 11-3. 算术比较运算符

Table 11-3. Arithmetic Comparison Operators

11.5.6 Matching Strings with case(带琴盒的配套琴弦)

The case keyword forms another conditional construct that is exceptionally useful for matching strings. The case conditional does not execute any test commands and therefore does not evaluate exit codes. However, it can do pattern matching. This example should tell most of the story:

case 关键字构成了另一种条件结构,对于匹配字符串特别有用。

case 条件不执行任何测试命令,因此不会评估退出代码。

不过,它可以进行模式匹配。

这个示例应该可以说明大部分问题:

#!/bin/sh
case $1 in
 bye)
 echo Fine, bye.
 ;;
 hi|hello)
 echo Nice to see you.
 ;;
 what*)
 echo Whatever.
 ;;
 *)
 echo 'Huh?'
 ;;
esac

The shell executes this as follows:

shell按以下方式执行此脚本:

  1. The script matches $1 against each case value demarcated with the ) character.
  2. If a case value matches $1, the shell executes the commands below the case until it encounters ;;, at which point it skips to the esac keyword.
  3. The conditional ends with esac.
  4. 脚本将$1与用字符)分隔的每个case值进行匹配。
  5. 如果一个case值与$1匹配,shell将执行case下面的命令,直到遇到;;,然后跳转到esac关键字。
  6. 条件以esac结束。

For each case value, you can match a single string (like bye in the preceding example) or multiple strings with | (hi|hello returns true if $1 equals hi or hello), or you can use the or ? patterns (what). To make a default case that catches all possible values other than the case values specified, use a single * as shown by the final case in the preceding example.

对于每个case值,您可以匹配单个字符串(如前面示例中的bye),也可以使用|匹配多个字符串(如果$1等于hi或hello,则hi|hello返回true),或者您可以使用或?模式(what)。

要创建一个默认情况,捕获除指定的case值之外的所有可能值,请使用单个*,如前面示例中的最后一个case所示。

NOTE Each case must end with a double semicolon (;;) or you risk a syntax error.

注意:每个case必须以两个分号(;;)结尾,否则可能会出现语法错误。

11.6 Loop(循环)

There are two kinds of loops in the Bourne shell: for and while loops.

Bourne shell 有两种循环:for 循环和 while 循环。

11.6.1 for Loops(for 循环)

The for loop (which is a “for each” loop) is the most common. Here’s an example:

for 循环(即 "for each "循环)是最常见的循环。下面是一个例子:

#!/bin/sh
for str in one two three four; do
 echo $str
done

In this listing, for, in, do, and done are all shell keywords. The shell does the following:

在这个列表中,for、in、do和done都是shell关键字。Shell执行以下操作:

  1. Sets the variable str to the first of the four space-delimited values following the in keyword (one).
  2. Runs the echo command between the do and done.
  3. Goes back to the for line, setting str to the next value (two), runs the commands between do and done, and repeats the process until it’s through with the values following the in keyword.
  4. 将变量str设置为in关键字后的四个以空格分隔的值中的第一个值(one)。
  5. 在do和done之间运行echo命令。
  6. 返回到for行,将str设置为下一个值(two),在do和done之间运行命令,并重复该过程,直到处理完in关键字后的所有值。

The output of this script looks like this:

此脚本的输出如下:

one
two
three
four

11.6.2 while Loops(while 循环)

The Bourne shell’s while loop uses exit codes, like the if conditional. For example, this script does 10 iterations:

Bourne shell 的 while 循环使用退出代码,就像 if 条件一样。例如,此脚本进行了 10 次迭代:

#!/bin/sh
FILE=/tmp/whiletest.$$;
echo firstline > $FILE
while tail -10 $FILE | grep -q firstline; do
 # add lines to $FILE until tail -10 $FILE no longer prints "firstline"
 echo -n Number of lines in $FILE:' '
 wc -l $FILE | awk '{print $1}'
 echo newline >> $FILE
done
rm -f $FILE

Here, the exit code of grep -q firstline is the test. As soon as the exit code is nonzero (in this case, when the string firstline no longer appears in the last 10 lines in $FILE), the loop exits.

在这里,grep -q firstline的退出代码是测试的结果。

一旦退出代码为非零值(在这种情况下,当字符串firstline不再出现在$FILE的最后10行中时),循环就会退出。

You can break out of a while loop with the break statement. The Bourne shell also has an until loop that works just like while, except that it breaks the loop when it encounters a zero exit code rather than a nonzero exit code. This said, you shouldn’t need to use the while and until loops very often. In fact, if you find that you need to use while, you should probably be using a language like awk or Python instead.

你可以使用break语句跳出while循环。

Bourne shell还有一个until循环,它的工作方式与while相同,只是当遇到零退出代码时会终止循环,而不是非零退出代码。

尽管如此,你通常不需要经常使用while和until循环。

实际上,如果你发现需要使用while,你可能应该使用像awk或Python这样的编程语言。

11.7 Command Substitution(指令替换)

The Bourne shell can redirect a command’s standard output back to the shell’s own command line. That is, you can use a command’s output as an argument to another command, or you can store the command output in a shell variable by enclosing a command in $().

Bourne shell可以将命令的标准输出重定向回shell的命令行。也就是说,您可以将一个命令的输出作为另一个命令的参数使用,或者可以通过将命令放在$()中来将命令的输出存储在shell变量中。

This example stores a command inside the FLAGS variable. The bold in the second line shows the command substitution

这个例子将一个命令存储在FLAGS变量中。第二行中的粗体显示了命令替换。

#!/bin/sh
FLAGS=$(grep ^flags /proc/cpuinfo | sed 's/.*://' | head -1)
echo Your processor supports:
for f in $FLAGS; do
 case $f in
 fpu) MSG="floating point unit"
 ;;
 3dnow) MSG="3DNOW graphics extensions"
 ;;
 mtrr) MSG="memory type range register"
 ;;
 *) MSG="unknown"
 ;;
 esac
 echo $f: $MSG
done

This example is somewhat complicated because it demonstrates that you can use both single quotes and pipelines inside the command substitution. The result of the grep command is sent to the sed command (more about sed in 11.10.3 sed), which removes anything matching the expression .*:, and the result of sed is passed to head.

这个例子有些复杂,因为它展示了在命令替换中可以同时使用单引号和管道符号。

grep命令的结果被发送到sed命令(关于sed的更多信息请参见11.10.3节),sed命令删除与表达式.*:匹配的内容,sed命令的结果被传递给head命令。

It’s easy to go overboard with command substitution. For example, don’t use $(ls) in a script because using the shell to expand * is faster. Also, if you want to invoke a command on several filenames that you get as a result of a find command, consider using a pipeline to xargs rather than command substitution, or use the -exec option (see 11.10.4 xargs).

在命令替换中很容易过度使用。

例如,在脚本中不要使用$(ls),因为使用shell来展开*会更快。

此外,如果你想对通过find命令获取的多个文件名执行一个命令,考虑使用管道传递给xargs而不是命令替换,或者使用-exec选项(参见11.10.4节xargs)。

NOTE The traditional syntax for command substitution is to enclose the command in back-ticks (``), and you’ll see this in many shell scripts. The $() syntax is a newer form, but it is a POSIX standard and is generally easier to read and write

注意:命令替换的传统语法是用反引号(``)将命令括起来,在许多shell脚本中可以看到这种写法。

$()语法是一种较新的形式,但它是POSIX标准,通常更易于阅读和编写。

11.8 Temporary File Management(临时文件管理)

It’s sometimes necessary to create a temporary file to collect output for use by a later command. When making such a file, make sure that the filename is distinct enough that no other programs will accidentally write to it. Here’s how to use the mktemp command to create temporary filenames. This script shows you the device interrupts that have occurred in the last two seconds:

有时候需要创建一个临时文件来收集输出,以供稍后的命令使用。

在创建这样的文件时,确保文件名足够独特,以免其他程序意外写入。

下面是使用mktemp命令创建临时文件名的方法。这个脚本会显示过去两秒钟内发生的设备中断情况。

#!/bin/sh
TMPFILE1=$(mktemp /tmp/im1.XXXXXX)
TMPFILE2=$(mktemp /tmp/im2.XXXXXX)
cat /proc/interrupts > $TMPFILE1
sleep 2
cat /proc/interrupts > $TMPFILE2
diff $TMPFILE1 $TMPFILE2
rm -f $TMPFILE1 $TMPFILE2

The argument to mktemp is a template. The mktemp command converts the XXXXXX to a unique set of characters and creates an empty file with that name. Notice that this script uses variable names to store the filenames so that you only have to change one line if you want to change a filename.

mktemp命令的参数是一个模板。

mktemp命令将XXXXXX转换为一组唯一的字符,并创建一个以该名称命名的空文件。

请注意,此脚本使用变量名称来存储文件名,这样如果您想更改文件名,只需更改一行即可。

NOTE Not all Unix flavors come with mktemp. If you’re having portability problems, it’s best to install the GNU coreutils package for your operating system.

注意,并非所有的Unix版本都带有mktemp。如果您遇到可移植性问题,最好为您的操作系统安装GNU coreutils软件包。

Another problem with scripts that employ temporary files is that if the script is aborted, the temporary files could be left behind. In the preceding example, pressing CTRL-C before the second cat command leaves a temporary file in /tmp. Avoid this if possible. Instead, use the trap command to create a signal handler to catch the signal that CTRL-C generates and remove the temporary files, as in this handler:

使用临时文件的另一个问题是,如果脚本被中止,临时文件可能会被遗留下来。

在上面的示例中,如果在第二个cat命令之前按下CTRL-C,则会在/tmp目录中留下一个临时文件。

如果可能的话,避免这种情况。

而是使用trap命令创建一个信号处理程序来捕获CTRL-C生成的信号并删除临时文件,如下所示的处理程序:

#!/bin/sh
TMPFILE1=$(mktemp /tmp/im1.XXXXXX)
TMPFILE2=$(mktemp /tmp/im2.XXXXXX)
trap "rm -f $TMPFILE1 $TMPFILE2; exit 1" INT
--snip--

You must use exit in the handler to explicitly end script execution, or the shell will continue running as usual after running the signal handler

必须在处理程序中使用 exit 来明确结束脚本的执行,否则 shell 将在运行信号处理程序后继续照常运行

NOTE You don’t need to supply an argument to mktemp; if you don’t, the template will begin with a /tmp/tmp. prefix.

如果不提供参数,模板将以 /tmp/tmp. 前缀开始。

11.9 Here Documents(文档嵌入)

Say you want to print a large section of text or feed a lot of text to another command. Rather than use several echo commands, you can use the shell’s here document feature, as shown in the following script:

假设你想要打印一大段文本或者将大量文本传递给另一个命令。与其使用多个echo命令,你可以使用shell的here document特性,如下所示的脚本:

#!/bin/sh
DATE=$(date)
cat <<EOF
Date: $DATE
The output above is from the Unix date command.
It's not a very interesting command.
EOF

The items in bold control the here document. The \< <EOF tells the shell to redirect all lines that follow the
standard input of the command that precedes \< <EOF, which in this case is cat. The redirection stops as soon as the EOF marker occurs on a line by itself. The marker can actually be any string, but remember to use the same marker at the beginning and end of the here document. Also, convention dictates that the marker be in all uppercase letters.

加粗的项目控制着这个文档。\< <EOF告诉shell将跟在\< <EOF之后的所有行重定向到前面命令的标准输入中,这里的命令是cat。

当独立的一行中出现EOF标记时,重定向停止。

实际上,标记可以是任何字符串,但记得在here document的开头和结尾使用相同的标记。

此外,约定俗成的是使用全部大写字母来表示标记。

Notice the shell variable $DATE in the here document. The shell expands shell variables inside here documents, which is especially useful when you’re printing out reports that contain many variables

请注意here document中的shell变量$DATE。shell会在here document中扩展shell变量,这在打印包含多个变量的报告时特别有用。

11.10 Important Shell Script Utilities(重要的 Shell 脚本实用程序)

Several programs are particularly useful in shell scripts. Certain utilities such as basename are really only practical when used with other programs, and therefore don’t often find a place outside shell scripts. However, others such as awk can be quite useful on the command line, too.

有几个程序在 shell 脚本中特别有用。

某些实用程序(如 basename)只有在与其他程序一起使用时才真正实用,因此在 shell 脚本之外并不常见。

不过,awk 等其他程序在命令行中也非常有用。

11.10.1 basename(简称)

If you need to strip the extension from a filename or get rid of the directories in a full pathname, use the basename command. Try these examples on the command line to see how the command works:

如果你需要从文件名中去掉扩展名或者去掉完整路径中的目录,可以使用basename命令。

在命令行上尝试以下示例,以了解该命令的工作原理:

$ basename example.html .html
$ basename /usr/local/bin/example

In both cases, basename returns example. The first command strips the .html suffix from example.html, and the second removes the directories from the full pathname.

在这两种情况下,basename返回的结果都是example。第一个命令从example.html中去掉了.html后缀,第二个命令则删除了完整路径中的目录。

This example shows how you can use basename in a script to convert GIF image files to the PNG format:

这个示例展示了如何在脚本中使用basename将GIF图像文件转换为PNG格式:

#!/bin/sh
for file in *.gif; do
 # exit if there are no files
 if [ ! -f $file ]; then
 exit
 fi
 b=$(basename $file .gif)
 echo Converting $b.gif to $b.png...
 giftopnm $b.gif | pnmtopng > $b.png
done

11.10.2 awk

The awk command is not a simple single-purpose command; it’s actually a powerful programming language. Unfortunately, awk usage is now something of a lost art, having been replaced by larger languages such as Python.

awk命令不是一个简单的单一用途的命令;它实际上是一种强大的编程语言。

不幸的是,awk的使用现在已经成为一种被更大的编程语言如Python所取代的失传技艺。

The are entire books on the subject of awk, including The AWK Programming Language by Alfred V. Aho, Brian W. Kernighan, and Peter J. Weinberger (Addison-Wesley, 1988). This said, many, many people use awk to do one thing—to pick a single field out of an input stream like this:

关于awk有很多专著,包括Alfred V. Aho、Brian W. Kernighan和Peter J. Weinberger所著的《AWK程序设计语言》(Addison-Wesley,1988年)。

话虽如此,很多人使用awk只是为了做一件事——从输入流中选择一个字段,就像这样:

$ ls -l | awk '{print $5}'

This command prints the fifth field of the ls output (the file size). The result is a list of file sizes.

这个命令打印出ls命令输出的第五个字段(文件大小)。结果是一个文件大小的列表。

11.10.3 sed

The sed program (sed stands for stream editor) is an automatic text editor that takes an input stream (a file or the standard input), alters it according to some expression, and prints the results to standard output. In many respects, sed is like ed, the original Unix text editor. It has dozens of operations, matching tools, and addressing capabilities. As with awk, entire books have been written about sed including a quick reference covering both, sed & awk Pocket Reference, 2nd edition, by Arnold Robbins (O’Reilly, 2002).

sed程序(sed代表流编辑器)是一种自动文本编辑器,它接受输入流(文件或标准输入),根据某些表达式进行修改,并将结果打印到标准输出。

在许多方面,sed与原始的Unix文本编辑器ed相似。

它具有数十种操作、匹配工具和定位功能。就像awk一样,有关sed的整本书已经出版,其中包括一份快速参考,涵盖了sed和awk两者的《sed & awk Pocket Reference, 2nd edition》(Arnold Robbins著,O'Reilly出版社,2002年)。

Although sed is a big program, and an in-depth analysis is beyond the scope of this book, it’s easy to see how it works. In general, sed takes an address and an operation as one argument. The address is a set of lines, and the command determines what to do with the lines.

虽然sed是一个庞大的程序,深入分析超出了本书的范围,但很容易看出它的工作原理。

一般来说,sed将地址和操作作为一个参数。地址是一组行,命令决定如何处理这些行。

A very common task for sed is to substitute some text for a regular expression (see 2.5.1 grep), like this:

sed的一个非常常见的任务是用正则表达式替换一些文本(参见2.5.1 grep),如下所示:


$ sed 's/exp/text/'

So if you wanted to replace the first colon in /etc/passwd with a % and send the result to the standard output,
you’d do it like this:

如果你想要用一个百分号替换掉/etc/passwd文件中的第一个冒号,并将结果发送到标准输出,你可以这样做:


$ sed 's/:/%/' /etc/passwd

To substitute all colons in /etc/passwd, add a g modifier to the end of the operation, like this:

如果想要替换/etc/passwd文件中的所有冒号,请在操作结尾处添加g修饰符,像这样:

$ sed 's/:/%/g' /etc/passwd

Here’s a command that operates on a per-line basis; it reads /etc/passwd and deletes lines three through six and sends the result to the standard output:

下面是一个按行操作的命令示例;它读取/etc/passwd文件并删除第三到第六行,并将结果发送到标准输出:

$ sed 3,6d /etc/passwd

In this example, 3,6 is the address (a range of lines), and d is the operation (delete). If you omit the address,

在本例中,3,6 是地址(行的范围),d 是操作(删除)。如果省略地址

sed operates on all lines in its input stream. The two most common sed operations are probably s (search and replace) and d.

sed 会对输入流中的所有行进行操作。最常见的两种 sed 操作可能是 s(搜索和替换)和 d。

You can also use a regular expression as the address. This command deletes any line that matches the regular expression exp:

也可以使用正则表达式作为地址。该命令会删除任何与正则表达式 exp 匹配的行:

$ sed '/exp/d'

11.10.4 xargs

When you have to run one command on a huge number of files, the command or shell may respond that it can’t fit all of the arguments in its buffer. Use xargs to get around this problem by running a command on each filename in its standard input stream.

当你必须在大量文件上运行一条命令时,命令或 shell 可能会回应说,它的缓冲区无法容纳所有参数。

使用 xargs 可以解决这个问题,它可以在标准输入流中的每个文件名上运行一条命令。

Many people use xargs with the find command. For example, the script below can help you verify that every file in the current directory tree that ends with .gif is actually a GIF (Graphic Interchange Format) image:

许多人将 xargs 与查找命令一起使用。

例如,下面的脚本可以帮助你确认当前目录树中以 .gif 结尾的每个文件实际上都是 GIF(图形交换格式)图像:

$ find . -name '*.gif' -print | xargs file

In the example above, xargs runs the file command. However, this invocation can cause errors or leave your system open to security problems, because filenames can include spaces and newlines. When writing a script, use the following form instead, which changes the find output separator and the xargs argument delimiter from a newline to a NULL character:

在上例中,xargs 运行文件命令。

然而,由于文件名可能包含空格和换行符,这种调用方式可能会导致错误或给系统带来安全问题。

在编写脚本时,请使用以下形式,将查找输出分隔符和 xargs 参数分隔符从换行符改为 NULL 字符:

$ find . -name '*.gif' -print0 | xargs -0 file

xargs starts a lot of processes, so don’t expect great performance if you have a large list of files.

xargs启动了很多进程,所以如果你有一个很长的文件列表,不要指望有很好的性能。

You may need to add two dashes (--) to the end of your xargs command if there is a chance that any of the target files start with a single dash (-). The double dash (--) can be used to tell a program that any arguments that follow the double dash are filenames, not options. However, keep in mind that not all programs support the use of a double dash.

如果目标文件中有以单个破折号(-)开头的文件,你可能需要在xargs命令的末尾添加两个破折号(--)。

双破折号(--)可以告诉程序跟在双破折号后面的参数是文件名,而不是选项。然而,要记住并不是所有的程序都支持使用双破折号。

There’s an alternative to xargs when using find: the -exec option. However, the syntax is somewhat tricky because you need to supply a {} to substitute the filename and a literal ; to indicate the end of the command. Here’s how to perform the preceding task using only find:

在使用find命令时,有一个替代xargs的选项:-exec。

然而,语法有些棘手,因为你需要提供一个{}来替换文件名,并使用一个字面的分号(;)来表示命令的结束。

以下是只使用find执行前面的任务的方法:

$ find . -name '*.gif' -exec file {} \;

11.10.5 expr

If you need to use arithmetic operations in your shell scripts, the expr command can help (and even do some string operations). For example, the command expr 1 + 2 prints 3. (Run expr --help for a full list of operations.)

The expr command is a clumsy, slow way of doing math. If you find yourself using it frequently, you should probably be using a language like Python instead of a shell script.

如果你需要在你的Shell脚本中使用算术运算,可以使用expr命令来帮助你(甚至进行一些字符串操作)。例如,命令expr 1 + 2会输出3。(运行expr --help可以查看所有可用的操作列表。)

expr命令是一种笨拙而慢速的进行数学计算的方式。

如果你发现自己经常使用它,那么你应该考虑使用像Python这样的编程语言,而不是Shell脚本。

11.10.6 exec

The exec command is a built-in shell feature that replaces the current shell process with the program you name after exec. It carries out the exec() system call that you learned about in Chapter 1. This feature is designed for saving system resources, but remember that there’s no return; when you run exec in a shell script, the script and shell running the script are gone, replaced by the new command

To test this in a shell window, try running exec cat. After you press CTRL-D or CTRL-C to terminate the cat program, your window should disappear because its child process no longer exists.

exec命令是一种内置的Shell功能,它用指定的程序替换当前的Shell进程。

它执行了你在第1章学习过的exec()系统调用。这个功能旨在节省系统资源,但请记住,它没有返回值;

当你在一个Shell脚本中运行exec时,脚本和运行脚本的Shell都会被替换为新的命令。

在一个Shell窗口中测试这个功能,尝试运行exec cat命令。在你按下CTRL-D或CTRL-C终止cat程序之后,窗口应该消失,因为它的子进程不再存在。

11.11 Subshells

Say you need to alter the environment in a shell slightly but don’t want a permanent change. You can change and restore a part of the environment (such as the path or working directory) using shell variables, but that’s a clumsy way to go about things. The easy way around these kinds of problems is to use a subshell, an entirely new shell process that you can create just to run a command or two. The new shell has a copy of the original shell’s environment, and when the new shell exits, any changes you made to its shell environment disappear, leaving the initial shell to run as normal.

假设你需要在Shell中稍微修改环境,但又不想做出永久性的更改。

你可以使用Shell变量来更改和恢复环境的一部分(例如路径或工作目录),但这种方法很笨拙。

解决这类问题的简单方法是使用子Shell,它是一个全新的Shell进程,你可以创建它来运行一个或两个命令。

新的Shell拥有原始Shell环境的副本,当新的Shell退出时,你对其Shell环境所做的任何更改都会消失,使初始Shell正常运行。

To use a subshell, put the commands to be executed by the subshell in parentheses. For example, the following line executes the command uglyprogram in uglydir and leaves the original shell intact:

要使用子Shell,将要由子Shell执行的命令放在括号中。

例如,以下行在uglydir中执行命令uglyprogram,并保持原始Shell的完整性:

$ (cd uglydir; uglyprogram)

This example shows how to add a component to the path that might cause problems as a permanent change:

本例展示了如何在路径中添加一个可能会引起问题的组件,作为永久性更改:

$ (PATH=/usr/confusing:$PATH; uglyprogram)

Using a subshell to make a single-use alteration to an environment variable is such a common task that there is even a built-in syntax that avoids the subshell:

使用子壳对环境变量进行一次性修改是一项非常常见的任务,甚至有一种内置语法可以避免使用子壳:

$ PATH=/usr/confusing:$PATH uglyprogram

Pipes and background processes work with subshells, too. The following example uses tar to archive the entire directory tree within orig and then unpacks the archive into the new directory target, which effectively duplicates the files and folders in orig (this is useful because it preserves ownership and permissions, and it’s generally faster than using a command such as cp -r):

管道和后台进程也可以与子外壳一起使用。

下面的示例使用 tar 将 orig 中的整个目录树存档,然后将存档解压到新目录 target 中,这实际上是复制 orig 中的文件和文件夹(这很有用,因为它保留了所有权和权限,而且通常比使用 cp -r 等命令更快):

$ tar cf - orig | (cd target; tar xvf -)

WARNING Double-check this sort of command before you run it to make sure that the target directory exists and is completely separate from the orig directory

警告 运行此类命令前请仔细检查,以确保目标目录存在,并且与原始目录完全分离

11.12 Including Other Files in Scripts(在脚本中包含其他文件)

If you need to include another file in your shell script, use the dot (.) operator. For example, this runs the commands in the file config.sh:

如果你需要在你的shell脚本中包含另一个文件,可以使用点(.)运算符。例如,下面的命令会运行config.sh文件中的命令:

. config.sh

This “include” file syntax does not start a subshell, and it can be useful for a group of scripts that need to use a single configuration file.

这种“包含”文件的语法不会启动一个子shell,并且对于需要使用单个配置文件的一组脚本非常有用。

11.13 Reading User Input(读取用户输入)

The read command reads a line of text from the standard input and stores the text in a variable. For example, the following command stores the input in $var:'

read命令从标准输入读取一行文本,并将文本存储在一个变量中。例如,下面的命令将输入存储在$var变量中:

$ read var

This is a built-in shell command that can be useful in conjunction with other shell features not mentioned in this book.

这是一个内置的shell命令,结合其他未在本书中提及的shell特性非常有用。

11.14 When (Not) to Use Shell Scripts(何时(不)使用 Shell 脚本)

The shell is so feature-rich that it’s difficult to condense its important elements into a single chapter. If you’re interested in what else the shell can do, have a look at some of the books on shell programming, such as Unix Shell Programming, 3rd edition, by Stephen G. Kochan and Patrick Wood (SAMS Publishing, 2003), or the shell script discussion in The UNIX Programming Environment by Bran W. Kernighan and Rob Pike (Prentice Hall, 1984).

Shell非常强大,很难在一个章节中概括其重要的元素。

如果你对shell还有其他的用途感兴趣,可以参考一些关于shell编程的书籍,比如《Unix Shell Programming》第3版,作者是Stephen G. Kochan和Patrick Wood(SAMS Publishing,2003),或者《The UNIX Programming Environment》中关于shell脚本讨论的章节,作者是Bran W. Kernighan和Rob Pike(Prentice Hall,1984)。

However, at a certain point (especially when you start using the read built-in), you have to ask yourself if you’re still using the right tool for the job. Remember what shell scripts do best: manipulate simple files and commands. As stated earlier, if you find yourself writing something that looks convoluted, especially if it involves complicated string or arithmetic operations, you should probably look to a scripting language like Python, Perl, or awk.

然而,在某个特定的点上(尤其是当你开始使用read内置命令时),你必须问自己是否仍然在使用正确的工具来完成工作。

记住shell脚本最擅长的是处理简单的文件和命令。

正如前面所述,如果你发现自己写的东西看起来很复杂,特别是涉及复杂的字符串或算术操作,那么你可能应该考虑使用Python、Perl或awk等脚本语言。


Xander
195 声望49 粉丝