Shell 特殊变量完全指南:$$、$?、$@、$# 等用法详解

Bash Shell 特殊变量完全指南,详解 $$、$!、$?、$-、$*、$@、$#、$0 等变量的含义和实际用法。包含 $* 与 $@ 的区别对比、退出状态码说明、完整示例代码和最佳实践。

Bruce

shellbashlinux脚本特殊变量

Linux

838 Words

2026-01-22 07:47 +0000


Shell 特殊变量完全指南,掌握 Bash 脚本的核心知识

编写 Shell 脚本时,你一定见过 $?$@$$ 这些以美元符号开头的特殊变量。它们是 Bash 内置的特殊参数,用于获取脚本运行状态、命令行参数等关键信息。掌握这些变量,是写出健壮 Shell 脚本的基础。

本文将系统讲解每个特殊变量的含义、使用场景,并通过实际示例帮助你深入理解。

一、特殊变量速查表

先来看一个快速参考表,方便日后查阅:

变量含义示例值
$0当前脚本的文件名./test.sh
$1~$9第 1~9 个位置参数arg1
${10}第 10 个及以后的参数(需要大括号)arg10
$#传入参数的个数(不含 $03
$*所有参数,作为一个字符串"arg1 arg2 arg3"
$@所有参数,作为独立字符串数组"arg1" "arg2" "arg3"
$?上一条命令的退出状态码0(成功)
$$当前 Shell 进程的 PID12345
$!最近一个后台进程的 PID12346
$-当前 Shell 的选项标志himBHs
$_上一条命令的最后一个参数/path/to/file

记忆技巧:$## 像是在数数(count),$?? 像是在问"结果如何",$$ 两个 $ 表示"我自己的身份"。


二、位置参数详解

1. $0 - 脚本名称

$0 保存当前执行脚本的名称(包含路径)。

#!/bin/bash
echo "脚本名称: $0"

运行结果:

$ ./scripts/deploy.sh
脚本名称: ./scripts/deploy.sh

$ bash /home/user/scripts/deploy.sh
脚本名称: /home/user/scripts/deploy.sh

应用场景:打印帮助信息时显示正确的脚本名。

usage() {
    echo "用法: $0 [选项] <参数>"
    echo "示例: $0 -f config.yml"
}

2. $1~$9 和 ${n} - 位置参数

$1$9 分别表示传入脚本的第 1 到第 9 个参数。超过 9 个时,必须用 ${10}${11} 这种大括号形式。

#!/bin/bash
echo "第一个参数: $1"
echo "第二个参数: $2"
echo "第十个参数: ${10}"

注意:如果写成 $10,Shell 会解析为 $1 加上字符 0,而不是第 10 个参数。

3. $# - 参数个数

$# 返回传入参数的数量,不包括 $0

#!/bin/bash
echo "参数个数: $#"

if [ $# -lt 2 ]; then
    echo "错误: 至少需要 2 个参数"
    exit 1
fi

运行结果:

$ ./test.sh a b c
参数个数: 3

$ ./test.sh
参数个数: 0

三、$* 与 $@ 的区别(重点)

终端中运行 Shell 脚本的示例

这两个变量都表示"所有参数",但在加引号时行为不同。这是 Shell 脚本面试中的经典问题。

不加引号时:行为相同

#!/bin/bash
echo "使用 \$*:"
for arg in $*; do
    echo "  - $arg"
done

echo "使用 \$@:"
for arg in $@; do
    echo "  - $arg"
done

运行 ./test.sh "hello world" foo bar

使用 $*:
  - hello
  - world
  - foo
  - bar
使用 $@:
  - hello
  - world
  - foo
  - bar

不加引号时,包含空格的参数 "hello world" 被拆分成两个词。

加引号时:关键区别

#!/bin/bash
echo "使用 \"\$*\":"
for arg in "$*"; do
    echo "  [$arg]"
done

echo "使用 \"\$@\":"
for arg in "$@"; do
    echo "  [$arg]"
done

运行 ./test.sh "hello world" foo bar

使用 "$*":
  [hello world foo bar]
使用 "$@":
  [hello world]
  [foo]
  [bar]

区别总结

形式结果说明
$*hello world foo bar所有参数拆分为单词
$@hello world foo bar所有参数拆分为单词
"$*""hello world foo bar"一个字符串
"$@""hello world" "foo" "bar"三个独立字符串

最佳实践:几乎所有情况下都应该使用 "$@",它能正确保留参数中的空格和特殊字符。


四、进程相关变量

1. $$ - 当前进程 PID

$$ 返回当前 Shell 脚本运行时的进程 ID,常用于创建临时文件。

#!/bin/bash
TEMP_FILE="/tmp/myapp_$$.tmp"
echo "临时文件: $TEMP_FILE"
echo "数据内容" > "$TEMP_FILE"

# 脚本结束时清理
trap "rm -f $TEMP_FILE" EXIT

为什么用 PID? 保证文件名唯一,避免多个脚本实例冲突。

2. $! - 后台进程 PID

$! 保存最近一个后台执行命令的进程 ID。

#!/bin/bash
# 启动后台任务
long_running_task &
TASK_PID=$!

echo "后台任务 PID: $TASK_PID"

# 等待任务完成
wait $TASK_PID
echo "任务已完成"

应用场景:管理后台进程、实现超时控制。

#!/bin/bash
# 带超时的命令执行
slow_command &
PID=$!

# 5秒后检查
sleep 5
if kill -0 $PID 2>/dev/null; then
    echo "命令执行超时,终止进程"
    kill $PID
fi

五、退出状态 $?

$? 保存上一条命令的退出状态码(Exit Code),是脚本流程控制的核心。

状态码含义

状态码含义
0命令执行成功
1通用错误
2命令使用错误(如参数错误)
126命令存在但无执行权限
127命令不存在
128+N被信号 N 终止
130被 Ctrl+C 中断(128+2)
255退出码超出范围

实际应用

#!/bin/bash
# 检查命令是否成功
grep "error" /var/log/app.log
if [ $? -eq 0 ]; then
    echo "发现错误日志"
else
    echo "没有错误"
fi

# 更简洁的写法
if grep -q "error" /var/log/app.log; then
    echo "发现错误日志"
fi

设置脚本退出码

使用 exit 命令设置脚本的退出状态码:

#!/bin/bash
if [ ! -f "$1" ]; then
    echo "错误: 文件不存在" >&2
    exit 1
fi

# 正常执行
process_file "$1"
exit 0

六、Shell 选项 $-

$- 显示当前 Shell 启用的选项标志。

$ echo $-
himBHs

常见标志含义:

标志含义
hhashall - 记住命令位置
iinteractive - 交互式 Shell
mmonitor - 作业控制
Bbraceexpand - 启用花括号展开
Hhistexpand - 启用历史展开
sstdin - 从标准输入读取命令

应用场景:判断脚本是否在交互式 Shell 中运行。

#!/bin/bash
case $- in
    *i*) echo "交互式 Shell" ;;
    *)   echo "非交互式 Shell" ;;
