Skip to content

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/nginxaccessls 收到两个参数。加了引号就是一个整体:

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管道中任一命令失败则整个失败只看最后一个命令的退出码,前面的失败被吞

逐个看:

-ecd 失败就退出,不会继续跑 rm。避免"在错误状态下继续执行"。

-u:变量名拼错($usr 写成 $user 的反例)时直接报错退出,而不是得到空值悄悄继续。

bash
set -u
echo "$usr"      # usr 未定义,报错退出:usr: unbound variable

-o pipefail:默认管道只看最后一个命令的退出码。grep error /var/log/messages | headgrep 没找到返回非 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 让脚本在出错时停下,是生产脚本的安全底线。

下一篇讲命令替换和退出码——怎么把命令的输出存进变量、怎么用退出码做逻辑判断。