Linux程序设计:位置参数

程序需要一种接受和处理命令行选项和参数的能力。

访问命令行

shell 提供了一个称为位置参数的变量集合,这个集合包含了命令行中所有独立的单词。这些变量按照从0 到9 给予命名。可以以这种方式讲明白:

#!/bin/bash
# posit-param: script to view command line parameters
echo "
\$0 = $0
\$1 = $1
\$2 = $2
\$3 = $3
\$4 = $4
\$5 = $5
\$6 = $6
\$7 = $7
\$8 = $8
\$9 = $9
"

一个非常简单的脚本,显示从$0 到$9 所有变量的值。当不带命令行参数执行该脚本时,输出结果如下:

[me@linuxbox ~]$ posit-param
$0 = /home/me/bin/posit-param
$1 =
$2 =
$3 =
$4 =
$5 =
$6 =
$7 =
$8 =
$9 =

即使不带命令行参数,位置参数$0 总会包含命令行中出现的第一个单词,也就是已执行程序的路径名。当带参数执行脚本时,我们看看输出结果:

[me@linuxbox ~]$ posit-param a b c d
$0 = /home/me/bin/posit-param
$1 = a
$2 = b
$3 = c
$4 = d
$5 =
$6 =
$7 =
$8 =
$9 =

注意:实际上通过参数展开方式你可以访问的参数个数多于9 个。只要指定一个大于9 的数字,用花括号把该数字括起来就可以。例如${10}, ${55}, ${211},等等。

确定参数个数

另外shell 还提供了一个名为$#,可以得到命令行参数个数的变量:

#!/bin/bash
# posit-param: script to view command line parameters
echo "
Number of arguments: $#
\$0 = $0
\$1 = $1
\$2 = $2
\$3 = $3
\$4 = $4
\$5 = $5
\$6 = $6
\$7 = $7
\$8 = $8
\$9 = $9
"

结果是:

[me@linuxbox ~]$ posit-param a b c d
Number of arguments: 4
$0 = /home/me/bin/posit-param
$1 = a
$2 = b
$3 = c
$4 = d
$5 =
$6 =
$7 =
$8 =
$9 =

shift - 访问多个参数的利器

但是如果我们给一个程序添加大量的命令行参数,会怎么样呢?正如下面的例子:

[me@linuxbox ~]$ posit-param *
Number of arguments: 82
$0 = /home/me/bin/posit-param
$1 = addresses.ldif
$2 = bin
$3 = bookmarks.html
$4 = debian-500-i386-netinst.iso
$5 = debian-500-i386-netinst.jigdo
$6 = debian-500-i386-netinst.template
$7 = debian-cd_info.tar.gz
$8 = Desktop
$9 = dirlist-bin.txt

在这个例子运行的环境下,通配符* 展开成82 个参数。我们如何处理那么多的参数?为此,shell 提供了一种方法,尽管笨拙,但可以解决这个问题。执行一次shift 命令,就会导致所有的位置参数“向下移动一个位置”。事实上,用shift 命令也可以处理只有一个参数的情况(除了其值永远不会改变的变量$0):

