動態

詳情 返回 返回

【Linux】《how linux work》第十一章 Shell 腳本簡介 - 動態 詳情

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等腳本語言。

user avatar journey_64224c9377fd5 頭像 u_11365552 頭像 u_15702012 頭像 ruozxby 頭像 macrozheng 頭像 an_653b347d1d3da 頭像 ruyadehuangdou 頭像
點贊 7 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.