千家信息网

shell 命令行参数(基本)

发表于:2026-11-01 作者:千家信息网编辑
千家信息网最后更新 2026年11月01日,命令行参数$0 表示程序名。$1 至 $9则是位置参数。$# 表示参数的个数。$* 将所有参数当做一个整体来引用$@ 把每个参数作为一个字符串返回,可以使用for循环来遍历$? 最近一个执行的命令的退
千家信息网最后更新 2026年11月01日shell 命令行参数(基本)

命令行参数

$0 表示程序名。
$1 至 $9则是位置参数。
$# 表示参数的个数。
$* 将所有参数当做一个整体来引用
$@ 把每个参数作为一个字符串返回,可以使用for循环来遍历
$? 最近一个执行的命令的退出状态。0表示执行成功
$_ 上一个命令的最后一个参数。使用快捷键 ESC+. 也是这个效果

位置参数

位置参数不止9个,更多的参数也是一样支持的。只是要使用\${10}这样的形式引用。
\$1 和 \${1}的效果是一样的。
不用花括号的话,\$10 会被认为是 \$1 和一个字符 0。

带空格的参数值
每个参数都是用空格分隔的。要在参数值中包含空格,必须要使用引号(单引号或双引号都可)。

将文本字符串作为参数传递时,引号并非数据的一部分。它们只是表明数据的起止位置。

获取脚本名

\$0 表示脚本名,但是不同的调用方法返回的结果也是不同的。下面的脚本就是简单的打印\$0的值:

$ cat filename.sh #!/bin/bashecho $0$ ./ filename.sh-bash: ./: 是一个目录$ cat filename.sh #!/bin/bashecho $0$ ./filename.sh./filename.sh$ bash filename.sh filename.sh$ bash /root/filename.sh /root/filename.sh$ 

使用 basename 命令
如果要使用脚本名称来进行判断,可以先用命令 basename 把路径的信息给过滤掉。命令的效果如下:

$ basename /var/log/messages messages$ 

所以上面的脚本可以修改成这样:

$ cat filename.sh #!/bin/bashecho $(basename $0)$ ./filename.shfilename.sh$ bash filename.sh filename.sh$ bash /root/filename.sh filename.sh$ 

测试参数

在脚本中使用参数要确保参数存在,否则运行时有可能会报错:

$ cat add.sh #!/bin/bashecho $1 + $2 = $[ $1 + $2 ]$ ./add.sh 1 21 + 2 = 3$ ./add.sh 1./add.sh:行2: 1 +  : 语法错误: 期待操作数 (错误符号是 "+  ")$ 

如果只是当做字符串引用,也不会报错。没有传参的参数默认都是空:

$ cat hello.sh #!/bin/bashecho Hello $1 $2.$ ./hello.sh Tom JerryHello Tom Jerry.$ ./hello.sh JerryHello Jerry .$ ./hello.shHello .$ 

判断参数是否存在
在 shell 中利用 -n 来判定字符串非空,-z 则正好相反,空即是真。上面已经测试过了,未定义的参数默认是空:

$ cat hello.sh #!/bin/bashif [ -n "$1" ]then    echo Hello $1.else    echo Hello Nobody.fi$ ./hello.sh TomHello Tom.$ ./hello.shHello Nobody.$ 

这里的判断的 \$1 要加上双引号,否则会被认为是字符串。一个字符串当然非空,所以结果会永远为真。

判断参数的个数
上面的例子的脚本也可以通过判断参数数量是否大于0来实现:

$ cat hello.sh #!/bin/bashecho 参数数量: $#if [ $# -gt 0 ]then    echo Hello $1.else    echo Hello Nobody.fi$ ./hello.sh参数数量: 0Hello Nobody.$ ./hello.sh Tom参数数量: 1Hello Tom.$ ./hello.sh Tom Jerry参数数量: 2Hello Tom.$ 

这里 -gt 比较的是前后两个数字(INT),所以\$#是不加引号的。
用这种方法也能判断参数是否存在。两种方法,效果一样,不同的书上都看到有人使用。

这里是一样加法的例子,必须要传入2个参数:

$ cat add.sh #!/bin/bashif [ $# -eq 2 ]then    echo $1 + $2 = $[ $1 + $2 ]else    echo 需要参数: 2, 实际参数: $#.fi$ ./add.sh 1 21 + 2 = 3$ ./add.sh 1 2 3需要参数: 2, 实际参数: 3.$ ./add.sh 1需要参数: 2, 实际参数: 1.$ 

