十一、构建基础脚本
- 使用多个命令
- shell 脚本的关键是能够输入多个命令并处理每个命令的结果,甚至是将一个命令的结果传给另一个命令。 shell 可以让你将多个命令串联起来,一次性执行完。
- 如果想让两个命令一起运行,可以将其放在同一行中,彼此用分号隔开。通过 这种办法,能将任意多个命令串联在一起使用,只要不超过命令行最大字符数 255 就可以。
- 创建 shell 脚本文件
- 在创建 shell 脚本文件时,必须在文件的第一行指定要使用的 shell,格式如下:
#!/bin/bash
- 在普通的 shell 脚本中, #用作注释行。 shell 并不会处理 shell 脚本中的注释行。然而, shell 脚本文件的第一行是个例外, #后面的惊叹号会告诉 shell用哪个 shell 来运行脚本。(是的, 可以使用 bash shell,然后使用另一个 shell 来运行你的脚本)
- 在指明了 shell 之后,可以在文件的各行输入命令,每行末尾加一个换行符。也可以根据需要,使用分号将两个命令放在一行中, 但在 shell 脚本中, 可以将命令放在独立的行中。 shell 会根据命令在文件中出现的顺序进行处理。
- 要让 shell 找到 test1 脚本,可以采用下列两种方法之一:
- 将放置 shell 脚本文件的目录添加到 PATH 环境变量中;
- 在命令行中使用绝对路径或相对路径来引用 shell 脚本文件。
- 有些 Linux 发行版会将$HOME/bin 目录加入 PATH 环境变量中。这样就在每个用户的 $HOME 目录中提供了一个存放文件的地方,shell 可以在那里查找要执行的命令。
- 可以使用单点号来引用当前目录下的文件。
在创建 test1 文件时, umask 的值决定了新文件的默认权限设置。由于 umask 变量被设为 022,因此新建的文件只有文件属主和属组才有读/写权限。因此要通过 chmod 命令赋予文件属主执行文件的权限:$ ./test1 bash: ./test1: Permission denied $ ls -l test1 -rw-r--r-- 1 user user 73 Jun 02 15:36 test1 $ chmod u+x test1 $ ./test1 // 执行结果打印
- 显示消息
- 可以通过 echo 命令来实现显示消息。如果在 echo 命令后面加上字符串,那么echo 命令就会显示出这个字符串:
- 注意, 在默认情况下, 无须使用引号将要显示的字符串划定出来。echo 命令可用单引号或双引号来划定字符串。如果你在字符串中要用到某种引号,可以使 用另一种引号来划定字符串:
$ echo "This is a test to see if you're paying attention" This is a test to see if you're paying attention $ echo 'Rich says "scripting is easy".' Rich says "scripting is easy".
- 可以将 echo 命令添加到shell 脚本中任何需要显示额外信息的地方。
- 可以使用 echo 命令 的-n 选项把字符串和命令输出显示在同一行中。
echo The time and date are: date 改成: echo -n "The time and date are:"
- 需要在字符串的两侧使用引号, 以确保要显示的字符串尾部有一个空格。命令输出会在紧 接着字符串结束的地方出现。
-
变量允许在 shell 脚本中临时存储信息,以便同脚本中的其他命令一起使用。
-
环境变量
- shell 维护着一组用于记录特定的系统信息的环境变量, 比如系统名称、已登录系统的用户 名、用户的系统 ID (也称为 UID)、用户的默认主目录以及 shell 查找程序的搜索路径。可以用 set 命令显示一份完整的当前环境变量列表。
- 在脚本中,可以在环境变量名之前加上$来引用这些环境变量,用法如下:
#!/bin/bash # display user information from the system. echo "User info for userid: $USER" echo UID: $UID echo HOME: $HOME $ echo "The cost of the item is $15" The cost of the item is 5 $ echo "The cost of the item is \$15" The cost of the item is $15
- 环境变量 U S E R 、 USER、 USER、UID 和$HOME 用来显示已登录用户的相关信息。
- echo 命令中的环境变量会在脚本运行时被替换成当前值。
- 在第一个字符串中 可以将 U S E R 系统变量放入双引号中,而 s h e l l 依然能够知道我们的意图。但这种方法存在一个问题。只要脚本在双引号中看到 USER 系统变量放入双引号中, 而 shell 依然能够知道我们的意图。但这种方法存在一个问题。只要脚本在双引号中看到 USER系统变量放入双引号中,而shell依然能够知道我们的意图。但这种方法存在一个问题。只要脚本在双引号中看到,就会以为你在引用变量。在上面的例子中, 脚本会尝试显示变量$1 的值(尚未定义), 再显示数字 5。
- 要显示$,必须在它前面放置一个反斜线。
- 反斜线允许 shell 脚本按照字面意义解释$,而不是引用变量。
- 可能还有通过 v a r i a b l e 形式引用的变量。花括号通常用于帮助界定 {variable}形式引用的变量。花括号通常用于帮助界定 variable形式引用的变量。花括号通常用于帮助界定后的变量名。
- 用户自定义变量
- 定义变量允许在脚本中临时存储并使用数据。
- 用户自定义变量的名称可以是任何由字母、数字或下划线组成的字符串,长度不能超过 20 个字符。而且变量名区分大小写。
- 使用等号为变量赋值。在变量、等号和值之间不能出现空格。
var1=10 var2=-57 var3=testing var4="still more testing"
- shell 脚本会以字符串形式存储所有的变量值,脚本中的各个命令可以自行决定变量值的数据类型。 shell 脚本中定义的变量在脚本的整个生命周期里会一直保持着它们的值,在脚本结束时会被删除。
- 与系统变量类似,用户自定义变量可以通过$引用。
- 引用变量值时要加 ,对变量赋值时则不用加 ,对变量赋值时则不用加 ,对变量赋值时则不用加。少了$,shell 会将变量名解释成普通的字符串。
- 命令替换
- shell 脚本中最有用的特性之一是可以从命令输出中提取信息并将其赋给变量。把输出赋给变量之后, 就可以随意在脚本中使用了。
- 有两种方法可以将命令输出赋给变量。
- 反引号(`)
- $()格式
- 命令替换允许将 shell 命令的输出赋给变量。要么将整个命令放入反引号内,要么使用$()格式:
testing=`date` testing=$(date)
- shell 会执行命令替换符内的命令, 将其输出赋给变量 testing。
- 注意, 赋值号和命令替换 符之间没有空格。
- 下面这个例子在脚本中通过命令替换获得当前日期并用其来生成唯一文件名:
#!/bin/bash # copy the /usr/bin directory listing to a log file today=$(date +%y%m%d) ls /usr/bin -al > log.$today
- today 变量保存着格式化后的 date 命令的输出。这是提取日期信息,用于生成日志文件名 的一种常用技术。 +%y%m%d 格式会告诉 date 命令将日期显示为两位数的年、月、日的组合。
- 文件本身包含重定向的目录列表输出。
- 日志文件采用$today 变量的值作为文件名的一部分。日志文件的内容是 /usr/bin 目录内容的列表输出。
- 命令替换会创建出子 shell 来运行指定命令,这是由运行脚本的 shell 所生成的一个独立的 shell。因此, 在子 shell 中运行的命令无法使用脚本中的变量。
- 如果在命令行中使用./路径执行命令,就会创建子 shell,但如果不加路径,则不会创建子 shell。不过,内建的 shell 命令也不会创建子shell 。在命令行中运行脚本时要当心。
- 输出重定向
- 最基本的重定向会将命令的输出发送至文件。 bash shell 使用大于号(>)来实现该操作:
command > outputfile
- 如果输出文件已存在,则重定向运算符会用新数据覆盖已有的文件。
- 将命令输出追加到已有文件中,可以用双大于号(>>)来追加数据。
- 输入重定向
- 输入重定向会将文件的内容重定向至命令, 而不是将命令输出重定向至文件。
- 输入重定向运算符是小于号(<):
command < inputfile
- wc 命令可以统计数据中的文本。在默认情况下,它会输出 3 个值。
- 文本的行数
- 文本的单词数
- 文本的字节数
- 通过将文本文件重定向到 wc 命令,立刻就可以得到文件中的行、单词和字节的数量。
- 还有另外一种输入重定向的方法,称为内联输入重定向(inline input redirection)。这种方法 无须使用文件进行重定向, 只需在命令行中指定用于输入重定向的数据即可。
- 内联输入重定向运算符是双小于号(<<)。除了这个符号, 必须指定一个文本标记来划分输 入数据的起止。任何字符串都可以作为文本标记, 但在数据开始和结尾的文本标记必须一致:
command << marker data marker
- 在命令行中使用内联输入重定向时, shell 会用 PS2 环境变量中定义的次提示符来提示输入数据, 其用法如下所示:
$ wc << EOF > test string 1 > test string 2 > test string 3 > EOF 3 9 42
- 次提示符会持续显示,以获取更多的输入数据,直到输入了作为文本标记的那个字符串。
- wc 命令会统计内联输入重定向提供的数据包含的行数、单词数和字节数。
- 管道
- 有时候需要将一个命令的输出作为另一个命令的输入。这可以通过重定向来实现。这种方法的确管用,但仍然是一种比较烦琐的信息生成方式。无须将命令输出重定向至文件,可以将其直接传给另一个命令。这个过程称为管道连接(piping)。
- 管道被置于命令之间,将一个命令的输出传入另一个命令中:
command1 | command2
- 可别以为由管道串联起的两个命令会依次执行。实际上, Linux 系统会同时运行这两个命令,在系统内部将二者连接起来。当第一个命令产生输出时,它会被立即传给第二个命令。数据传输不会用到任何中间文件或缓冲区。
- 管道可以串联的命令数量没有限制。可以持续地将命令输出通过管道传给其他命令来细化操作。
- 管道最常见的用法之一是将命令产生的大量输出传送给 more 命令。对 ls 命令来说,这种用法尤为常见。
$ ls -al | more
- ls -l 命令会产生目录中所有文件的长列表。对包含大量文件的目录来说,这个列表会相当长。将输出通过管道传给 more 命令,可以强制分屏显示 ls 的输出列表。
-
在 shell 脚本中,执行数学运算有两种方式:
- expr 命令
- 使用方括号
-
expr 命令
- expr,该命令可在命令行中执行数学运算,但是特别笨拙。
- expr 命令能够识别少量算术运算符和字符串运算符。expr 命令运算符如下表:
运算符 描述 ARG1 | ARG2 如果 ARG1 既不为 null 也不为 0 ,就返回 ARG1;否则,返回 ARG2 ARG1 & ARG2 如果 ARG1 和 ARG2 都不为 null 或 0 ,就返回 ARG1;否则,返回 0 ARG1 < ARG2 如果 ARG1 小于 ARG2,就返回 1 ;否则,返回 0 ARG1 <= ARG2 如果 ARG1 小于或等于 ARG2,就返回 1 ;否则,返回 0 ARG1 = ARG2 如果 ARG1 等于 ARG2,就返回 1 ;否则,返回 0 ARG1 != ARG2 如果 ARG1 不等于 ARG2,就返回 1 ;否则,返回 0 ARG1 >= ARG2 如果 ARG1 大于或等于 ARG2,就返回 1 ;否则,返回 0 ARG1 > ARG2 如果 ARG1 大于 ARG2,就返回 1 ;否则,返回 0 ARG1 + ARG2 返回 ARG1 和 ARG2 之和 ARG1 - ARG2 返回 ARG1 和 ARG2 之差 ARG1 * ARG2 返回 ARG1 和 ARG2 之积 ARG1 / ARG2 返回 ARG1 和 ARG2 之商 ARG1 % ARG2 返回 ARG1 和 ARG2 之余数 STRING : REGEXP 如果 REGEXP 模式匹配 STRING,就返回该模式匹配的内容 match STRING REGEXP 如果 REGEXP 模式匹配 STRING,就返回该模式匹配的内容 substr STRING POS LENGTH 返回起始位置为 POS (从 1 开始计数)、长度为 LENGTH 的子串 index STRING CHARS 返回 CHARS 在字符串 STRING 中所处的位置;否则,返回 0 length STRING 返回字符串 STRING 的长度 - TOKEN | 将 TOKEN 解释成字符串, 即使 TOKEN 属于关键字
(EXPRESSION) | 返回 EXPRESSION 的值
- TOKEN | 将 TOKEN 解释成字符串, 即使 TOKEN 属于关键字
- 使用方括号
- 在 bash 中, 要将数学运算结果赋给变量, 可以使用$和方括号( $[ operation ]):
$ var1=$[1 + 5] $ echo $var1 6 $ var2=$[$var1 * 2] $ echo $var2 12
- 用方括号来执行数学运算要比 expr 命令方便得多。这种技术也适用于 shell脚本。
- 在使用方括号执行数学运算时,无须担心 shell 会误解乘号或其他符号。 shell 清楚方括号内 的星号不是通配符。
- bash shell 的数学运算符只支持整数运算。
- z shell(zsh)提供了完整的浮点数操作。如果需要在 shell 脚本中执行浮点数运算,那么不妨考虑一下 z shell。
- 浮点数解决方案
- 能够克服 bash 只支持整数运算的限制的最常见的做法是使用内建的 bash 计算器 bc。
- bash 计算器实际上是一种编程语言, 允许在命令行中输入浮点数表达式, 然后解释并计算该表达式,最后返回结果。 bash 计算器能够识别以下内容:
- 数字(整数和浮点数)
- 变量(简单变量和数组)
- 注释(以#或 C 语言中的/* */开始的行)
- 表达式
- 编程语句(比如 if-then 语句)
- 函数
- 可以在 shell 提示符下通过 bc 命令访问 bash 计算器。要退出 bash 计算器,必须输入 quit。
- 浮点数运算是由内建变量 scale 控制的。你必须将该变量的值设置为希望在计算结果中保 留的小数位数,否则将无法得到期望的结果。
$ bc -q 3.44 / 5 0 scale=4 3.44 / 5 .6880 quit
- scale 变量的默认值是 0。在 scale 值被设置前, bash 计算器的计算结果不包含小数位。 在将其设置成 4 后, bash 计算器显示的结果包含 4 位小数。
- -q 选项可以不显示bash 计算器冗长的欢迎信息。
- 除了普通数字, bash 计算器还支持变量:
$ bc -q var1=10 var1 * 4 40 var2 = var1 / 5 print var2 2 quit
- 变量值一旦被定义,就可以在整个 bash 计算器会话中使用了。 print 语句可以打印变量和数字。
- 要在脚本中使用 bc,可以用命令替换来运行 bc 命令,将输出赋给变量。基本格式如下:
variable=$(echo "options; expression" | bc)
- 第一部分的 options 允许你设置变量。如果需要多个变量,可以用分号来分隔它们。 expression 定义了要通过bc 执行的数学表达式。
- 下面是在脚本中执行此操作的示例:
#!/bin/bash var1=$(echo " scale=4; 3.44 / 5" | bc) echo The answer is $var1
- 这个例子将 scale 变量设置为 4位小数,在 expression 部分指定了特定的运算。
- bc 命令能接受输入重定向,允许将一个文件重定向到 bc 命令来处理。
- 最好的办法是使用内联输入重定向, 它允许直接在命令行中重定向数据。在 shell 脚本中, 可以将输出赋给一个变量:
variable=$(bc << EOF options statements expressions EOF )
- 字符串 EOF 标识了内联重定向数据的起止。
- 用命令替换符将 bc 命令的输出赋给变量 variable。
- 可以将 bash 计算器涉及的各个部分放入脚本文件的不同行中。以下示例演示了如何 在脚本中使用这项技术:
$ cat test12 #!/bin/bash var1=10.46 var2=43.67 var3=33.2 var4=71 var5=$(bc << EOF scale = 4 a1 = ( $var1 * $var2) b1 = ($var3 * $var4) a1 + b1 EOF ) echo The final answer for this mess is $var5 $
- 将选项和表达式放在脚本的不同行中可以让处理过程变得更清晰并提高易读性。
- EOF 字符串标识了重定向给 bc 命令的数据的起止。当然,必须用命令替换符标识出用来给变量赋值的命令。
- 在这个例子中,可以在 bash 计算器中为变量赋值。
- 在 bash 计算器中创建的变量仅在计算器中有效,不能在 shell 脚本中使用。
- 退出脚本
- shell 中运行的每个命令都使用退出状态码来告诉 shell 自己已经运行完毕。退出状态码是一个 0 ~ 255 的整数值, 在命令结束运行时由其传给 shell。可以获取这个值并在脚本中使用。
- 查看退出状态码
-
Linux 提供了专门的变量 ? 来保存最后一个已执行命令的退出状态码。对于需要进行检查的命令,必须在其运行完毕后立刻查看或使用 ?来保存最后一个已执行命令的退出状态码。对于需要进行检查的 命令,必须在其运行完毕后立刻查看或使用 ?来保存最后一个已执行命令的退出状态码。对于需要进行检查的命令,必须在其运行完毕后立刻查看或使用?变量。这是因为该变量的值会随时变成由 shell 所 执行的最后一个命令的退出状态码。
-
按照惯例,对于成功结束的命令, 其退出状态码是 0。对于因错误而结束的命令,其退出状态码是一个正整数。
-
无效命令会返回退出状态码 127。
-
Linux 退出状态码
状态码 描述 0 命令成功结束 1 一般性未知错误 2 不适合的 shell 命令 126 命令无法执行 127 没找到命令 128 无效的退出参数 128+x 与 Linux 信号 x 相关的严重错误 130 通过 Ctrl+C 终止的命令 255 正常范围之外的退出状态码 -
退出状态码 126 表明用户没有执行命令的正确权限
-
另一个常见错误是给命令提供了无效参数,这会产生一般性的退出状态码 1,表明在命令中发生了未知错误。
- exit 命令
- 在默认情况下, shell 脚本会以脚本中的最后一个命令的退出状态码退出。
- 可以改变这种默认行为,返回自己的退出状态码。 exit 命令允许在脚本结束时指定一个退出状态码:
$ cat test13 #!/bin/bash # testing the exit status var1=10 var2=30 var3=$[ $var1 + $var2 ] echo The answer is $var3 exit 5 $ $ chmod u+x test13 $ ./test13 The answer is 40 $ echo $? 5 $
- 也可以使用变量作为 exit 命令的参数。但使用这个功能时要小心,因为退出状态码最大只能是 255。
- 退出状态码被缩减到了 0 ~ 255 的区间。 shell 通过模运算得到这个结果。一个值的模就是被 除后的余数。最终的结果是指定的数值除以 256 后得到的余数。如果指定的值是 300(返回值),余数是 44,这个余数就成了最后的退出状态码。
- 用 if-then 语句来检查某个命令返回的错误状态码, 以便知道命令是否成功。
- date 命令允许使用-d 选项指定特定日期(以任意格式), 然后以我们定义的其他格式输出该日期。
$date -d "Jan 1, 2020" +%s 1577854800 $
- 输出的是纪元时间(纪元时间将时间 指定为 1970 年 1 月 1 日午夜后的整数秒(这是一个古老的 Unix 标准))。