#!/bin/bash
# posit-param2: script to display all arguments
count=1
while [[ $# -gt 0 ]]; do
        echo "Argument $count = $1"
        count=$((count + 1))
shift
done

每次shift 命令执行的时候,变量$2 的值会移动到变量$1 中,变量$3 的值会移动到变量$2 中,依次类推。变量$# 的值也会相应的减1。
在该posit-param2 程序中,我们编写了一个计算剩余参数数量,只要参数个数不为零就会继续执行的while 循环。我们显示当前的位置参数,每次循环迭代变量count 的值都会加1,用来计数处理的参数数量,最后,执行shift 命令加载$1,其值为下一个位置参数的值。这里是程序运行后的输出结果:

[me@linuxbox ~]$ posit-param2 a b c d
Argument 1 = a
Argument 2 = b
Argument 3 = c
Argument 4 = d

简单应用

即使没有shift 命令,也可以用位置参数编写一个有用的应用。举例说明,这里是一个简单的输出文件信息的程序:

#!/bin/bash
# file_info: simple file information program
PROGNAME=$(basename $0)
if [[ -e $1 ]]; then
       echo -e "\nFile Type:"
       file $1
       echo -e "\nFile Status:"
       stat $1
else
       echo "$PROGNAME: usage: $PROGNAME file" >&2
exit 1
fi

这个程序显示一个具体文件的文件类型(由file 命令确定)和文件状态(来自stat 命令)。该程序一个有意思的特点是PROGNAME 变量。它的值就是basename $0 命令的执行结果。这个basename 命令清除一个路径名的开头部分,只留下一个文件的基本名称。在我们的程序中,basename 命令清除了包含在$0 位置参数中的路径名的开头部分,$0 中包含着我们示例程序的完整路径名。当构建提示信息正如程序结尾的使用信息的时候,basename $0 的执行结果就很有用处。按照这种方式编码,可以重命名该脚本,且程序信息会自动调整为包含相应的程序名称。

Shell函数中使用位置参数

正如位置参数被用来给shell 脚本传递参数一样,它们也能够被用来给shell 函数传递参数。为了说明这一点,我们将把file_info 脚本转变成一个shell 函数:

file_info () {
# file_info: function to display file information
if [[ -e $1 ]]; then
       echo -e "\nFile Type:"
       file $1
       echo -e "\nFile Status:"
       stat $1
else
       echo "$FUNCNAME: usage: $FUNCNAME file" >&2
return 1
fi
}

现在,如果一个包含shell 函数file_info 的脚本调用该函数,且带有一个文件名参数,那这个参数会传递给file_info 函数。
通过此功能,我们可以写出许多有用的shell 函数,这些函数不仅能在脚本中使用,也可以用在.bashrc 文件中。
注意那个PROGNAME 变量已经改成shell 变量FUNCNAME 了。shell 会自动更新FUNCNAME 变量,以便跟踪当前执行的shell 函数。注意位置参数$0 总是包含命令行中第一
项的完整路径名(例如,该程序的名字),但不会包含这个我们可能期望的shell 函数的名字。

处理集体位置参数

有时候把所有的位置参数作为一个集体来管理是很有用的。例如,我们可能想为另一个程序编写一个“包裹程序”。这意味着我们会创建一个脚本或shell 函数,来简化另一个程序的执行。包裹程序提供了一个神秘的命令行选项列表,然后把这个参数列表传递给下一级的程序。
为此shell 提供了两种特殊的参数。他们二者都能扩展成完整的位置参数列表,但以相当微妙的方式略有不同。它们是:

参数     描述
$*        展开成一个从1 开始的位置参数列表。当它被用双引号引起来的时候,展开成一个由双引号引起来的字符串,包含了所有的位置参数,每个位置参数由shell 变量IFS 的第            一个字符(默认为一个空格)分隔开。
$@     展开成一个从1 开始的位置参数列表。当它被用双引号引起来的时候,它把每一个位置参数展开成一个由双引号引起来的分开的字符串。

下面这个脚本用程序中展示了这些特殊参数:

#!/bin/bash
# posit-params3 : script to demonstrate $* and $@
print_params () {
           echo "\$1 = $1"
           echo "\$2 = $2"
           echo "\$3 = $3"
           echo "\$4 = $4"
}
pass_params () {
           echo -e "\n" '$* :'; print_params $*
           echo -e "\n" '"$*" :'; print_params "$*"
           echo -e "\n" '$@ :'; print_params $@
           echo -e "\n" '"$@" :'; print_params "$@"
}
pass_params "word" "words with spaces"

       在这个相当复杂的程序中,我们创建了两个参数:“word”和“words with spaces”,然后把它们传递给pass params 函数。这个函数,依次,再把两个参数传递给print params 函数,使用了特殊参数$* 和$@ 提供的四种可用方法。脚本运行后,揭示了这两个特殊参数存在的差异:

[me@linuxbox ~]$ posit-param3
$* :
$1 = word
$2 = words
$3 = with
$4 = spaces
"$*" :
$1 = word words with spaces
$2 =
$3 =
$4 =
$@ :
$1 = word
$2 = words
429
$3 = with
$4 = spaces
"$@" :
$1 = word
$2 = words with spaces
$3 =
$4 =

通过我们的参数,$* 和$@ 两个都产生了一个有四个词的结果:
word words with spaces
"$*" produces a one word result:
      "word words with spaces"
"$@" produces a two word result:
      "word" "words with spaces"
这个结果符合我们实际的期望。我们从中得到的教训是尽管shell 提供了四种不同的得到位置参数列表的方法,但到目前为止,“$@” 在大多数情况下是最有用的方法,因为它保留了每一个位置参数的完整性。

一个更复杂的应用

       我们将看一个叫做sys info page 的程序。我们下一步要给程序添加如下几个命令行选项:
       • 输出文件。我们将添加一个选项,以便指定一个文件名,来包含程序的输出结果。选项格式要么是-f file,要么是--file file
       • 交互模式。这个选项将提示用户输入一个输出文件名,然后判断是否指定的文件已经存在了。如果文件存在,在覆盖这个存在的文件之前会提示用户。这个选项可以通过-i 或
  者--interactive 来指定。
       • 帮助。指定-h 选项或者是--help 选项,可导致程序输出提示性的使用信息。
这里是处理命令行选项所需的代码:

usage () {
      echo "$PROGNAME: usage: $PROGNAME [-f file | -i]"
      return
}
# process command line options
interactive=
filename=
while [[ -n $1 ]]; do
      case $1 in
      -f | --file)            shift
                                filename=$1
                                ;;
      -i | --interactive) interactive=1
                                ;;
      -h | --help)          usage
                                exit
                                ;;
       *)                      usage >&2
                                exit 1
                                ;;
        esac
        shift