如果要表示不相等,就是 if [ $# -ne 2 ]

获取最后一个参数
这是一个使用 \$# 的小技巧。使用${$#}似乎就是参数的最后一个变量了。
但是其实不然,花括号里不能这样用\$,这里要把里面的换成感叹号:

$ cat hello.sh #!/bin/bashif [ $# -gt 0 ]then    echo Hello ${!#}.else    echo Hello Nobody.fi$ ./hello.sh Tom JerryHello Jerry.$ 

如果没有任何命令行参数,那么就是返回\$0,也就是脚本名。

上面感叹号的问题,效果是引用变量的值而不是变量自身。类似于指针的取值。把#号换成一个有名字的变量来说明比较直观:

$ cat parameter.sh #!/bin/bashparamater=keykey=valueecho "${paramater}"echo "${!paramater}"echo "${key}"$ ./parameter.sh keyvaluevalue$ 

不加感叹号,就是直接去该变量的值。加上感叹号,就是去变量值所对应的变量名的那个变量的值。

获取所有参数

\$* 和 \$@ 都是表示所有的字符串,但是在遍历的时候会有区别:

$ cat all.sh #!/bin/bashecho '$* 的效果:'count=1for i in "$*"do    echo $count: $i    count=$[ $count + 1 ]doneecho '$@ 的效果:'count=1for i in "$@"do    echo $count: $i    count=$[ $count + 1 ]done$ ./all.sh Oliver Barry Kara Sara Kane$* 的效果:1: Oliver Barry Kara Sara Kane$@ 的效果:1: Oliver2: Barry3: Kara4: Sara5: Kane$ 

\$*就一个整体的值,无法遍历。要遍历每一个变量要使用\$@。这里的双引号很重要。

不加引号的话,就是把 \$* 和 \$@ 的内容(变量解析后就是多个词)传递给for循环遍历,这样两个参数的效果是一样的。和直接传不加引号的字符串的效果一样。
加上引号,引号里的内容就是一个整体。如果是\$*,这个整体里的所有内容还是一个词,不会拆。如果是\$@,这个整体里会按空格拆分成多个词。
下面是演示的效果:

$ cat all2.sh #!/bin/bashecho '$* 不加引号的效果:'count=1for i in $*do    echo $count: $i    count=$[ $count + 1 ]doneecho '$@ 不加引号的效果:'count=1for i in $@do    echo $count: $i    count=$[ $count + 1 ]doneecho '直接遍历不加引号的字符的效果:'count=1for i in Oliver Barry Kara Sara Kanedo    echo $count: $i    count=$[ $count + 1 ]doneecho '加引号遍历的效果:'count=1for i in "Oliver Barry Kara Sara Kane"do    echo $count: $i    count=$[ $count + 1 ]done$ ./all2.sh Oliver Barry Kara Sara Kane$* 不加引号的效果:1: Oliver2: Barry3: Kara4: Sara5: Kane$@ 不加引号的效果:1: Oliver2: Barry3: Kara4: Sara5: Kane直接遍历不加引号的字符的效果:1: Oliver2: Barry3: Kara4: Sara5: Kane加引号遍历的效果:1: Oliver Barry Kara Sara Kane$ 

强调:特殊参数\$@一定要用在双引号内,效果是每个参数都扩展为分隔的单词。在使用for循环遍历的时候会体现出效果。

移动变量 shift

shift 命令能够用来操作命令行参数。默认情况下将每个参数向左移动一个位置。被移出的参数就被丢弃了,无法恢复。
先掌握这个命令的使用,使用这个命令可以方便地解析命令行参数。

使用示例

下面是一个简单的示例:

$ cat pop.sh #!/bin/bashcount=1while [ -n "$1" ]# while [ $# -ne 0 ]do    echo "$count: $1"    count=$[ $count + 1 ]    shiftdone$ ./pop.sh Oliver Barry Kara Sara Kane1: Oliver2: Barry3: Kara4: Sara5: Kane$ 

这里有2中判断方法来判断是否还有参数,效果是一样的。

移动多个位置

带参数执行shift,指明要移动几个位置就可以了:

$ cat pop.sh #!/bin/bashcount=1# while [ -n "$1" ]while [ $# -ne 0 ]do    if [ -n "$2" ]    then        echo "$count: $1, $2"        shift 2    else        echo "$count: $1"        shift    fi    count=$[ $count + 1 ]done$ ./pop.sh Oliver Barry Kara Sara Kane1: Oliver, Barry2: Kara, Sara3: Kane$ 

简单修改下上面的脚本,一次输出2个参数,然后移动2个位置。

处理选项

当shell脚本需要多个命令行参数时,在调用脚本的时候就必须将所有参数按固定的顺序。
或者还可以使用选项来指定参数的值。

case 配合 shift

这个例子里有带值的选项也有不带值的选项:

$ cat format.sh #!/bin/bashprefix=""    # 前缀base="test"  # 默认字符串suffix=""    # 后缀upper=off    # 是否大写# 解析命令行参数while [ -n "$1" ]do    case "$1" in        -a) suffix="$2"            shift ;;        -b) prefix="$2"            shift ;;        -s) base="$2"            shift ;;        -u) upper=on ;;         *) echo "$1 is not an option"            exit 1 ;;  # 发现未知参数,直接退出    esac    shiftdone# 添加前缀和后缀output="${prefix:+${prefix}_}${base}${suffix:+_${suffix}}"# 判断是否要全大写输出if [ $upper = on ]then    output=${output^^}fi# 输出结果echo "$output"$ ./format.sh -a aftertest_after$ ./format.sh -s hello -b beforbefor_hello$ ./format.sh -s hello -u -a after -b beforBEFOR_HELLO_AFTER$ ./format.sh -s hello -u -a after -b befor -l-l is not an option$ 

case语句找到一个选项就处理一个选项。如果还需要在命令行提供其他参数,可以在通用情况的处理部分中处理。而这里因为不需要提供任何参数,凡是解析不正确的就报告错误并退出(exit 1)。

能解析参数的版本
这个版本匹配所有的参数进行格式化输出:

$ cat format.sh #!/bin/bashprefix=""    # 前缀base="test"  # 默认字符串suffix=""    # 后缀upper=off    # 是否大写# 显示声明一下这是个数组变量,其实没有必要declare -a names  # 需要格式化输出的所有原始字符串# 解析命令行参数while [ -n "$1" ]do    case "$1" in        -a) suffix="$2"            shift ;;        -b) prefix="$2"            shift ;;        -s) base="$2"            shift ;;        -u) upper=on ;;         *) names=("${names[@]}" "$1") ;;    esac    shiftdonenames[0]=${names[0]:-$base}for name in "${names[@]}"do    # 添加前缀和后缀    output="${prefix:+${prefix}_}${name}${suffix:+_${suffix}}"    # 判断是否要全大写输出    if [ $upper = on ]    then        output=${output^^}    fi    # 输出结果    echo "$output"done$ $ ./format.sh -a after -b befor -u value1 value2 value3BEFOR_VALUE1_AFTERBEFOR_VALUE2_AFTERBEFOR_VALUE3_AFTER$ ./format.sh -a after after1 -b befor befor1 -u value1 value2 value3BEFOR_AFTER1_AFTERBEFOR_BEFOR1_AFTERBEFOR_VALUE1_AFTERBEFOR_VALUE2_AFTERBEFOR_VALUE3_AFTER$ ./format.sh -a after after1 -b befor befor1 -u -v value1 value2 value3BEFOR_AFTER1_AFTERBEFOR_BEFOR1_AFTERBEFOR_-V_AFTERBEFOR_VALUE1_AFTERBEFOR_VALUE2_AFTERBEFOR_VALUE3_AFTER$ 

看最后的两项的结果,提供的命令行参数有问题,但是程序无法发现。
倒数第二项可以认为提供的参数是对的,但是选项和参数交替出现。
而最后一项提供了一个错误的选项,但是无法识别出来。
解决这个问题,需要更加规范的方法来分离参数和选项。下一小节的内容。

数组带空格的问题
数组添加元素有很多方法,这里是一种重新创建数组的做法:

array_name=("${array_name[@]}" value1 ... valueN)

可以一次添加多个元素,如果字符串包含空格,就要加上引号。

和命令行参数的\$@与\$*一样,数组所有的元素也有这两个类似的符号。最严谨的方法是使用 "${names[@]}" 使用带双引号的@。
添加元素和取出元素的时候都要注意,否则存在带空格的元素的时候就会破坏数组原本的元素分隔。
添加元素这里使用:

names=("${names[@]}" "$1")

不单是数组里的元素,被添加的元素也要加上双引号,否则如果有空格,就会按多个元素被添加进数组。

遍历元素使用:

for name in "${names[@]}"

只有添加的时候正确了,才能正确的遍历。然后遍历的时候也要保证正确。
验证效果:

$ ./format.sh -a after -b befor -u value1 "value2 value3" value4BEFOR_VALUE1_AFTERBEFOR_VALUE2 VALUE3_AFTERBEFOR_VALUE4_AFTER$ 

完美。

分离参数和选项

这里的参数就是命令行参数中除了定义的选项之外,其他额外的参数。要同时处理参数和选项,就要用特殊字符(双破折线--)将二者分开。双破折线表明选项列表结束,双破折线后面的都是参数。基于这个逻辑,只要在case语句中加一项判断就行了。
把上面的脚本做一些修改:

$ cat format.sh #!/bin/bashprefix=""    # 前缀base="test"  # 默认字符串suffix=""    # 后缀upper=off    # 是否大写# 显示声明一下这是个数组变量,其实没有必要declare -a names  # 需要格式化输出的所有原始字符串# 解析选项while [ -n "$1" ]do    case "$1" in        -a) suffix="$2"            shift ;;        -b) prefix="$2"            shift ;;        -s) base="$2"            shift ;;        -u) upper=on ;;        --) shift            break ;;         *) echo "$1 is not an option"            exit 1 ;;  # 发现未知参数,直接退出    esac    shiftdone# 解析参数while [ -n "$1" ]do    names=("${names[@]}" "$1")    shiftdonenames[0]=${names[0]:-$base}for name in "${names[@]}"do    # 添加前缀和后缀    output="${prefix:+${prefix}_}${name}${suffix:+_${suffix}}"    # 判断是否要全大写输出    if [ $upper = on ]    then        output=${output^^}    fi    # 输出结果    echo "$output"done$ 

基于这个版本,在使用的时候,需要先输入选项,然后使用双破折线隔开,再输入参数。当脚本遇到双破折线时,它会停止处理选项,并将剩下的参数都当作参数:

$ ./format.sh -a after -b befor -u value1 value2 value3value1 is not an option$ ./format.sh -a after -b befor -u -- value1 value2 value3BEFOR_VALUE1_AFTERBEFOR_VALUE2_AFTERBEFOR_VALUE3_AFTER$ ./format.sh -a after -b befor -v -u -- value1 value2 value3-v is not an option$ 

第一次没有使用双破折线,所以报错。
第二次正确的用双破折号分隔了参数和选项。
第三次在选项部分出现了未定义的选项,也能发现错误。

小结
这一小节的内容也是为下面的getopt命令做铺垫。getopt就是可以帮我们完成命令行参数的解析,返回一个用双破折线隔开选项和参数的规整的参数列表。
另外这里还不支持选项合并:

$ ls -al

这些问题,用getopt都能解决,而且还支持长选项。

参数 效果 引号 命令 字符 字符串 脚本 元素 变量 就是 输出 时候 折线 数组 方法 空格 位置 前缀 后缀 多个 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 手磨咖啡 互联网科技公司经营范围 互联网科技革命 安徽鑫晟辉互联网科技 友谊互联网络科技 互联网科技logo 上海万想互联网科技 中国最先进互联网科技公司 湖南洣水互联网科技有限公司扣款 广州优美互联网科技 彤创互联网科技有限公司 花神(南京)互联网科技有限公司 本届互联网博览会上的黑科技 乌鲁木齐新华智原互联网科技学校 中国互联网公司排名锤子科技 佛山市翊博互联网络科技有限公司 怎样加入科技互联网 银川新华互联网科技学校原画 互联网科技博主张洲 山东蛋芒互联网科技有限公司 广东万易互联网科技有限公司惠州分公司 互联网科技工作委员会副主任毛军 互联网 科技 互联网科技金融 平安科技 互联网 壕鑫互联网络科技 易通互联网络科技有限公司 本届互联网大会上的黑科技 工业互联网科技创业 玉溪新华互联网科技怎么选
0