简单的 shell 脚本
一个简单的 shell 脚本只是一点点按顺序执行的命令列表。通常,一个 shell 脚本应该从如下面的一行开始:
#!/bin/bash这表示脚本应该在 bash shell 中运行,无论用户选择了哪个交互式 shell。这是非常重要的,因为不同 shell 的语法可能有很大差异。
简单的例子
这是一个非常简单的 shell 脚本示例。 它只是运行一些简单的命令:
#!/bin/bashecho "hello, $USER. I wish to list some files of yours"echo "listing files in the current directory, $PWD"ls # list files首先,请注意第4行的注释。在一个 bash 脚本中,任何一个 #(除了第一行的 shebang 之外)都被视为注释。 即 shell 解释器会忽略它。而对于人们阅读脚本是有益的。
$USER 和 $PWD 是变量。这些是由 bash shell 本身定义的标准变量,它们不需要在脚本中定义。请注意,当变量名称在双引号内时,变量是展开的(expanded)。展开(expand)是一个非常合适的词:shell 看到字符串 $USER,并用变量的值替换它,然后执行命令。
下面我们来继续讨论变量...
变量
任何编程语言都需要变量。如下定义一个变量:
X="hello"然后引用它:
$X更具体地说,$X 用于表示变量 X 的值。 关于语义的一些事情要注意:
- 如果你在
=标志的两边留下空格,bash就会变得不快乐。 例如,以下内容提供了一个错误信息:X = hello
- 虽然在我的例子中有引号,但并不总是必需的。 当变量的值包含空格时需要引号。 例如:
X=hello world # errorX="hello world" # OK
这是因为 shell 本质上将命令行看作一堆由空格分隔的命令和命令参数。 foo=baris 被认为是一个命令。 foo = bar的问题是 shell 看到由空格分隔的单词 foo,并把它解释为一个命令。 同样,命令 X=hello world 的问题是 shell 将 X=hello 解释为一个命令,而 world 这个词没有任何意义(因为赋值命令不能携带参数)。
单引号与双引号
基本上,变量名称只在双引号内展开,单引号里不展开。如果不需要引用变量,单引号很好用,因为结果更可预测。
一个例子:
#!/bin/bashecho -n '$USER=' # -n option stops echo from breaking the lineecho "$USER"echo "\$USER=$USER" # this does the same thing as the first two lines输出看起来像这样(假设你的用户名是 elflord):
$USER=elflord$USER=elflord双引号更灵活,但是可预测性较低。如果可以在两者之间选择的话,使用单引号。
使用引号括起变量
有时,使用双引号保护变量名是个好主意。 如果您的变量值(a)包含空格或(b)是空字符串,则这是很重要的。 一个例子如下:
#!/bin/bashX=""if [ -n $X ]; then # -n tests to see if the argument is non empty echo "the variable X is not the empty string"fi这段脚本会输出:the variable X is not the empty string。因为 shell 将 $X 扩展为空字符串。 表达式 [-n] 返回 true(因为它没有提供参数)。 一个更好的脚本将是:
#!/bin/bashX=""if [ -n "$X" ]; then # -n tests to see if the argument is non empty echo "the variable X is not the empty string"fi在这个例子中,表达式展开为 [ -n "" ],返回 false。因为用双引号括起来的字符串显然是空的。
变量展开实战
只是为了说服你,shell 真的像我之前提到的那样在 “展开” 变量,这里是一个例子:
#!/bin/bashLS="ls"LS_FLAGS="-al"$LS $LS_FLAGS $HOME这看起来有点神秘。 最后一行会发生什么,它实际上是执行命令ls -al /home/elflord(假设 /home/elflord 是你的主目录)。 也就是说,shell 只是用它们的值替换变量,然后执行命令。
使用大括号来保护变量
好了,这里有一个潜在的问题。 假设要 echo 变量 X 的值,紧接着是字母 abc。 问题:你怎么做的? 我们来试一试:
#!/bin/bashX=ABCecho "$Xabc"这样没有得到输出。哪里出了错?答案是,shell 认为我们引用的是未初始化的变量 Xabc。处理这个问题的方法是把大括号放在 X 上以将其与其他字符分开。以下给出了期望的结果:
#!/bin/bashX=ABCecho "${X}abc"条件语句
有时需要检查某些条件。一个字符串是否有0个长度?文件“foo”是否存在,它是一个符号链接还是一个真实的文件?首先,我们使用 if 命令来运行测试。 语法如下:
if conditionthen statement1 statement2 ..........fi
有时您可能希望在条件失败时指定备用操作。这是如何做的://: <> (Sometimes, you may wish to specify an alternate action when the condition fails. Here's how it's done.)
if conditionthen statement1 statement2 ..........else statement3fi
或者,如果第一个 if 失败,则可以测试另一个条件。 请注意,可以添加任何数量的 elif。
if condition1then statement1 statement2 ..........elif condition2then statement3 statement4 ........ elif condition3then statement5 statement6 ........ fi
如果相应的条件为真,则 if/elif 和下一个 elif 或 fi 之间的块内的语句将被执行。 实际上,任何命令都可以替代条件,并且当且仅当命令返回退出状态为0(换句话说,如果命令退出“成功”),则该块将被执行。 但是,在本文档中,我们只会使用 test 或 [ ] 来测试条件。
The Test Command and Operators
The command used in conditionals nearly all the time is the test command. Test returns true or false (more accurately, exits with 0 or non zero status) depending respectively on whether the test is passed or failed. It works like this:test operand1 operator operand2for some tests, there need be only one operand (operand2) The test command is typically abbreviated in this form:[ operand1 operator operand2 ]To bring this discussion back down to earth, we give a few examples:
#!/bin/bashX=3Y=4empty_string=""if [ $X -lt $Y ] # is $X less than $Y ?then echo "\$X=${X}, which is smaller than \$Y=${Y}"fiif [ -n "$empty_string" ]; then echo "empty string is non_empty"fiif [ -e "${HOME}/.fvwmrc" ]; then # test to see if ~/.fvwmrc exists echo "you have a .fvwmrc file" if [ -L "${HOME}/.fvwmrc" ]; then # is it a symlink ? echo "it's a symbolic link elif [ -f "${HOME}/.fvwmrc" ]; then # is it a regular file ? echo "it's a regular file" fielse echo "you have no .fvwmrc file"fiSome pitfalls to be wary of
The test command needs to be in the form "operand1operatoroperand2" or operatoroperand2 , in other words you really need these spaces, since the shell considers the first block containing no spaces to be either an operator (if it begins with a '-') or an operand (if it doesn't). So for example; this
if [ 1=2 ]; then echo "hello"fi
gives exactly the "wrong" output (ie it echos "hello", since it sees an operand but no operator.)Another potential trap comes from not protecting variables in quotes. We have already given an example as to why you must wrap anything you wish to use for a -n test with quotes. However, there are a lot of good reasons for using quotes all the time, or almost all of the time. Failing to do this when you have variables expanded inside tests can result in very wierd bugs. Here's an example: For example,
#!/bin/bashX="-n"Y=""if [ $X = $Y ] ; then echo "X=Y"fiThis will give misleading output since the shell expands our expression to[ -n = ]and the string "=" has non zero length.
A brief summary of test operators
Here's a quick list of test operators. It's by no means comprehensive, but its likely to be all you'll need to remember (if you need anything else, you can always check the bash manpage ... )
| operator | produces true if... | number of operands |
|---|---|---|
| -n | operand non zero length | 1 |
| -z | operand has zero length | 1 |
| -d | there exists a directory whose name is operand | 1 |
| -f | there exists a file whose name is operand | 1 |
| -eq | the operands are integers and they are equal | 2 |
| -neq | the opposite of -eq | 2 |
| = | the operands are equal (as strings) | 2 |