Appearance
02|变量与引号
上一篇的脚本里用了 $(whoami) 把命令输出塞进字符串。但很多时候要存的不止这些——要存用户名、路径、要清理的目录、报警阈值。这些都要用变量。
变量看起来简单,但 Shell 里的变量和引号有个坑:同一行命令,加不加引号、用单引号还是双引号,结果可能完全不同。本篇讲变量赋值和引号规则,再讲一个让脚本更安全的开关 set -euo pipefail。
一、变量赋值
变量赋值用等号,注意等号两边不能有空格:
bash
user="deploy"
count=5
path="/var/log/nginx"这和 Python 等语言不一样。user = "deploy"(带空格)在 Shell 里会被当成一条命令:执行 user 这个命令,参数是 = 和 "deploy"。带空格的赋值是初学最常见的错误。
引用变量用 $:
bash
echo $user
echo "清理目录: $path"更稳的写法是给变量名加花括号 ${user},区分变量名边界:
bash
echo "${user}home" # 引用 user 变量,后面接 home
echo "$userhome" # 引用 userhome 变量(多半没定义,得到空)二、单引号和双引号的区别
这是 Shell 里最容易踩的坑。看一个对比:
bash
name="world"
echo "hello $name" # 双引号
echo 'hello $name' # 单引号text
hello world
hello $name双引号里 $name 会展开成变量的值,单引号里 $name 原样输出。
规律:双引号允许变量展开和命令替换,单引号里一切都是字面值。
什么时候用哪个?要展开变量用双引号,要原样输出(正则、含特殊字符的字符串)用单引号。
三、不加引号的陷阱
变量引用时不加引号也能跑,但有空格就出问题:
bash
path="/var/log/nginx access"
ls $path # 不加引号
# ls: cannot access '/var/log/nginx': No such file or directory
# ls: cannot access 'access': No such file or directory$path 不加引号时,Shell 把它按空格拆成两个词 /var/log/nginx 和 access,ls 收到两个参数。加了引号就是一个整体:
bash
ls "$path" # 加双引号,当成一个路径规律:引用变量时养成加双引号的习惯,尤其是路径、文件名这类可能含空格的值。不加引号一旦值里有空格或特殊字符,脚本行为就不可控。
四、特殊变量
Shell 有一些预定义的特殊变量,常用这几个:
| 变量 | 含义 |
|---|---|
$? | 上一条命令的退出码(0 成功,非 0 失败) |
$0 | 脚本名 |
$1 $2 ... | 脚本的参数 |
$# | 参数个数 |
$@ | 所有参数 |
$$ | 当前脚本的进程号 |
$! | 最近一个后台进程的进程号 |
$? 最常用——判断上一条命令成功没:
bash
mkdir /tmp/backup
if [ $? -ne 0 ]; then
echo "创建目录失败"
exit 1
fi参数相关的($0-$@)下一篇专门讲。
五、set -euo pipefail:让脚本更安全
看一段脚本:
bash
#!/usr/bin/env bash
cd /data/backup
rm -rf *.tmp如果 /data/backup 不存在,cd 失败,但脚本不会停——继续执行 rm -rf *.tmp,在当前目录(可能是 /root)删掉所有 .tmp 文件。这就是脚本"该停不停"的事故源。
set -euo pipefail 是脚本的安全开关,放在 shebang 后面:
bash
#!/usr/bin/env bash
set -euo pipefail
cd /data/backup
rm -rf *.tmp三个选项各管一件事:
| 选项 | 作用 | 不加的后果 |
|---|---|---|
-e | 命令失败立即退出 | 脚本继续往下跑,可能在错误状态下执行后续命令 |
-u | 引用未定义变量报错 | 未定义变量得到空串,悄悄出错难排查 |
-o pipefail | 管道中任一命令失败则整个失败 | 只看最后一个命令的退出码,前面的失败被吞 |
逐个看:
-e:cd 失败就退出,不会继续跑 rm。避免"在错误状态下继续执行"。
-u:变量名拼错($usr 写成 $user 的反例)时直接报错退出,而不是得到空值悄悄继续。
bash
set -u
echo "$usr" # usr 未定义,报错退出:usr: unbound variable-o pipefail:默认管道只看最后一个命令的退出码。grep error /var/log/messages | head 里 grep 没找到返回非 0,但 head 成功返回 0,整条管道看起来成功。pipefail 让管道任一段失败都算失败。
生产脚本的标准开头就是这个组合:
bash
#!/usr/bin/env bash
set -euo pipefail不是所有脚本都该加 -e——有些命令失败是预期的(比如 grep 没匹配到返回 1),加了 -e 会让脚本退出。这种情况用 || true 显式说明"失败也继续":
bash
grep "error" /var/log/messages || true六、变量的作用域
默认 Shell 变量是当前 shell 的局部变量。脚本里定义的变量,脚本结束就没了。但如果脚本里调用另一个脚本,变量默认传不过去:
bash
# a.sh
name="deploy"
bash b.sh # b.sh 里看不到 name要让变量被子进程看到,用 export:
bash
# a.sh
export name="deploy"
bash b.sh # b.sh 里能读到 name函数里的局部变量用 local,避免污染外层。这个在函数篇讲。
学会了之后
变量和引号是 Shell 脚本的基础——存值、引用值、控制变量展开。set -euo pipefail 让脚本在出错时停下,是生产脚本的安全底线。
下一篇讲命令替换和退出码——怎么把命令的输出存进变量、怎么用退出码做逻辑判断。