命令行的解析和扩展
首先看这样一个脚本sa:
pre=:
post=:
printf "$pre%s$post\n" "$@"
注意其中的$@用双引号引起来了,这样会将脚本运行时的参数一个一行输出。比如说,如果输入:
sa a b "c c"
则输出将会是
:a:
:b:
:c c:
但如果直接写$@而不要双相号,输出将会是:
:a:
:b:
:c:
:c:
这说明,bash在执行每条语句时,会对命令行的参数进行解析,它会将参数以没有引在双引号里的空格为分隔分开,然后再时行扩展,最后将结果作为实参传给该命令。
引号
前面已经说了,bash会跟据空格(包括tab和换行)对参数进行分割。但以下空格不包括在内:被单引号或双引号引起来的、被转义符‘\‘转义了的。这些空格都会被看成是单词的一部分。
比如,输入:
sa \ this "is a" 'demonstration of' \ quotes\ and\ escapes (quote前面有两个空格)
输出将会是:
: this:
:is a:
:demonstration of:
: :
:quotes and escapes:
在双引号中有双引号字符时要转义,但单引号中有双引号字符或双引号中有单引号字符时都不需要转义。单引号中不能出现单引号(因为单引号中所有字符都不会被转义)。
两个被引起来的词之前如果没有空格将会被合成一个参数。形式为$'string'字符串中string里可以出现转义的单引号。
如:
echo $'\'line1\'\n\'line2\''
输出为:
'line1'
'line2'
大括号
大括号中的参数以逗号分隔,注意逗号两边不能有空格,否则会将大括号看成是一个普通字符。
如:
sa {one,two,three}
输出为:
:one:
:two:
:three:
大括号中还可以表示一个范围内的序列,如:
sa {1..3}
输出为:
:1:
:2:
:3:
sa pre{d,l}ate
输出为:
:predate:
:prelate:
大括号可以嵌套,如:
sa {{1..3},{a..c}}
bash版本4中,数字可以用0填充,增量可以自己指定,如:
sa {01..14..3}
输出为:
:01:
:04:
:07:
:10:
:13:
字符也有类似的,如{a..e..3}
波浪符
sa ~
将输出用户的home目录
~号后面可以接登录名,表示对应用户的home目录(如果用户不存在,则~将被看成是一个普通字符)。
参数和变量
形参和变量名前加$符表示其取值,为了避免歧义,有时形参名或变量名还要用大括号括起来。如:
first=Jane
last=Johnson
sa "$first_$last" "${first}_$last"
输出会是:
:Johnson:
Jane_Johnson
这是因为first_也是一个合法的变量名,它会被解释成空。
算术表达式
形式为$(( expression )),注意空格。
如果表达式没有用双引号引起来,则表达式的结果将会受单词分割的影响。
expression中可以用 **来求幂。
命令代入
形式为` command `或$( command )。执行时它们将会被相应命令的执行结果所代替。
如果命令代入没有被双引号引起来,则结果也会受分词影响。
分词
形参、变量、算术表达式的结果和命令代入的结果如果没有被引起来,都要参加分词。
分词是依据IFS(internal field seperator)来进行的。IFS的默认值为$' \t\n'(注意前面已经讲过,如果单引号前面加$符,则其中的转义符是有效的)。
可以对IFS进行赋值来自定义分隔符。
如果设定的IFS中既有空格,也有其他字符,分词过程中,空格将会直接省去,而另一字符将会划定一个领域。如:
IFS=' :'
var="qwerty : uiop : :: er " ## : :: delimits 2 empty fields
sa $var
输出将会是:
:qwerty:
:uiop:
::
::
:er:
:uiop:
::
::
:er:
而如果IFS中没有空格,则在分词过程中,空格将会被保留。
路径名称
如果在没有被引号引起的参数中出现了*,?,[]则首先会查看当前目录下有没有匹配的文件(*号匹配多个字符,?号匹配一个字符,中括号中的多个字符匹配一个如[a-0]匹配a到o的任意一个字符,[[:lower:]]匹配所有的小写字母)。
进程代入
进程代入将会为一个命令或一个命令列表产生一个临时文件。可以用来代替管道。与管道不同的是,当将一条命令的执行结果以管道的形式传一个循环语句时,循环语句中定义的变量在循环外是不可见的。
如:
s -l |
while read perms links owner group size month day time file
do
totalsize=$(( ${totalsize:=0} + ${size:-0} ))
done
echo ${totalsize-unset} ## print "unset" if variable is not set
输出将会是unset。
但如果这样写:
while read perms links owner group size month day time file
do
printf "%10d %s\n" "$size" "$file"
totalsize=$(( ${totalsize:=0} + ${size:-0} ))
done < <(ls -l *)
echo ${totalsize-unset}
则会输出totalsize的值。
解析选项
运行脚本时有时需要输入一些选项,选项就是那些以连词符号开头的参数,有些选项还需要接一个实参,有些不需要,可以一个连词符号后接多个选项名,但这个选项名中最多只能有一个要实参,且有的话必须排在最后。
用getopts来解析。形式为
getopts OPTSTRING var
OPTSTRING中包含选项字母,如果选项要实参的话字母后面要加一个:号。
当getopts解析完所有的参数或遇到一个‘--‘则会成功返回,余下的参数将会作为实参交给脚本。
有一个例子parseopts:
progname=${0##*/} ## Get the name of the script without its path
## Default values
verbose=0
filename=
## List of options the program will accept;
## those options that take arguments are followed by a colon
optstring=f:v
## The loop calls getopts until there are no more options on the command line
## Each option is stored in $opt, any option arguments are stored in OPTARG
while getopts $optstring opt
do
case $opt in
f) filename=$OPTARG ;; ## $OPTARG contains the argument to the option
v) verbose=$(( $verbose + 1 )) ;;
*) exit 1 ;;
esac
done
## Remove options from the command line
## $OPTIND points to the next, unparsed argument
shift "$(( $OPTIND - 1 ))"
## Check whether a filename was entered
if [ -n "$filename" ]
then
if [ $verbose -gt 0 ]
then
printf "Filename is %s\n" "$filename"
fi
else
if [ $verbose -gt 0 ]
then
printf "No filename entered\n" >&2
fi
exit 1
fi
## Check whether file exists
if [ -f "$filename" ]
then
if [ $verbose -gt 0 ]
then
printf "Filename %s found\n" "$filename"
fi
else
if [ $verbose -gt 0 ]
then
printf "File, %s, does not exist\n" "$filename" >&2
fi
exit 2
fi
## If the verbose option is selected,
## print the number of arguments remaining on the command line
if [ $verbose -gt 0 ]
then
printf "Number of arguments is %d\n" "$#"
fi
OPTIND是下一个选项的索引
相关命令
head:截取文件的前N行,N缺省为10。
cut:截取文件中的列。