Shell传递参数

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

十、常见坑与建议

  1. 第 10 个参数写 ${10}:不要写 $10。
  2. 透传参数用 “$@”:不要用 $ 或 “$“,否则多词参数会变一个字符串。
  3. 使用参数时加引号:写 “$1”“$2”,避免空或含空格时出错。
  4. 先判断 $# 再取 $1:避免“没传参却用了 $1”的逻辑错误。
  5. $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函数.mdShell流程控制.md 一起练,参数传递就能用熟。


文档以 Bash 为准;sh 或其它 Shell 在 ${10}、部分展开上可能略有差异。

发表评论