36.1 变量赋值
在 Bash 中,变量赋值时等号两边千万不能有空格。正确的写法是 a=123,而不是 a = 123。如果将等号两边加上空格,Bash 会将其理解为执行命令 a,并将 = 和 123 作为参数传递给它。当然,这种写法在字符串比较的语法中也会出现,但从本质上讲,它是 [ 或 [[ 这类测试命令的参数。
36.2 $@
$@ 会展开为所有命令行参数,并且每个参数都作为独立的单词处理。这一点和 $* 不同——$* 会将所有参数合并成一个单一的单词。
下面通过示例来说明。假设有一个脚本,用两个参数调用:
$ ./script.sh "1 2" "3 4"
此时 $* 将展开为 1 2 3 4,因此下面的循环:
for var in $*; do # 等同于 for var in $@
echo "<$var>"
done
将输出:
<1>
<2>
<3>
<4>
而如果使用 "$*",它会展开为 "1 2 3 4",因此循环:
for var in "$*"; do
echo "<$var>"
done
将只调用一次 echo,并输出:
<1 2 3 4>
但若使用 "$@",它将展开为 "$1" "$2",即 "1 2" "3 4",因此循环:
for var in "$@"; do
echo "<$var>"
done
将输出:
<1 2>
<3 4>
这样既保留了每个参数内部的空格,又保持了参数间的分隔。需要留意的是,for var in "$@"; do ... 这种结构非常常见,并且是习惯用法;实际上它也是 for 循环的默认行为,可以简写为 for var; do ...。
36.3 $
$# 用于获取命令行参数(或位置参数)的个数。示例脚本:
#!/bin/bash
echo $#
当用三个参数运行时,上述脚本将输出:
$ ./testscript.sh firstarg secondarg thirdarg
3
36.4 $HISTSIZE
HISTSIZE 变量记录了 Bash 历史中记住的命令最大数量。
$ echo $HISTSIZE
1000
36.5 $FUNCNAME
FUNCNAME 可以获取当前函数的名称。在函数内部:
my_function() {
echo "This function is $FUNCNAME" # 输出:This function is my_function
}
如果在函数外部使用,则返回空值:
echo "This function is $FUNCNAME" # 输出:This function is
36.6 $HOME
HOME 是当前用户的主目录。
$ echo $HOME
/home/user
36.7 $IFS
IFS 包含了内部字段分隔符的字符串,Bash 在循环等操作中会用它来分割字符串。默认值是空白字符:换行 \n、制表符 \t 和空格。将其更改为其他字符,就可以用不同的字符来分割字符串:
IFS=","
INPUTSTR="a,b,c,d"
for field in $INPUTSTR; do
echo $field
done
输出:
a
b
c
d
注意:这也就是所谓的“单词分割”。
36.8 $OLDPWD
OLDPWD 记录的是上一次执行 cd 命令之前的目录。
$ cd directory
directory> $ echo $OLDPWD
/home/user
36.9 $PWD
PWD 是当前所在的工作目录。
$ echo $PWD
/home/user
$ cd directory
directory> $ echo $PWD
/home/user/directory
36.10 $1、$2、$3 等
这些是传递给脚本或函数的位置参数,依次从命令行获取。
#!/bin/bash
# $n 是第 n 个位置参数
echo "$1"
echo "$2"
echo "$3"
运行示例:
$ ./testscript.sh firstarg secondarg thirdarg
firstarg
secondarg
thirdarg
如果位置参数的数量超过 9,必须使用花括号,比如 ${10}。例如:
set -- 1 2 3 4 5 6 7 8 nine ten eleven twelve
# 下面这行将输出 10(由 $1 的值 1 与后面的 0 拼接),而不是第10个参数
echo $10 # 实际被解析为 ${1}0,即 "10"
echo ${10} # 正确输出:ten
更清晰的演示:
set -- arg{1..12}
echo $10 # 输出:arg10(${1}0 -> arg10)
echo ${10} # 输出:arg10(第10个参数)
36.11 $*
$* 将所有位置参数作为一个单一的字符串返回。
testscript.sh:
#!/bin/bash
echo "$*"
运行并传入多个参数:
./testscript.sh firstarg secondarg thirdarg
输出:
firstarg secondarg thirdarg
36.12 $!
$! 保存着最后一个放入后台执行的作业的进程 ID(PID)。
$ ls &
testfile1 testfile2
[1]+ Done ls
$ echo $!
21715
36.13 $?
$? 是上一个执行的函数或命令的退出状态。通常 0 表示成功,其他非零值表示失败。
$ ls *.blah; echo $?
ls: cannot access *.blah: No such file or directory
2
$ ls; echo $?
testfile1 testfile2
0
36.14 $$
$$ 是当前进程的进程 ID(PID)。
$ echo $$
13246
36.15 $RANDOM
每次引用 $RANDOM 都会生成一个介于 0 和 32767 之间的随机整数。若对该变量赋值,则会为随机数生成器设定种子。
$ echo $RANDOM
27119
$ echo $RANDOM
1349
36.16 $BASHPID
BASHPID 是当前 Bash 实例的进程 ID。它与 $$ 变量有所不同,但在多数情况下结果相同。这是 Bash 4 新增的特性,在 Bash 3 中不可用。
$ echo "$$ pid = $$ BASHPID = $BASHPID"
$$ pid = 9265 BASHPID = 9265
36.17 $BASH_ENV
BASH_ENV 是一个环境变量,指向 Bash 的启动文件,该文件在脚本被调用时会被读取。
36.18 $BASH_VERSINFO
BASH_VERSINFO 是一个数组,包含了 Bash 的完整版本信息,拆分为多个元素。如果只需要主版本号,用它比用 $BASH_VERSION 方便得多。
$ for ((i=0; i<6; i++)); do echo "BASH_VERSINFO[$i] = ${BASH_VERSINFO[$i]}"; done
BASH_VERSINFO[0] = 3
BASH_VERSINFO[1] = 2
BASH_VERSINFO[2] = 25
BASH_VERSINFO[3] = 1
BASH_VERSINFO[4] = release
BASH_VERSINFO[5] = x86_64-redhat-linux-gnu
36.19 $BASH_VERSION
BASH_VERSION 显示正在运行的 Bash 的版本,你可以据此决定是否能够使用某些高级特性。
$ echo $BASH_VERSION
4.1.2(1)-release
36.20 $EDITOR
EDITOR 是脚本或程序默认调用的编辑器,通常是 vi 或 emacs。
$ echo $EDITOR
vi
36.21 $HOSTNAME
HOSTNAME 是系统启动时分配的主机名。
$ echo $HOSTNAME
mybox.mydomain.com
36.22 $HOSTTYPE
HOSTTYPE 用于标识硬件类型,可用来决定执行哪些二进制文件。
$ echo $HOSTTYPE
x86_64
36.23 $MACHTYPE
与 $HOSTTYPE 类似,MACHTYPE 还包含了操作系统信息以及硬件信息。
$ echo $MACHTYPE
x86_64-redhat-linux-gnu
36.24 $OSTYPE
OSTYPE 返回机器上运行的操作系统类型信息。
$ echo $OSTYPE
linux-gnu
36.25 $PATH
PATH 是用于查找命令二进制文件的搜索路径,常见目录包括 /usr/bin 和 /usr/local/bin。
当用户或脚本尝试运行一个命令时,Shell 会按顺序搜索 $PATH 中的路径,直到找到具有执行权限的匹配文件。
$PATH 中的目录由 : 字符分隔。
$ echo $PATH
/usr/kerberos/bin:/usr/local/bin:/bin:/usr/bin
因此,给定上述 $PATH,如果在提示符下键入 ls,Shell 将依次查找 /usr/kerberos/bin/ls、/usr/local/bin/ls、/bin/ls、/usr/bin/ls,最后才得出没有此命令的结论。
36.26 $PPID
PPID 是脚本或 Shell 的父进程的进程 ID,也就是调用当前脚本或 Shell 的那个进程。
$ echo $$
13016
$ echo $PPID
13015
36.27 $SECONDS
SECONDS 记录脚本已运行的秒数。如果在 Shell 中直接查看,这个数字通常比较大。
$ echo $SECONDS
98834
36.28 $SHELLOPTS
SHELLOPTS 是一个现成的列表,包含 Bash 启动时提供的用于控制其行为的选项。
$ echo $SHELLOPTS
braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitor
36.29 $_
$_ 会输出上一个执行的命令的最后一个字段,这对将内容传递给另一个命令十分有用。
$ ls *.sh; echo $_
testscript1.sh testscript2.sh
testscript2.sh
如果它在任何其他命令之前被使用,则会给出脚本的路径。但请注意,这并不是获取脚本路径的可靠方法。
# test.sh:
#!/bin/bash
echo $_
输出:
$ ./test.sh
./test.sh
36.30 $GROUPS
GROUPS 是一个数组,包含当前用户所属组的编号。
#!/usr/bin/env bash
echo "You are assigned to the following groups:"
for group in "${GROUPS[@]}"; do
IFS=: read -r name dummy number members < <(getent group "$group")
printf "name: %-10s number: %-15s members: %s\n" "$name" "$number" "$members"
done
36.31 $LINENO
LINENO 输出当前脚本中的行号,主要用于调试脚本。
#!/bin/bash
# 这是第 2 行
echo something # 这是第 3 行
echo $LINENO # 将输出 4
36.32 $SHLVL
当你执行 bash 命令时,会打开一个新的 Shell。SHLVL 环境变量就保存了当前 Shell 运行在多少层 Shell 之上。
在一个全新的终端窗口中,执行以下命令可能会因 Linux 发行版不同而得到不同结果。
echo $SHLVL
在 Fedora 25 中,输出是 3。这表明,当打开一个新 Shell 时,一个初始的 bash 命令执行并完成一个任务,该初始的 bash 命令又执行一个子进程(另一个 bash 命令),而这个子进程又执行最终的 bash 命令来打开新 Shell。当新 Shell 打开时,它作为其他 2 个 Shell 进程的子进程在运行,因此输出为 3。
在下面的示例中(仍假设用户运行 Fedora 25),新 Shell 中的 $SHLVL 初始为 3。每执行一个 bash 命令,SHLVL 的值就会增加 1。
$ echo $SHLVL
3
$ bash
$ echo $SHLVL
4
$ bash
$ echo $SHLVL
5
可以看到,直接执行 bash 命令(或运行一个 Bash 脚本)会打开一个新的 Shell。而 source 一个脚本则是在当前 Shell 中运行代码。
下面是几个脚本的对比:
test1.sh:
#!/usr/bin/env bash
echo "Hello from test1.sh. My shell level is $SHLVL"
source "test2.sh"
test2.sh:
#!/usr/bin/env bash
echo "Hello from test2.sh. My shell level is $SHLVL"
run.sh:
#!/usr/bin/env bash
echo "Hello from run.sh. My shell level is $SHLVL"
./test1.sh
赋予执行权限并运行:
$ chmod +x test1.sh test2.sh run.sh
$ ./run.sh
输出:
Hello from run.sh. My shell level is 4
Hello from test1.sh. My shell level is 5
Hello from test2.sh. My shell level is 5
可见,test2.sh 被 source 引入,所以它继承了 test1.sh 的 Shell 层级,而不是另开一个新的。
36.33 $UID
UID 是一个只读变量,存储用户的 ID 号。
$ echo $UID
12345
本文由云栈社区整理提供,更多 Bash 脚本编程与运维实战技巧,欢迎访问 云栈社区 Bash 专题。