done

       首先,添加了一个叫做usage 的shell 函数,以便显示帮助信息,当启用帮助选项或敲写了一个未知选项的时候。
       下一步开始处理循环。当位置参数$1 不为空的时候,这个循环会持续运行。在循环的底部,有一个shift 命令,用来提升位置参数,以便确保该循环最终会终止。在循环体内,使用一个case 语句来检查当前位置参数的值,看看它是否匹配某个支持的选项。若找到了匹配项,就会执行与之对应的代码。若没有,就会打印出程序使用信息,该脚本终止且执行错误。
处理-f 参数的方式很有意思。当监测到-f 参数的时候,会执行一次shift 命令,从而提升位置参数$1 为伴随着-f 选项的filename 参数。
我们下一步添加代码来实现交互模式:

# interactive mode
if [[ -n $interactive ]]; then
       while true; do
             read -p "Enter name of output file: " filename
             if [[ -e $filename ]]; then
                     read -p "'$filename' exists. Overwrite? [y/n/q] > "
                     case $REPLY in
                     Y|y)        break
                                    ;;
                     Q|q)        echo "Program terminated."
                                    exit
                                    ;;
                     *)            continue
                                    ;;
                      esac
             elif [[ -z $filename ]]; then
                 continue
             else
                 break
            fi
     done
fi

       若interactive 变量不为空,就会启动一个无休止的循环,该循环包含文件名提示和随后存在的文件处理代码。如果所需要的输出文件已经存在,则提示用户覆盖,选择另一个文件名,或者退出程序。如果用户选择覆盖一个已经存在的文件,则会执行break 命令终止循环。注意case 语句是怎样只检测用户选择了覆盖还是退出选项。其它任何选择都会导致循环继续并提示用户再次选择。
为了实现这个输出文件名的功能,首先我们必须把现有的这个写页面(page-writing)的代码转变成一个shell 函数,一会儿就会明白这样做的原因:

write_html_page () {
       cat <<- _EOF_
             <HTML>
                        <HEAD>
                                  <TITLE>$TITLE</TITLE>
                        </HEAD>
                        <BODY>
                                  <H1>$TITLE</H1>
                                  <P>$TIMESTAMP</P>
                                  $(report_uptime)
                                  $(report_disk_space)
                                  $(report_home_space)
                        </BODY>
              </HTML>
_EOF_
return
}
# output html page
if [[ -n $filename ]]; then
        if touch $filename && [[ -f $filename ]]; then
                write_html_page > $filename
        else
                echo "$PROGNAME: Cannot write file '$filename'" >&2
                exit 1
        fi
else
        write_html_page
fi

       解决-f 选项逻辑的代码出现在以上程序片段的末尾。在这段代码中,我们测试一个文件名是否存在,若文件名存在,则执行另一个测试看看该文件是不是可写文件。为此,会运行touch 命令,紧随其后执行一个测试,来决定touch 命令创建的文件是否是个普通文件。这两个测试考虑到了输入是无效路径名(touch 命令执行失败),和一个普通文件已经存在的情况。正如我们所看到的,程序调用write html page 函数来生成实际的网页。函数输出要么直接定向到标准输出(若filename 变量为空的话)要么重定向到具体的文件中。
 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值