特殊变量

首先来看几个特殊变量:

  1. #!/bin/bash
  2. echo $0 # 当前脚本的文件名(间接运行时还包括绝对路径)。
  3. echo $n # 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是 $1 。
  4. echo $# # 传递给脚本或函数的参数个数。
  5. echo $* # 传递给脚本或函数的所有参数。
  6. echo $@ # 传递给脚本或函数的所有参数。被双引号 (" ") 包含时,与 $* 不同,下面将会讲到。
  7. echo $? # 上个命令的退出状态,或函数的返回值。
  8. echo $$ # 当前 Shell 进程 ID。对于 Shell 脚本,就是这些脚本所在的进程 ID。
  9. echo $_ # 上一个命令的最后一个参数。
  10. echo $! # 后台运行的最后一个进程的 ID 号。

保存为一个脚本,然后加上几个参数运行,结果如下:

  1. $ ./test.sh A B B D E
  2. ./test.sh # $0, 当前脚本的文件名(间接运行时还包括绝对路径)。
  3. # $n, 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是 $1。
  4. 5 # $#, 传递给脚本或函数的参数个数。
  5. A B B D E # $*, 传递给脚本或函数的所有参数。
  6. A B B D E # $@, 传递给脚本或函数的所有参数。被双引号 (" ") 包含时,与 $* 不同,下面将会讲到。
  7. 0 # $?, 上个命令的退出状态,或函数的返回值。
  8. 24946 # $$, 当前 Shell 进程 ID。对于 Shell 脚本,就是这些脚本所在的进程 ID。
  9. 24946 # $_, 上一个命令的最后一个参数。
  10. # $!, 后台运行的最后一个进程的 ID 号。

$*$@ 都表示传递给函数或脚本的所有参数,不被双引号 (“”) 包含时,都以 “$1””$2” … “$n” 的形式输出所有参数。但是当它们被双引号 (“”) 包含时,”$*“会将所有的参数作为一个整体,以 “$1 $2 … $n” 的形式输出所有参数; “$@“会将各个参数分开,以 “$1”、”$2”、…、”$n” 的形式输出所有参数。

  1. #!/bin/bash
  2. echo "\$* = " $*
  3. echo "\"\$*\" = " "$*"
  4. echo "\$@ = " $@
  5. echo "\"\$@\" = " "$@"
  6. echo "打印 \$* 中的每一个参数: "
  7. for var in $*
  8. do
  9. echo "$var"
  10. done
  11. echo "打印 \$@ 中的每一个参数: "
  12. for var in $@
  13. do
  14. echo "$var"
  15. done
  16. echo "从 \"\$*\" 获取并打印每一个参数: "
  17. for var in "$*"
  18. do
  19. echo "$var"
  20. done
  21. echo "从 \"\$@\" 获取并打印每一个参数: "
  22. for var in "$@"
  23. do
  24. echo "$var"
  25. done

运行及输出:

  1. $ ./test.sh A B B D E
  2. $* = A B B D E
  3. "$*" = A B B D E
  4. $@ = A B B D E
  5. "$@" = A B B D E
  6. 打印 $* 中的每一个参数:
  7. A
  8. B
  9. B
  10. D
  11. E
  12. 打印 $@ 中的每一个参数:
  13. A
  14. B
  15. B
  16. D
  17. E
  18. "$*" 获取并打印每一个参数:
  19. A B B D E
  20. "$@" 获取并打印每一个参数:
  21. A
  22. B
  23. B
  24. D
  25. E

shift 使用

shift 命令用于对参数的移动(左移),通常用于在不知道传入参数个数的情况下依次遍历每个参数然后进行相应处理,常见于 Linux 中各种程序的启动脚本。

1. 每次移动一个参数

