这篇文章将学习 Bash 脚本中的引用、转义、退出及退出状态相关知识。
引用的作用
引用就是将字符串用引号括起来,引用的主要作用就是保护字符串中的特殊字符不被 shell 或者是 shell 脚本重新解释或扩展。因此在 Bash 中,当我们引用一个字符串,我们的目标是保留它的字面含义。
1 2 3 4
| t.sh
ls: cannot access 't*': No such file or directory
|
特定的程序和工具能够展开引用字符串中的特殊字符。引用的一个重要的作用就是保护 Shell 中的命令行参数,但还是允许调用的程序来扩展它:
引用也可以抑制 echo 命令 吞掉
换行符:
1 2 3 4 5 6 7
| total 0 -rw-r--r-- 1 root root 0 May 28 14:26 t.sh -rw-r--r-- 1 root root 0 May 28 14:31 u.sh
total 0 -rw-r--r-- 1 root root 0 May 28 14:26 t.sh -rw-r--r-- 1 root root 0 May 28 14:31 u.sh
|
引用变量
应用变量时,通常将变量放在双引号内,此时除了 $
、` 反引号、\
转义符 之外的其他特殊字符都将失去它们的特殊含义。在双引号中,仍然可以通过 $variable
获取变量的值。
使用双引号可以防止字符串分割,此时即使参数中拥有很多空白字符,被包在双引号中依旧算单一字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #!/bin/bash
List="one two three"
for a in $List do echo "str $a" done
echo "---"
for a in "$List" do echo "str $a" done
|
1 2 3 4 5 6
| str one str two str three --- str one two three
|
如下是一个更复杂的实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| variable1="a variable containing five words"
COMMAND This is $variable1
COMMAND "This is $variable1"
variable2=""
COMMAND $variable2 $variable2 $variable2
COMMAND "$variable2" "$variable2" "$variable2"
COMMAND "$variable2 $variable2 $variable2"
|
单引号总体上与双引号类似,但是在单引号内,除了 '
字符之外,其他所有特殊字符都没有特殊的含义。所以可以认为单引号比双引号更严格。这也是为什么在单引号内无法进行变量替换。尤其注意,单引号内中的转义符 \
也失去转义作用,所以想在单引号引用的字符串内转义 单引号
,是无法实现的:
1 2 3 4
| It's me # echo 'It\'s me' >
|
转义
转义是一种引用单个字符的方法,通过在特殊字符前面添加 \
来告诉 shell 按照字面意思去解释这个字符,而不是使用它的特殊含义。需要注意的是,在某些特定的命令中,例如 echo
、sed
等,转义符往往起到相反的效果,它反倒可能引发出这个字符特殊的含义。
如下展示了在 echo
、sed
中使用的具有特殊含义的转义字符:
- \n:换行
- \r:回车
- \t:水平 tab
- \v:垂直 tab
- \b:退格
- \a:警报
- \nnn:ASCII 码的八进制形式
- \xhh:十六进制数表示
- ":转义引号
- $:转义 $
- \:转义 \
在 $’…’ 字符串扩展结构中,可以通过转义八进制或十六进制的 ASCII 码形式给变量赋值,例如 quote=$’\042’。
如下脚本展示了在 echo 中使用转义字符的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| #!/bin/bash
echo ""
echo "this will print as two lines"
echo "this will print \ as one lines"
echo echo echo "========"
echo "\v\v\v" echo "========" echo "VERTICAL TABS" echo -e "\v\v\v"
echo "QUOTATION MARK" echo -e "\042" echo "========"
echo; echo "NEWLINE and (maybe) BEEP" echo $'\n' echo $'\a'
echo "Introducing the \$'...' string-expansion construct ..." echo "... featuring more quotation marks" echo $'\t \042 \t' echo $'\t \x22 \t' echo
quote=$'\042' echo "$quote Quoted String $quote and this lies outside the quotes" echo
triple_underline=$'\137\137\137' echo "$triple_underline UNDERLINE $triple_underline" echo
ABC=$'\101\102\103\010' echo $ABC echo
escape=$'\033' echo "\"escape\" echoes an $escape" echo
exit 0
|
- 在 echo 命令中,通过 -e 选项来打印转义字符
- 如果使用
$'\nnn'
或 $'\xhh'
字符串扩展时,就不需要使用 -e
选项
- 在字符串扩展中,可以使用连续多个 ASCII 码字符
根据转义符所在的上下文(强引用、弱引用、命令替换或者在 here document)的不同,它的行为也会有所不同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| #!/bin/bash
echo \z
echo \\z
echo '\z'
echo '\\z'
echo "\z"
echo "\\z"
echo "**********"
echo `echo \z`
echo `echo \\z`
echo `echo \\\z`
echo `echo \\\\z`
echo `echo \\\\\\z`
echo `echo "\z"`
echo `echo "\\z"`
echo "**********"
cat <<EOF \z EOF
catg << EOF \z EOF
|
含有转义字符的字符串可以赋值给变量,但是仅仅将单一的转义字符赋值给变量是不行的,例如如下代码:
1 2
| variable=\ echo "$variable"
|
此时 \
其实转义了换行符,最终效果是:
1
| variable=echo "$variable"
|
转义空格能够避免在命令参数列表中的字符分隔问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
-rw-r--r-- 1 root root 0 May 30 14:11 file1 -rw-r--r-- 1 root root 0 May 30 14:11 file2 -rw-r--r-- 1 root root 0 May 30 14:11 file3
ls: cannot access 'file1 file2 file3': No such file or directory
-rw-r--r-- 1 root root 0 May 30 14:11 file1 -rw-r--r-- 1 root root 0 May 30 14:11 file2 -rw-r--r-- 1 root root 0 May 30 14:11 file3
ls: cannot access 'file1 file2 file3': No such file or directory
|
转义符也提供了一种可以撰写多行命令的方式,因为它将之后的换行符进行了转义,使得跨越多行的命令最终都在一行上。
1 2
| (cd /source/directory && tar cf - . ) | \ (cd /dest/directory && tar xpvf -)
|
当然由于 bash 脚本中,如果以 |
作为一行的结束字符,那么不需要加转义符 \
也可以写多行命令。但是好的编写习惯还是在编写多行命令时,始终使用行尾转义符 \
。
1 2 3
| tar cf - -C /source/directory . | tar xpvf - -C /dest/directory
|
退出与退出状态
exit 用来退出脚本,它可以返回一个值给父进程。每个命令都有退出状态,命令执行成功返回 0,如果返回非 0,通常被认为是一个错误代码。好的 UNIX 命令、程序在正常退出时都会返回一个 0 退出码(当然也有例外)。
脚本中的函数也会返回一个退出状态,在脚本或者脚本函数中执行的最后命令会决定它们的退出状态。当脚本以不带参数的 exit 来结束时,脚本的退出状态也是由最后执行的命令决定(exit 命令之前)。
因此如下三种形态,其效果都是一样的,都是以最后执行的命令(exit 命令之前)来决定退出状态:
1 2 3
| command_1 ... command_last
|
1 2 3 4
| command_1 ... command_last exit $?
|
1 2 3 4
| command_1 ... command_last exit
|
$?
用于返回上一个命令的退出状态,在函数返回后,$?
也可以给出函数的退出状态,这也是函数的返回值。在管道执行后,$?
给出的是最后执行的那条命令的退出状态。
使用 ! comand
可以反转 command 的退出状态。注意 !
和 command
之间有一个空格,否则 !command
将会触发 Bash 的历史机制,显示这个命令的调用历史。
1 2 3 4 5 6 7 8
| hello
0
lsddkba: command not found
127
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
0
1
lsdabl: command not found
127
lsdabl: command not found
0
|
某些退出码具有保留含义,不应该在脚本中重新定义这些退出码。