【shell&bash进阶系列】(二十一)向脚本传递参数(shift和getopts)

使用特定变量 1 . . 9可以向脚本传递参数。$ #用于统计传递参数的个数。可以创建一个usage语句,需要时可通知用户怎样以适当的调用参数调用脚本或函数。

本章内容有:

• shift。
• getopts。
• shift和getopts例子。

简单地说,下述脚本框架控制参数开始与停止。脚本需要两个参数,如果没有输入两个

参数,那么产生一个u s a g e语句。注意这里使用c a s e语句处理输入脚本的不同参数。

  1. #!/bin/bash
  2. opt.sh

  3. usage()
  4. {
  5. echo "usage: `basename $0` start|stop process name"
  6. }
  7. OPT=$1
  8. PROCESSID=$1
  9. if [ $# -ne 2 ]; then
  10. usage
  11. exit 1
  12. fi
  13. case $OPT in
  14. start|Start) echo "Starting..$PROCESSID"
  15. some process to go here

  16. ;;
  17. stop|Stop) echo "Stopping..$PROCESSID"
  18. some process to go here

  19. ;;
  20. *)usage
  21. ;;
  22. esac

执行脚本,输入一下参数,结果为:

  1. root@localhost \~\]# sh opt.sh start named

  2. root@localhost \~\]# sh opt.sh start

任何UNIX或LINUX命令均接受一般格式:

命令选项文件选项部分最多可包含1 2个不同的值。上述脚本中,如果必须控制不同的命令选项,就要加入大量脚本。这里只控制两个选项:开始和停止。

幸运的是shell提供shift命令以帮助偏移选项,使用shift可以去除只使用1到9传递参数的限制。

1.1 shift命令

向脚本传递参数时,有时需要将每一个参数偏移以处理选项,这就是s h i f t命令的功能。
它每次将参数位置向左偏移一位,下面用一段简单脚本详述其功能。脚本使用w h i l e循环反馈
所有传递到脚本的参数。

  1. #!/bin/bash
  2. opt2.sh

  3. loop=0
  4. while [ $# -ne 0 ] # while there are still arguments
  5. do
  6. echo $1
  7. done

你可能想像,上述脚本一直执行,直到命令行中不再有更多的参数输入。错了,因为没
有办法偏移到脚本中下一个参数,将只会反馈出第一个参数。执行结果如下:

  1. sh opt2.sh file1 file2 file3

  2. file1
  3. file1
  4. file1
  5. ......

1.1.1 shift命令简单用法

使用s h i f t命令来处理传递到脚本的每一个参数。改动后脚本如下:

  1. #!/bin/bash
  2. opt2.sh

  3. loop=0
  4. while [ $# -ne 0 ] # while there are still arguments
  5. do
  6. echo $1
  7. shift
  8. done

现在执行一下,结果就会明显不同了。如下所示:

  1. root@localhost \~\]# sh opt2.sh file1 file2 file3

  2. file2
  3. file3

1.1.2 命令行输入的最后一个参数

虽然还没有讲e v a l命令,如果需要知道命令行中输入的最后一个参数(通常是一个文件名),可以有两种选择:使用命令eval echo \$$#;使用s h i f t命令:shift 'expr $# -2'。

1.1.3 使用shift处理文件转换

shift可使控制命令行选项更加容易。下面构造一个转换脚本,使用tr将文件名转换为大写或小写。
脚本选项为:
-l 用于小写转换。
-u 用于大写转换。
使用shift命令将脚本放在一起以控制- l和- u选项。脚本的第一版本如下:

  1. #!/bin/bash
  2. tr_case.sh

  3. case conversion

  4. usage()
  5. {
  6. usage

  7. echo "usage: `basename $0` -[l|u] file [files]" >&2
  8. exit 1
  9. }
  10. if [ $# -eq 0 ]; then
  11. no parameters passed !

  12. fi
  13. while [ $# -gt 0 ]
  14. do
  15. case $1 in
  16. -u|-U) echo "-u option specified"
  17. do any setting of variables here for lowercase then shift

  18. shift
  19. ;;
  20. -l|-L) echo "-l option specified"
  21. do any setting of variables here for uppercase then shift

  22. shift
  23. ;;
  24. *) usage
  25. ;;
  26. esac
  27. done

