Shell 传递参数详解(新手详细版)
本文档面向零基础新手,从“脚本和函数怎么拿到传入的参数”讲起,详细说明*位置参数、$# / $ / $@、默认值、引号、shift、传参给函数**等,并配有大量示例。
一、参数从哪里来?
1.1 脚本的参数(命令行传入)
在终端里执行脚本时,脚本名后面用空格分隔的每一段,就是传给脚本的参数:
./my_script.sh 第一个参数 第二个参数 第三个参数
- 脚本内部用 $1、$2、$3、… 来取这些参数。
- $0 是脚本名(或调用时的命令名),不是第 1 个参数。
1.2 函数的参数(调用时传入)
在脚本里调用函数时,函数名后面跟的内容就是传给该函数的参数;函数内部同样用 $1、$2、… 来取(此时 $1、$2 只在该函数内有效,不会改变脚本级的 $1、$2)。
my_func() {
echo "函数收到的第1个参数: $1"
}
my_func "你好"
# 函数收到的第1个参数: 你好
下面先以脚本参数为主讲,$1、$2、$#、$@ 等在函数里用法相同,只是作用域在函数内。
二、位置参数:$0、$1、$2、$3、…
2.1 含义
| 变量 | 含义 |
|---|---|
| $0 | 当前脚本名(或 Shell 名),不是第 1 个参数 |
| $1 | 第 1 个参数 |
| $2 | 第 2 个参数 |
| $3、$4、… | 第 3、4、… 个参数 |
| ${10}、${11}、… | 第 10、11、… 个参数(超过 9 必须加大括号) |
示例脚本 args_demo.sh:
#!/bin/bash
echo "脚本名: $0"
echo "第1个参数: $1"
echo "第2个参数: $2"
echo "第3个参数: $3"
执行:
./args_demo.sh one two three
输出:
脚本名: ./args_demo.sh
第1个参数: one
第2个参数: two
第3个参数: three
2.2 第 10 个及以后的参数要加 {}
$10 在某些 Shell 里会被当成 $1 后面跟字符 0,所以第 10、11、… 个参数要写成 ${10}、${11}:
echo "${10}"
2.3 参数不足时
若调用时没有传那么多参数,对应的 $n 就是空(未定义),不会报错。所以脚本里经常要先检查参数个数再使用。
./args_demo.sh 只有两个
# 第3个参数: (空)
三、参数个数:$
$# 表示参数的个数(不包含 $0)。
#!/bin/bash
echo "一共传了 $# 个参数"
./script.sh a b c
# 一共传了 3 个参数
./script.sh
# 一共传了 0 个参数
典型用法:检查是否传了足够参数
if [ $# -lt 1 ]; then
echo "用法: $0 <文件名>"
exit 1
fi
echo "第一个参数: $1"
四、所有参数:$* 与 $@
4.1 不带引号时
**$* 和 $@** 在不加引号时,效果类似:都会把各个参数展开成多个“词”,中间用空格隔开。
#!/bin/bash
echo "用 $*: $*"
echo "用 $@: $@"
./script.sh a b c
# 用 $*: a b c
# 用 $@: a b c
4.2 带双引号时的关键区别
- “$“:把所有参数拼成一个字符串,用 $IFS 的第一个字符(默认空格)连接。
所以 “$“ 始终是一个词。 - “$@”:保持每个参数各自一个词;参数 1、2、3 分别对应 “词1″、”词2″、”词3″,即使某参数里含空格也是如此。
示例:
# 假设执行: ./script.sh "a b" c
# 即第1个参数是 "a b"(一个),第2个参数是 "c"(一个)
echo "参数个数: $#"
# 参数个数: 2
# 用 "$*":整句当成一个字符串
printf ">[%s]n" "$*"
# >[a b c] # 一个整体
# 用 "$@":每个参数一个
printf ">[%s]n" "$@"
# >[a b] # 第1个参数
# >[c] # 第2个参数
4.3 何时用哪个?
- 需要“原样把参数传给另一条命令”时,用 “$@”,这样每个参数保持独立,不会被拆坏。
例如:some_cmd "$@" - 需要“把所有参数当成一整段文字”时,用 “$“ 或 “$“(例如拼成一句提示语)。
示例:包装一条命令并透传参数
#!/bin/bash
echo "即将执行: $@"
real_cmd "$@" # 正确:每个参数原样传给 real_cmd
# real_cmd "$*" # 错误:所有参数会变成一个大字符串
五、参数默认值(变量展开)
当参数可能“没传”时,可以用下面几种写法给出默认值或报错。
这里用 $1 举例,对 $2、$3 等同样适用。
5.1 ${1:-默认值}:未设或为空则用默认值
若 $1 未设置或为空,就用“默认值”;不改变 $1 本身。
name=${1:-"访客"}
echo "你好,$name"
./script.sh
# 你好,访客
./script.sh 张三
# 你好,张三
5.2 ${1:=默认值}:未设或为空则赋值并用默认值
若 $1 未设置或为空,则给位置参数 1 赋值为“默认值”(在子 Shell 里对 $1 的赋值可能不保留,脚本里更常用于普通变量)。
# 常用于普通变量,例如
: ${COUNT:=0}
5.3 ${1:?错误信息}:未设或为空则报错并退出
若 $1 未设置或为空,打印“错误信息”到标准错误并退出(非交互 Shell 通常退出码非 0)。
file=${1:?"请提供文件名"}
# 若没传 $1,会打印:script: 1: 请提供文件名(并退出)
5.4 ${1:+有值时的替换}:有值才替换
若 $1 已设置且非空,则用“有值时的替换”;否则为空。
多用于“有参数就多打印一段”这类逻辑。
msg=${1:+"你传了: $1"}
echo "${msg:-没有传参}"
5.5 小结表
| 写法 | 条件($1 未设或空) | 条件($1 有值) |
|---|---|---|
| ${1:-default} | 展开为 default | 展开为 $1 |
| ${1:=default} | 赋值为 default 并展开 | 展开为 $1 |
| ${1:?msg} | 报错 msg 并退出 | 展开为 $1 |
| ${1:+value} | 展开为空 | 展开为 value |
六、shift:左移参数列表
shift [n] 会把位置参数整体左移 n 个(默认 n=1):原来的 $2 变成 $1,$3 变成 $2,…;$# 会减少。
常用于循环处理“每取一个参数用掉一个”。
#!/bin/bash
while [ $# -gt 0 ]; do
echo "当前第1个参数: $1"
shift
done
./script.sh A B C
# 当前第1个参数: A
# 当前第1个参数: B
# 当前第1个参数: C
shift 2:一次丢掉前两个参数。
echo "1=$1 2=$2 3=$3"
shift 2
echo "1=$1 2=$2 3=$3"
# 若原为 a b c,第一次 1=a 2=b 3=c,shift 2 后 1=c 2= 3=
七、引号:为什么要把参数加引号
7.1 参数里含空格
若参数中有空格,在调用时必须用引号包起来,否则会被拆成多个参数:
./script.sh hello world
# $1=hello $2=world (两个参数)
./script.sh "hello world"
# $1=hello world (一个参数)
7.2 在脚本里使用 $1、$2 时加引号
在脚本里写 “$1”、“$2”,可以保证即使用户传了空或含空格的参数,也不会被 Shell 再拆成多个词或触发路径展开。
# 危险:若 $1 为空,会变成 [ -f ],语法错误
if [ -f $1 ]; then
# 安全:若 $1 为空,是 [ -f "" ]
if [ -f "$1" ]; then
建议:只要把“参数”当整体用,就写成 “$1”、“$@” 等。
八、函数如何接收参数
8.1 函数有自己的 $1、$2、$#、$@
在函数内部,*$1、$2、$#、$、$@ 指的是本次调用**传入函数的参数,不会改变脚本级的 $1、$2。
show() {
echo "函数内: 1=$1 2=$2 #=$#"
}
show one two
# 函数内: 1=one 2=two #=2
# 脚本级的 $1、$2 不受影响(若脚本有参数的话)
echo "脚本级: 1=$1 2=$2"
8.2 把脚本参数“原样”传给函数
用 “$@” 把当前脚本的所有参数传给函数:
my_func() {
echo "函数收到: $@"
}
my_func "$@"
九、实用示例
9.1 检查必须参数并给出用法
if [ $# -lt 1 ]; then
echo "用法: $0 <文件名> [选项]"
exit 1
fi
file=$1
shift
# 剩余参数在 $@ 里,可继续处理
9.2 带默认值的脚本
name=${1:-"未知"}
echo "用户: $name"
9.3 遍历所有参数
for arg in "$@"; do
echo "参数: $arg"
done
9.4 用 shift 逐个处理
while [ $# -gt 0 ]; do
echo "处理: $1"
# 对 $1 做操作...
shift
done
9.5 简单“选项 + 参数”解析(不涉及 getopts)
# 期望: ./script -o out.txt in1.txt in2.txt
outfile=""
while [ $# -gt 0 ]; do
case "$1" in
-o)
outfile=$2
shift 2
;;
*)
echo "输入文件: $1"
shift
;;
esac
done
十、常见坑与建议
- 第 10 个参数写 ${10}:不要写 $10。
- 透传参数用 “$@”:不要用 $ 或 “$“,否则多词参数会变一个字符串。
- 使用参数时加引号:写 “$1”、“$2”,避免空或含空格时出错。
- 先判断 $# 再取 $1:避免“没传参却用了 $1”的逻辑错误。
- $0 是脚本名:不是第 1 个参数;打印用法时常用 $0。
十一、速查表
| 变量 | 含义 |
|---|---|
| $0 | 脚本名(或命令名) |
| $1, $2, …, ${10}, … | 第 1、2、…、10、… 个参数 |
| $# | 参数个数 |
| $* | 所有参数(无引号时展开;”$*” 为一大串) |
| $@ | 所有参数(”$@” 保持每个参数独立,透传用) |
| ${1:-default} | $1 空时用 default |
| ${1:?msg} | $1 空时报错并退出 |
| shift [n] | 参数列表左移 n 个 |
十二、小结
- 脚本参数:命令行中脚本名后面的内容,在脚本里用 $1、$2、…、$0(脚本名)、$#。
- $ 与 $@:透传、循环时用 “$@”;需要“一整句”时用 “$“。
- 默认值:${1:-默认}、${1:?错误信息} 等;超过 9 用 ${10}。
- shift:左移参数,便于循环处理;引号:调用时含空格要加引号,脚本里用 “$1”、“$@”。
- 函数内部有自己的 $1、$2、$#、$@;传脚本参数给函数用 函数名 “$@”。
建议多写几个小脚本:检查 $#、用 “$1″、遍历 “$@”、shift 循环、默认值,再结合 Shell函数.md、Shell流程控制.md 一起练,参数传递就能用熟。
文档以 Bash 为准;sh 或其它 Shell 在 ${10}、部分展开上可能略有差异。