esac

七、完整示例脚本

下面是一个综合运用特殊变量的实用脚本:

#!/bin/bash
# 文件: show_vars.sh
# 说明: 演示 Shell 特殊变量的用法

echo "===== 基本信息 ====="
echo "脚本名称: $0"
echo "进程 PID: $$"
echo "参数个数: $#"
echo "Shell 选项: $-"

echo ""
echo "===== 位置参数 ====="
echo "第一个参数: ${1:-'(空)'}"
echo "第二个参数: ${2:-'(空)'}"
echo "第三个参数: ${3:-'(空)'}"

echo ""
echo '===== $* vs $@ ====='
echo "使用 \"\$*\":"
for arg in "$*"; do
    echo "  -> [$arg]"
done

echo "使用 \"\$@\":"
for arg in "$@"; do
    echo "  -> [$arg]"
done

echo ""
echo "===== 退出状态演示 ====="
ls /nonexistent 2>/dev/null
echo "ls 不存在目录的退出码: $?"

ls / >/dev/null
echo "ls 存在目录的退出码: $?"

echo ""
echo "===== 后台进程 ====="
sleep 1 &
echo "后台进程 PID: $!"
wait
echo "后台进程已结束"

运行结果:

$ ./show_vars.sh "hello world" foo bar

===== 基本信息 =====
脚本名称: ./show_vars.sh
进程 PID: 28547
参数个数: 3
Shell 选项: hB

===== 位置参数 =====
第一个参数: hello world
第二个参数: foo
第三个参数: bar

===== $* vs $@ =====
使用 "$*":
  -> [hello world foo bar]
使用 "$@":
  -> [hello world]
  -> [foo]
  -> [bar]

===== 退出状态演示 =====
ls 不存在目录的退出码: 2
ls 存在目录的退出码: 0

===== 后台进程 =====
后台进程 PID: 28548
后台进程已结束

八、最佳实践

1. 始终用 “$@” 而不是 $*

# 推荐
for arg in "$@"; do
    process "$arg"
done

# 不推荐
for arg in $*; do
    process "$arg"
done

2. 检查参数数量

if [ $# -eq 0 ]; then
    echo "用法: $0 <文件名>" >&2
    exit 1
fi

3. 使用有意义的退出码

readonly E_SUCCESS=0
readonly E_NO_ARGS=1
readonly E_FILE_NOT_FOUND=2

if [ $# -eq 0 ]; then
    exit $E_NO_ARGS
fi

4. 临时文件使用 $$ 确保唯一

TMPFILE="/tmp/${0##*/}.$$"
trap "rm -f $TMPFILE" EXIT

总结

Shell 特殊变量是脚本编程的基础工具:

  • 位置参数$0~$9$#$@)用于处理命令行输入
  • 进程变量$$$!)用于进程管理和临时文件
  • 状态变量$?)用于流程控制和错误处理
  • "$@" 几乎总是比 $* 更安全的选择

掌握这些变量,你就能写出更健壮、更专业的 Shell 脚本。


相关阅读

参考资料

Comments

Join the discussion — requires a GitHub account