首先检查脚本是否有参数,如果没有,打印u s a g e语句,如果有需要处理的参数,使用case语句捕获每一个传送过来的选项。当处理完此选项后,使用shift命令搜集命令行中下一选项,如果未发现匹配选项,打印u s a g e语句。
当向脚本传递两个无效参数时,输出如下:

  1. root@localhost \~\]# sh tr_case.sh -u -l -k

  2. -l option specified
  3. usage: tr_case.sh -[l|u] file [files]

下一步就是要用c a s e语句处理选项后传递过来的文件名。为此需改动c a s e语句。c a s e语句
中捕获任意模式*应该为- *以允许传递无效选项,例如- p或- q。
*模式也匹配传递过来的所有文件名,以便用f o r循环处理每一个文件,这里也将使用- f选项检测文件是否存在。
改动后的c a s e语句如下:

  1. case
  2. ......
  3. -*) echo "usage: `basename $0` -[l|u] file [file..]"
  4. exit 1
  5. ;;
  6. *) # collect the files to process
  7. if [ -f $1 ]; then
  8. add the filenames to a variable list

  9. FILES=FILES" "1
  10. else
  11. echo "`basename 0\`: Error cannot find the file 1"
  12. fi
  13. shift
  14. ;;
  15. esac

还需要指定与选项( - l,- u)相关的变量设置。这些变量是:
T R C A S E 保存转换类型(大写或小写)。
E X T 所有文件转换后,大写文件名为. U C,小写为. L C,不保存初始文件状态。
O P T 如果给出此选项,设其为y e s,否则为n o。如果没有给出此选项,捕获此信息并反馈出来。
其他部分脚本用于实际转换处理,这里即t r命令。t r命令放在c a s e语句f o r循环中读取文件名进行处理的脚本末尾部分。
以下为完整脚本:

  1. #!/bin/bash
  2. tr_case.sh

  3. case conversion

  4. convert files to either upper or lower case

  5. FILES=""
  6. TRCASE=""
  7. EXT=""
  8. OPT=no
  9. gets called when a conversion fails

  10. do any setting of variables here for lowercase then shift

  11. shift
  12. ;;
  13. error_msg()
  14. {
  15. _FILENAME=$1
  16. shift
  17. ;;
  18. echo "`basename 0\`: Error the conversion failed on _FILENAME"
  19. }
  20. if [ $# -eq 0 ]; then
  21. usage
  22. no parameters passed !

  23. fi
  24. while [ $# -gt 0 ]
  25. do
  26. case $1 in
  27. set the variables based on what option was used

  28. -u) TRCASE=upper
  29. EXIT".UC"
  30. OPT=yes
  31. shift
  32. ;;
  33. -l) TRCASE=lower
  34. EXT=".LC"
  35. OPT=yes
  36. shift
  37. ;;
  38. -help) echo "convert a file(s) to uppercase from lowercase"
  39. echo "convert a file(s) from lowercase to uppercase"
  40. echo "will convert all characters according to the"
  41. ;;
  42. echo " specified command option."
  43. echo " Where option is"
  44. echo " -l Convert to lowercase"
  45. echo " -u Convert to uppercase"
  46. echo " The original file(s) is not touched. A new file(s)"
  47. echo "will be created with either a .UC or .LC extension"
  48. echo "usage: $0 -[l|u] file [file..]"
  49. exit 0
  50. ;;
  51. -*) echo "usage: `basename $0` -[l|u] file [file..]"
  52. exit 1
  53. ;;
  54. *) # collect the files to process
  55. if [ -f $1 ]; then
  56. add the filenames to a variable list

  57. FILES=FILES" "1
  58. else
  59. echo "`basename 0\`: Error cannot find the file 1"
  60. fi
  61. shift
  62. ;;
  63. esac
  64. done
  65. no options given ... help the user

  66. if [ "$OPT" == "no" ]; then
  67. echo " try `basename $0` --help"
  68. exit 1
  69. fi
  70. now read in all the file(s)

  71. use the variable LOOP, I just love the word LOOP

  72. for LOOP in $FILES
  73. do
  74. case $TRCASE in
  75. lower) cat LOOP \| tr "\[a-z\]" "\[A-Z\]" \> LOOP$EXT
  76. if [ $? != 0 ]; then
  77. error_msg $LOOP
  78. else
  79. echo "Converted file called LOOPEXT"
  80. fi
  81. ;;
  82. upper) cat LOOP \| tr "\[A-Z\]" "\[a-z\]" \>LOOP$EXT
  83. if [ $? !=0 ]; then
  84. error_msg $LOOP
  85. else
  86. echo "Converted file called LOOPEXT"
  87. fi
  88. ;;
  89. esac
  90. done

