Shell脚本——位置参数
2026/9/9 19:49:22 网站建设 项目流程

位置参数

    • 1、访问命令行
      • 1.1、确定参数个数
      • 1.2、shift—访问多个参数
      • 1.3、简单应用
      • 1.4、在Shell函数中使用位置参数
    • 2、批量处理位置参数
    • 3、一个更完整的应用
    • 4、总结

1、访问命令行

Shell提供了一组名为位置参数(positionalparameters)的变量,其中包含了命令行上的各个单词。这些变量按照0~9分别命名,可以通过下列方式展示:

#!/bin/bash# posit-param: 查看命令行参数的脚本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 、 {10}、10{55}、${211}等。

1.1、确定参数个数

Shell还提供了变量$#,其中包含了命令行中的参数个数:

#!/bin/bash# posit-param: 查看命令行参数的脚本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=

1.2、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: 脚本显示所有参数count=1while[[$#-gt0]];doecho"Argument$count=$1"count=$((count+1))shiftdone

每次执行shift,$2的值就会移入$1,然后$3的值移入2 ,依此类推。与此同时, 2,依此类推。与此同时,2,依此类推。与此同时,#的值也会相应减一。

在posit-param2程序中,我们创建了一个循环,评估所剩的参数数量,只要数量不为0,循环就继续执行。在每次循环中,我们先显示当前参数;然后增加变量count的值,表示已处理过的参数数量;最后执行shift,将下一个参数的值移入$1。该程序的执行结果如下:

[me@linuxbox ~]$ posit-param2 a b c d Argument1=a Argument2=b Argument3=c Argument4=d

1.3、简单应用

即便没有shift,也能利用位置参数写出实用的程序。来看一个简单的文件信息程序:

#!/bin/bash# file-info: 简单文件信息程序PROGNAME="$(basename"$0")"if[[-e"$1"]];thenecho-e"\nFile Type:"file"$1"echo-e"\nFile Status:"stat"$1"elseecho"$PROGNAME: usage:$PROGNAMEfile">&2exit1fi

该程序显示文件类型(取自file命令)和文件状态信息(取自stat命令)​。其中一处值得注意的地方是PROGNAME变量,它的值是命令basename "$0"的执行结果。basename命令会删除路径名中的开头部分,只留下文件的基础名称。在这个例子中,basename删除了$0参数所包含的文件信息程序完整路径名的开头部分。这个值在构建如程序末尾的用法信息时能用得上。通过这种方法,就算程序改了名,消息也会自动调整,显示正确的程序名称。

1.4、在Shell函数中使用位置参数

位置参数既可以向Shell脚本传递参数,也可以向Shell函数传递参数。作为演示,我们将file_info脚本改写成Shell函数:

file_info(){# file_info: 函数显示文件信息if[[-e"$1"]];thenecho-e"\nFile Type:"file"$1"echo-e"\nFile Status:"stat"$1"elseecho"$FUNCNAME: usage:$FUNCNAMEfile">&2return1fi}

现在,如果包含Shell函数file_info的脚本以文件名作为参数调用该函数,参数就会被传入函数。

有了这种能力,我们便可以编写出很多实用的Shell函数,不仅能将其用在脚本中,还能将其应用于.bashrc文件。

注意,PROGNAME变量被改成了Shell变量FUNCNAME。Shell会自动更新该变量,以跟踪当前正在执行的Shell函数。$0始终包含的是命令行中第一项(也就是程序名)的完整路径,并不包含Shell函数名。

2、批量处理位置参数

有时候批量处理所有位置参数更为实用。假设我们要为其他程序编写一个“包装器”​(wrapper)​,这意味着需要创建一个脚本或Shell函数,以简化其他程序的调用。包装器负责提供一系列晦涩的命令选项,然后将参数列表传给低层程序。

Shell为此提供了两个特殊参数*和@,两者均可扩展成完整的位置参数列表,但其区别有些微妙。下表描述了这两个特殊参数。

来看一个演示这些特殊参数用法的脚本示例:

#!/bin/bash# posit-params3: 演示$*和$@的脚本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,在这个过程中使用了4种不同的方法来处理特殊参数∗ 和 *和@。从脚本的执行结果中可以看出两者之间的差异:

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

使用我们选择的参数,∗ 和 *和@生成了4个单词:

word words with spaces

"$*"生成了1个单词:

"word words with spaces"

"$@"生成了2个单词:

"word""words with spaces"

这符合我们的实际意图。从中得到的结论就是,在Shell所提供的4种获取位置参数列表的不同方法中,到目前为止,"$@"适用于大部分情况,因为其保留了每个位置参数的整体性。为了保证安全性,你应该坚持使用这种方法,除非你有令人信服的原因。

3、一个更完整的应用

我们现在要继续完善前面的sys_info_page程序,接下来,要为其添加一些命令行选项。

  • 输出文件。该选项用于指定保存程序输出的文件名,其形式可以是-f file或–file file。
  • 交互模式。该选项提示用户输出文件并判断指定文件是否已经存在。如果存在,在覆盖已有文件之前会提示用户,其形式为-i或–interactive。
  • 帮助。该选项使程序输出用法信息,其形式为-h或–help。

实现命令行处理的代码如下:

usage(){echo"$PROGNAME: usage:$PROGNAME[-f file | -i]"return}# 进程命令行选项interactive=filename=while[[-n"$1"]];docase"$1"in-f|--file)shiftfilename="$1";;-i|--interactive)interactive=1;;-h|--help)usageexit;;*)usage>&2exit1;;esacshiftdone

首先,添加Shell函数usage,其用于在指定帮助选项或出现未知选项时显示用法信息。

接下来,我们开始处理循环。只要位置参数$1不为空,循环就不会停止。在循环末尾,使用shift命令向前移动位置参数,确保该循环最终能够停止。

在循环内部,使用case语句检查当前位置参数,看一看是否匹配所支持的某个选项。如果匹配,则执行相应的操作。如果发现有未知选项,则显示用法信息,终止脚本并返回错误。

-f选项的处理方式值得注意。如果发现有该选项,会再执行一次shift,并将-f选项的文件名参数移入位置参数$1中。

实现交互模式的代码如下:

# 交互模式if[[-n"$interactive"]];thenwhiletrue;doread-p"Enter name of output file: "filenameif[[-e"$filename"]];thenread-p"'$filename' exists. Overwrite? [y/n/q] > "case"$REPLY"inY|y)break;;Q|q)echo"Program terminated."exit;;*)continue;;esacelif[[-z"$filename"]];thencontinueelsebreakfidonefi

如果变量interactive不为空,则开启无限循环,其中包含文件名提示以及后续的已有文件处理代码。如果指定的输出文件已存在,会提示用户要么覆盖该文件,要么更改文件名,要么退出程序。如果用户选择覆盖已有文件,执行break,停止循环。注意,case语句只检查用户选择的是覆盖文件还是退出程序,其他选择会使循环继续执行,再次向用户发出提示信息。

为了实现指定输出文件名的功能,我们必须先将现有的写页面代码(page-writing code)改为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}# 输出HTML页if[[-n"$filename"]];theniftouch"$filename"&&[[-f"$filename"]];thenwrite_html_page>"$filename"elseecho"$PROGNAME: Cannot write file '$filename'">&2exit1fielsewrite_html_pagefi

处理-f选项的逻辑位于上述代码的结尾部分。其中,我们测试了文件是否存在,如果存在,再测试文件是否可写。为此,执行touch命令并检验生成的文件是否为普通文件。这两处测试分别考虑到了输入的路径是否无效(touch会执行失败)以及如果文件已存在,其是否为普通文件。

可以看到,实际生成页面时调用了write_html_page函数,该函数的输出被传入标准输出(如果filename变量为空)或重定向到指定文件。因为HTML代码有两个可能的目的地,因此有必要将write_html_page转换为Shell函数,以避免冗余代码。

4、总结

有了位置参数,我们现在能够写出功能更强的脚本。例如,对于重复性任务,我们可以利用位置参数编写实用Shell函数,并将其放入用户的.bashrc文件。

sys_info_page程序已经变得越来越复杂,下面给出了完整的代码并着重标出了最新的改动:

#!/bin/bash# sys_info_page: 程序输出系统显示页PROGNAME="$(basename"$0")"TITLE="System Information Report For$HOSTNAME"CURRENT_TIME="$(date+"%x %r %Z")"TIMESTAMP="Generated$CURRENT_TIME, by$USER"report_uptime(){cat<<-_EOF_<h2>System Uptime</h2><pre>$(uptime)</pre>_EOF_return}report_disk_space(){cat<<-_EOF_<h2>Disk Space Utilization</h2><pre>$(df-h)</PRE>_EOF_return}report_home_space(){if[["$(id-u)"-eq0]];thencat<<-_EOF_<h2>Home Space Utilization(All Users)</h2><pre>$(du-sh/home/*)</pre>_EOF_elsecat<<-_EOF_<h2>Home Space Utilization($USER)</h2><pre>$(du-sh"$HOME")</pre>_EOF_fireturn}usage(){echo"$PROGNAME: usage:$PROGNAME[-f file | -i]"return}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}# 进程命令行选项interactive=filename=while[[-n"$1"]];docase"$1"in-f|--file)shiftfilename="$1";;-i|--interactive)interactive=1;;-h|--help)usageexit;;*)usage>&2exit1;;esacshiftdone# 交互模式if[[-n"$interactive"]];thenwhiletrue;doread-p"Enter name of output file: "filenameif[[-e"$filename"]];thenread-p"'$filename' exists. Overwrite? [y/n/q] > "case"$REPLY"inY|y)break;;Q|q)echo"Program terminated."exit;;*)continue;;esacelif[[-z"$filename"]];thencontinueelsebreakfidonefi# 输出HTML页if[[-n"$filename"]];theniftouch"$filename"&&[[-f"$filename"]];thenwrite_html_page>"$filename"elseecho"$PROGNAME: Cannot write file '$filename'">&2exit1fielsewrite_html_pagefi

该程序还不完善,有几处地方仍有改善的空间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询