示例:依次读取输入的参数并打印参数个数

  • shift1.sh

    1. #!/bin/bash
    2. while [ $# != 0 ]
    3. do
    4. echo -e "参数值为 $1, 参数个数为 $#"
    5. shift
    6. done
  • 输入如下命令运行:

    1. $ sh shift1.sh a b c d
    2. 参数值为 a, 参数个数为 4
    3. 参数值为 b, 参数个数为 3
    4. 参数值为 c, 参数个数为 2
    5. 参数值为 d, 参数个数为 1

每次运行 shift(不带参数的),销毁一个参数(变量的个数($#)减一),后面的参数前移(不管参数之间相隔一个或者多个空格)。

2. 每次移动多个参数

shift 命令一次移动参数的个数由其所带的参数指定。例如,现在有 $1, $2, $3, …,$9 个参数,当 shell 程序处理完前 4 个命令行参数后(即还剩下后面 5 个参数),可以使用 shift 4 命令把 $9 移到 $1。

示例:自定义 shift 每次移动的参数个数。

  • shift2.sh

    1. #!/bin/bash
    2. while [ $# -gt 5 ]
    3. do
    4. echo -e "参数值为 $1, 参数个数为 $#"
    5. shift
    6. done
    7. shift 4
    8. echo -e "参数值为 $1, 参数个数为 $#"
  • 输入如下命令运行

    1. $ sh shift2.sh a1 b2 c3 d4 e5 f6 g7 h8 i9
    2. 参数值为 a1, 参数个数为 9
    3. 参数值为 b2, 参数个数为 8
    4. 参数值为 c3, 参数个数为 7
    5. 参数值为 d4, 参数个数为 6
    6. 参数值为 i9, 参数个数为 1

3. 常与 case 条件配合使用

case ..... esacif ... then ... fi 一样,都是条件判断式的语句结构,它们用倒序的字母单词和正序的单词配对,比如 if 语句,结束时用 fi 来配对, esaccase 配对,是多路分支的语句,类似于 C 中的 switch/case 语句,大致形式如下:

  1. case $参数变量 in # 以关键字 case 作为开始
  2. "变量值1" # 变量值用双引号括起来,关键字用小括号括起来
  3. 程序段1 # 对应的逻辑
  4. ;; # 每个类别结尾使用两个连续的分号来处理
  5. "变量值2"
  6. 程序段2
  7. ;;
  8. "变量值3"
  9. 程序段3
  10. ;;
  11. *) # 最后一个位置参数值会用*来代替所有其他值
  12. 程序段4
  13. ;;
  14. esac # 关键字 esac(case 的反写) 结束

shift 操作经常与 case ... esac 条件分支配合使用。

示例: shift 搭配 case ... esac 实现 shell 脚本手动传递参数处理

  • shift3.sh

    1. while [ -n "$1" ]
    2. do
    3. case "$1" in
    4. -a)
    5. echo "发现 -a 选项"
    6. ;;
    7. -b)
    8. echo "发现 -b 选项"
    9. echo "-b 选项的参数值是:$2"
    10. shift
    11. ;;
    12. -c)
    13. echo "发现 -c 选项"
    14. echo "-c 选项的参数值是:$2"
    15. shift
    16. ;;
    17. *)
    18. echo "$1 是一个未知选项"
    19. ;;
    20. esac
    21. shift
    22. done
  • 输入如下命令运行

    1. $ sh shift3.sh -a -b 200 -c 300 -d 400
    2. 发现 -a 选项
    3. 发现 -b 选项
    4. -b 选项的参数值是:200
    5. 发现 -c 选项
    6. -c 选项的参数值是:300
    7. -d 是一个未知选项
    8. 400 是一个未知选项

简单总结

在 bash shell 中解析命令行参数的方法有很多,网络上介绍的各种方法中,都可以看到它们与 shift 命令搭配使用的身影,因此在深入这一部分编程前,很有必要先了解一下 shift 的作用。这里只是抛砖引玉简单介绍一下,更多技巧还请大家不断去实践理解。

本篇文章的部分内容和代码来源于网络,但都经本人一一验证过,如有侵权或者其他问题,欢迎留言。