执行上述脚本,给出不同选项,得结果如下:
转换一个不存在的文件:

  1. root@localhost \~\]# sh tr_case.sh -k cursor

传递不正确选项:

  1. root@localhost \~\]# sh tr_case.sh cursor

  2. tr_case.sh : Error you need to specify an option. No action taken
  3. try tr_case.sh --help

只键入文件名,希望脚本提示更多帮助信息:

  1. root@localhost \~\]# sh tr_case.sh

输入两个有效文件及第三个无效文件:

  1. root@localhost \~\]# sh tr_case.sh -l file1 file2 sd

  2. Converted file called file1.LC
  3. Converted file called file2.LC

使用上述脚本可以将许多文件转换为同样的格式。编写一段脚本,使其控制不同的命令行选项,这种方式编程量很大,是一件令人头疼的事。
假定要写一段脚本,要求控制以下各种不同的命令行选项:
命令-l -c 23 -v 文件1文件2
shift命令显得力不从心,这就需要用到getopts命令。

1.2 getopts命令

g e t o p t s可以编写脚本,使控制多个命令行参数更加容易。g e t o p t s用于形成命令行处理标
准形式。原则上讲,脚本应具有确认带有多个选项的命令文件标准格式的能力。

1.2.1 getopts脚本实例

通过例子可以更好地理解g e t o p t s。以下g e t o p t s脚本接受下列选项或参数。
• a 设置变量A L L为t r u e。
• h 设置变量H E L P为t r u e。
第20章向脚本传递参数229
下载
• f 设置变量F I L E为t r u e。
• v 设置变量V E R B O S E为t r u e。
对于所有变量设置,一般总假定其初始状态为f a l s e:

  1. #!/bin/bash
  2. getopt1.sh

  3. set the vars

  4. ALL=false
  5. HELP=false
  6. FILE=false
  7. VERBOSE=false
  8. while getopts ahfgv OPTION
  9. do
  10. case $OPTION in
  11. a) ALL=true
  12. echo "ALL is $ALL"
  13. ;;
  14. h) HELP=true
  15. echo "HELP is $HELP"
  16. ;;
  17. f) FILE=true
  18. echo "FILE is $FILE"
  19. ;;
  20. v) VERBOSE=true
  21. echo "VERBOSE is $VERBOSE"
  22. ;;
  23. esac
  24. done

getopts一般格式为:
getopts option_string variable
在上述例子中使用脚本:
while getopts ahfgv OPTION
可以看出w h i l e循环用于读取命令行,o p t i o n s t r i n g为指定的5个选项(- a,- h,- f,- g,- v),
脚本中v a r i a b l e为O P T I O N。注意这里并没有用连字符指定每一单个选项。
运行上述脚本,给出几个有效和无效的选项,结果为:

  1. root@localhost \~\]# sh getopt1.sh -a -h

  2. HELP is true
  3. root@localhost \~\]# sh getopt1.sh -a -h -p

  4. HELP is true
  5. getopt1.sh: illegal option -- p

可以看出不同选项的结合方式。

1.2.2 getopts使用方式

getopts读取o p t i o n s t r i n g,获知脚本中使用了有效选项。
getopts查看所有以连字符开头的参数,将其视为选项,如果输入选项,将把这与o p t i o n s t r i n g对比,如果匹配发现,变量设置为O P T I O N,如果未发现匹配字符,变量能够设置为?。重复此处理过程直到选项输入完毕。
getopts接收完所有参数后,返回非零状态,意即参数传递成功,变量O P T I O N保存最后处理参数,一会儿就可以看出处理过程中这样做的好处。

1.2.3 使用getopts指定变量取值

有时有必要在脚本中指定命令行选项取值。getopts为此提供了一种方式,即在o p t i o n s t r i n g中将一个冒号放在选项后。例如:
getopts ahfvc: OPTION
上面一行脚本指出,选项a、h、f、v可以不加实际值进行传递,而选项c必须取值。使用选项取值时,必须使用变量O P TA R G保存该值。如果试图不取值传递此选项,会返回一个错误信息。错误信息提示并不明确,因此可以用自己的反馈信息屏蔽它,方法如下:
将冒号放在o p t i o n s t r i n g开始部分。
while getopts :ahfgvc: OPTION
在c a s e语句里使用?创建一可用语句捕获错误。

  1. #!/bin/bash
  2. getopt2.sh

  3. set the vars

  4. ALL=false
  5. HELP=false
  6. FILE=false
  7. VERBOSE=false
  8. COPIES=0
  9. the value for the -c option is set to zero

  10. while getopts ahfgvc: OPTION
  11. do
  12. case $OPTION in
  13. a) ALL=true
  14. echo "ALL is $ALL"
  15. ;;
  16. h) HELP=true
  17. echo "HELP is $HELP"
  18. ;;
  19. f) FILE=true
  20. echo "FILE is $FILE"
  21. ;;
  22. v) VERBOSE=true
  23. echo "VERBOSE is $VERBOSE"
  24. ;;
  25. c) COPIES=$OPTARG
  26. echo "COPIES is $COPIES"
  27. \?) # usage statement
  28. echo "`basename $0` -[a h f v] -[c value] file" >&2
  29. ;;
  30. esac
  31. done

运行上述脚本,选项- c不赋值,将返回错误,但显示的是脚本语句中的反馈信息:

  1. root@localhost \~\]# sh getopt2.sh -ah -c

  2. HELP is true
  3. getopt2.sh: option requires an argument -- c
  4. getopt2.sh -[a h f v] -[c value] file

现在输入所有的合法选项:

  1. root@localhost \~\]# sh getopt2.sh -ah -c 3

  2. HELP is true
  3. COPIES is 3

1.2.4 访问取值方式

getopts的一种功能是运行后台脚本。这样可以使用户加入选项,指定不同的磁带设备以备份数据。使用getopts实现此任务的基本框架如下:

  1. #!/bin/bash
  2. backups.sh

  3. QUITE=n
  4. DEVICE=awa
  5. LOGFILE=/tmp/logbackup
  6. usage()
  7. {
  8. echo "Usage: `basename $0` -d [device] -l [logfile] -q"
  9. exit 1
  10. }
  11. if [ $# == 0 ]; then
  12. usage
  13. fi
  14. while getopts :qd:l: OPTION
  15. do
  16. case $OPTION in
  17. q) QUITE=y
  18. LOGFILE="/tmp/backup.log"
  19. ;;
  20. d) DEVICE=$OPTARG
  21. ;;
  22. l) LOGFILE=$OPTARG
  23. ;;
  24. \?) usage
  25. ;;
  26. esac
  27. done
  28. echo "you chose the following options .. I can process these"
  29. echo "Quite= QUITE DEVICE $LOGFILE"
    上述脚本中如果指定选项d,则需为其赋值。该值为磁带设备路径。用户也可以指定是否备份输出到登录文件中的内容。运行上述脚本,指定下列输入:
  30. root@localhost \~\]# sh backups.sh -d/dev/rmt0 -q

  31. Quite= y /dev/rmt0 /tmp/backup.log

getopts检查完之后,变量O P TA R G取值可用来进行任何正常的处理过程。当然,如果输入选项,怎样进行进一步处理及使该选项有有效值,完全取决于用户。
以上是使用getopts对命令行参数处理的基本框架。
实际处理文件时,使用f o r循环,就像在t r- c a s e脚本中使用s h i f t命令过滤所有选项一样。使用getopts与使用shift方法比较起来,会减少大量的编程工作。

1.2.5 使用getopts处理文件转换

现在用所学知识将t r- c a s e脚本转换为getopts版本。命令行选项getopts方法与s h i f t方法的唯一区别是一个V E R B O S E选项。
变量V E R B O S E缺省取值为n o,但选择了命令行选项后, c a s e语句将捕获它,并将其设为
y e s,反馈的命令是一个简单的i f语句。

  1. if [ "$VERBOSE" == "on" ]; then
  2. echo "doing..lower on LOOP .. newfile called LOOP$EXT"
  3. fi
    复制代码
    如果正在使用其他系统命令包,它总是反馈用户动作,只需简单地将包含错误的输出重
    定向到/ d e v / n u l l中即可。如:
    命令>/dev/null 2 >&1
    缺省时V E R B O S E关闭(即不显示),使用- v选项可将其打开。例如要用V E R B O S E将
    m y f i l e文件系列转换为小写,方法如下:
    tr-case -l -v myfile1 myfile2 ...
    或者
    tr-case -v -l myfile1 myfile2 ...
    可能首先注意的是使用getopts后脚本的缩减效果。这里用于文件处理的脚本与s h i f t版本
    相同。
    脚本如下:
  4. #!/bin/bash
  5. tr_case2.sh

  6. convert case, using getopts

  7. EXT=""
  8. TRCASE=""
  9. FLAG=""
  10. OPT="no"
  11. VERBOSE="off"
  12. while getopts :luv OPTION
  13. do
  14. case $OPTION in
  15. l) TRCASE="lower"
  16. EXT=".LC"
  17. OPT=yes
  18. ;;
  19. u) TRCASE="upper"
  20. EXT=".UC"
  21. OPT=yes
  22. ;;
  23. v) VERBOSE=on
  24. ;;
  25. \?) echo "usage: `basename $0`: -[l|u] --v file[s]"
  26. echo "doing.. lower on LOOP .. newfile called LOOP$EXT
  27. exit 1
  28. ;;
  29. esac
  30. done
  31. next argument down only please

  32. shift `expr $OPTION - 1`
  33. are there any argument passed ??

  34. if [ "#" == "0" \] \|\| \[ "OPT" == "no" ]; then
  35. echo "usage: `basename $0` : -[l|u] -v file[s]" >&2
  36. exit 1
  37. fi
  38. for LOOP in "$@"
  39. do
  40. if [ ! -f $LOOP ]; then
  41. echo "`basename 0\` : Error cannot find file LOOP" >&2
  42. exit 1
  43. fi
  44. echo TRCASE LOOP
  45. case $TRCSSE in
  46. lower)
  47. if [ "$VERBOSE" == "on" ]; then
  48. echo "doing..lower on LOOP .. newfile called LOOP$EXT"
  49. fi
  50. cat LOOP \| tr "\[a-z\]" "\[A-Z\]" \> LOOP$EXT
  51. ;;
  52. upper)
  53. if [ "$VERBOSE" == "on" ]; then
  54. echo "doing..lower on LOOP ..newfile called LOOP$EXT"
  55. fi
  56. cat LOOP \| tr "\[A-Z\]" "\[a-z\]" \>LOOP$EXT
  57. ;;
  58. esac
  59. done
    在脚本中指定命令行选项时,最好使其命名规则与U N I X或L I N U X一致。下面是一些选项及其含义的列表。

选项含义

  • a 扩展
  • c 计数、拷贝
  • d 目录、设备
  • e 执行
  • f 文件名、强制
  • h 帮助
  • i 忽略状态
  • l 注册文件
  • o 完整输出
  • q 退出
  • p 路径
    -v 显示方式或版本

1.3 小结

正确控制命令行选项会使脚本更加专业化,对于用户来说会使之看起来像一个系统命令。
本章讲到了控制命令行选项的两种方法, shift和getopts。使用getopts检测脚本的数量远远小于使用shift方法检测脚本的数量。
shift也克服了脚本参数 1 . . 9的限制。使用shift命令,脚本可以很容易偏移至所有调用参数,因此脚本可以做进一步处理。

相关推荐
24级计算机应用技术3班闫卓1 小时前
Bash Shell 基础操作全面指南
开发语言·bash
zly35002 小时前
删除文件(rm 命令 删除目录)
linux·运维·服务器
fbllfbll2 小时前
Alpine下部署Nginx+MAZANOKE在线批量压缩图片
服务器·nginx·pve·alpine·lxc容器·在线压缩图片·mazanoke
被AI抢饭碗的人2 小时前
linux:线程池
linux·开发语言
木风小助理2 小时前
PostgreSQL 的范式跃迁:从关系型数据库到统一数据平台
服务器·云原生·kubernetes
曹天骄2 小时前
OSS 传输加速 与 CDN 的区别
运维·阿里云·https
Studying 开龙wu2 小时前
Linux 系统中配置国内源下载时使用pip install 和conda install哪个快?
linux·conda·pip
呱呱巨基2 小时前
Linux 进程控制
linux·c++·笔记·学习
qq_455760852 小时前
docker - 网络
运维·docker·容器