http://bbs.chinaunix.net/viewthread.php?tid=704241&extra=page%3D1
在这个帖子中大家讨论了一些关于$@与$*的话题,但当我试图完全解释其中的每个结果时,感到了巨大的困难。
下面是我对bash工作原理的一些理解和心得,大部分来自bash的manpage。这不是一篇官方文档,很多地方可能用字不当或是对原理的误解,请
大家即时指出,以免误导观众。
1.相关的两个重要的基本定义:
字(word/token): shell中由字符序列构成的一个处理单位。
元字符(metacharacter): 当没有被引用时(when unquoted),对shell具有特殊含义,并导致字分割,bash中的元字符有且只有:
| & ; ( ) < > space tab
2.bash如何处理一个简单命令(Simple command)
2.1什么是简单命令?
用白话说:shell会一句句执行命令,那么每一个句子就是一条简单命令
严谨点的定义:由可选的赋值列表,重定向操作,命令,参数构成的序列,它将由 换行符 | & && || ; 终止。
比如下面就是一个简单命令: a=123 echo 1 >file &
相对于简单命令:还有其它一些shell命令的形式:
流水线(Pipelines): Smiple-command1|Smiple-command2|.....
命令列表(List): Pipelines1&&Pipelines2||.....
组合命令: (list1
list2 ...)
{ list
list; }
2.2bash读入一行命令时,首先作的是什么?
首先作的就是用元字符把这一行命令分割成字,然后从左到右去决定下一步作什么,比如判断是不是一个开放的语句而去读下一行
是不是一个alias要替代,是一个简单命令还是个命令列表......这些暂且不表,以免离题太远。
2.3好了,就算bash当前处理的是一个简单命令,那它又将干些什么呢?
bash读入一个简单命令,它将进行一系列的预处理(performed);首先就是用元字符对它进行分割成为字(这个对任何命令形式都是一样的,参(2.2)
然后从左到右扫描第个字进行以下四步预处理:
s1.如果字是赋值操作(比如var=123),或IO重定向操作,将保留下来以备以后的处理
s2.陈了上面两种情况的其它字将进行扩展,其规则在下面详述,扩展结果的第一个字作为命令名字,其它作为它的参数
s3.准备IO重定向
s4.对赋值操作进行扩展,它的扩展规则与s2有些不同,下面会进行讨论。如果本行命令进行了s2的处理,那么赋值后的变量将作为环境变量传
给s2中的命令,如果本行没有命令名字,那么赋值操作将在本进程中进行。
这时shell就可以进行引用的移除(用作引用的"'将被移除)判断命令名字是函数/内置命令(如果是内置命令eval还要送回最开始进行新一轮的处理),如果不是还要进行路径扩展寻找加载的命令在什么地方等等(这些也从略吧)
3.上面的扩展遵循什么规则
3.1 s2的扩展如何进行:
扩展包括先后7个步骤:每个步骤都按从左到右的顺序进行
e1大括号扩展(brace expansion)
e2波浪号扩展(tilde expansion)
e3参数扩展与变量替代(parameter and variable expansion)
e4命令替代(command substitution)
e5数学扩展(arithmetic expansion)
e6单词分割(word splitting)
e7路径扩展(pathname expansion)
我们重点讨论的就是e3与e6
注意上面的扩展只有e1和e6e7会改变字的数量,其它的扩展都是从一个字变为另一个字,除了数组变量的扩展"$array[@]"
还要注意的就是引用(quoted/qouting),O'eilly的《learning the bash shell》一书中给了一个形象的扩展路线图,相信大家也都熟悉,就
把它挂在下面以作参考
3.2 s4中赋值操作的扩展如何进行
它将包含四步:
e2波浪号扩展(tilde expansion)
e3参数扩展与变量替代(parameter and variable expansion)
e4命令替代(command substitution)
e5数学扩展(arithmetic expansion)
而且遵循附图中qouting作用的规则
(为什么没有e1和e6,当然不应该有:想像一下:如果 var={1,2}会进行e1的扩展的话,它会被扩展为: var=1 2,这显然是个病句,e6也是一样的),最后进行引用的移除(用作引用的"'将被移除)并将结果赋值于变量
3.3 e6单词分割
在进行了e3e4e5(parameter and variable expansion command substitution arithmetic expansion)之后,bash会对它们的结果进行再一次的分割,注意只是这三种扩展的结果才进行二次分割,显然这种分割是必要的,如果你不希望这种分割,请使用quoting。
这次分割将按照IFS变量的值进行
3.4 e3中的参数扩展中数组变量如何进行扩展
一个标准变量$var或单个数组元素${a[3]}的扩展是简单明了的,但对于数组,有几个特殊的扩展
${a[* ]}将扩展为包含每个数组元素一个字${a[1]}\c${a[2]}\c........${a[n]} 其中\c是IFS的第一个字符,如果IFS为缺省值,\c是space,如果IFS设置为空(null),则\c也为空
${a[@]}将扩展为n个字,每个数组元素为一个字,注意扩展后结果的首尾字是“开放性的”
比如
var=(a b c)
echo 1${var[@]}3
处理后将分割为4个字
`echo'、`1a'、`b'与`c3'注意 a与1不会分成两个字
"${a[@]}"将会用"引用${a[@]}扩展成的每个字让它不进行word splitting与pathname expansion
(这里我的解释与manpage中稍有不同,manpage中指出只有"${a[@]}"才会扩展成n个字,但我认为不加引号一样是n个字)
3.5 $* $@与"$*" "$@"
上面四个变量只是对一个特殊数组的引用--位置参数,与对其它数组的处理没什么不同
3.6赋值操作时如何处理${a[* ]}与${a[@]}
${a[* ]}与"${a[* ]}"不用多讲了,它们本身就是一个字,而且因为赋值操作不进行参数扩展后的word splitting,又因为赋值操作最后的引号去除处理, var=${a[* ]}与var="${a[* ]}"不会产生任何不同的结果
而对于var=${a[@]},因为$var只可能是一个字,可以在赋值时${a[@]}扩展的n个字被赋值操word splitting用space连成一个字。var="${a[@]}"则保护不进行word splitting拆分
[ 本帖最后由 waker 于 2006-2-24 12:38 编辑 ]
waker 回复于:2006-02-24 12:11:27
好了,唠叨了这么多,试着读解一下incompat.sh的结果吧
#!/bin/bash
# Erratic behavior of the "$*" and "$@" internal Bash variables,
#+ depending on whether they are quoted or not.
# Inconsistent handling of word splitting and linefeeds.
set -- "First one" "second" "third:one" "" "Fifth: :one"
# Setting the script arguments, $1, $2, etc.
echo
echo 'IFS unchanged, using "$*"'
c=0
for i in "$*" # quoted
do echo "$((c+=1)): [$i]" # This line remains the same in every instance.
# Echo args.
done
###############
#结果
#1: [First one second third:one Fifth: :one]
#$*被扩展为一个字`First one second third:one Fifth: :one',注意扩展时的\c为space
#由于""的作用,不进行word splitting
#变量i in 一个字
###############
echo ---
echo 'IFS unchanged, using $*'
c=0
for i in $* # unquoted
do echo "$((c+=1)): [$i]"
done
###############
#结果
#1: [First]
#2: [one]
#3: [second]
#4: [third:one]
#5: [Fifth:]
#6: [:one]
#$*被扩展为一个字`First one second third:one Fifth: :one',注意扩展时的\c为space
#然后进行word splitting扩展为`First'、`one'、 `second'、`third:one'、`Fifth:'、`:one'六个字,
注意使用缺省IFS时中间连续的空格被解释为一个字分割符
#变量i in 六个字`First'、`one'、 `second'、`third:one'、`Fifth:'、`:one'
###############
echo ---
echo 'IFS unchanged, using "$@"'
c=0
for i in "$@"
do echo "$((c+=1)): [$i]"
done
#结果
#1: [First one]
#2: [second]
#3: [third:one]
#4: []
#5: [Fifth: :one]
#$@被扩展为五个字`First one'、`second'、`third:one'、`空字串'、`Fifth: :one'
#由于""的作用,不进行word splitting
#变量i in 五个字`First'、`one'、 `second'、`third:one'、`Fifth: :one'
echo ---
echo 'IFS unchanged, using $@'
c=0
for i in $@
do echo "$((c+=1)): [$i]"
done
#结果
#1: [First]
#2: [one]
#3: [second]
#4: [third:one]
#5: [Fifth:]
#6: [:one]
#$@被扩展为五个字`First one'、`second'、`third:one'、`空字串'、`Fifth: :one'
#然后进行word splitting扩展为`First'、`one'、 `second'、`third:one'、`Fifth:'、`:one'六个字,注意由于没有""的保护上一步那个空
字串已经不见了
#变量i in 六个字`First'、`one'、 `second'、`third:one'、`Fifth:'、`:one'
echo ---
IFS=:
echo 'IFS=":", using "$*"'
c=0
for i in "$*"
do echo "$((c+=1)): [$i]"
done
###############
#结果
#1: [First one:second:third:one::Fifth: :one]
#$*被扩展为一个字`First one:second:third:one::Fifth: :one',注意扩展时的\c为:
#由于""的作用,不进行word splitting
#变量i in 一个字
###############
echo ---
echo 'IFS=":", using $*'
c=0
for i in $*
do echo "$((c+=1)): [$i]"
done
###############
#结果
#1: [First one]
#2: [second]
#3: [third]
#4: [one]
#5: []
#6: [Fifth]
#7: [ ]
#8: [one]
#$*被扩展为一个字`First one:second:third:one::Fifth: :one',注意扩展时的\c为:
#然后进行word splitting扩展为`First one'、`second'、`third'、`one'、`空字串'、`Fifth'、` '、`one'八个字,
注意使用指定IFS时每间个IFS都被认为是一个字分割符
#变量i in 八个字`First one'、`second'、`third'、`one'、`空字串'、`Fifth'、` '、`one'
###############
echo ---
var=$*
echo 'IFS=":", using "$var" (var=$*)'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
###############
#结果
#1: [First one:second:third:one::Fifth: :one]
#$*被扩展为一个字`First one:second:third:one::Fifth: :one'并赋值给var,注意扩展时的\c为:
#由于""的作用,$var替代后不进行word splitting
#变量i in 一个字`First one:second:third:one::Fifth: :one'
###############
echo ---
echo 'IFS=":", using $var (var=$*)'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done
###############
#结果
#IFS=":", using $var (var=$*)
#1: [First one]
#2: [second]
#3: [third]
#4: [one]
#5: []
#6: [Fifth]
#7: [ ]
#8: [one]
#1: [First one:second:third:one::Fifth: :one]
#$*被扩展为一个字`First one:second:third:one::Fifth: :one'并赋值给var,注意扩展时的\c为:
#$var替代后进行word splitting扩展为`First one'、`second'、`third'、`one'、`空字串'、`Fifth'、` '、`one'八个字,
注意使用指定IFS时每间个IFS都被认为是一个字分割符
#变量i in 八个字`First one'、`second'、`third'、`one'、`空字串'、`Fifth'、` '、`one'
###############
echo ---
var="$*"
echo 'IFS=":", using $var (var="$*")'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done
###############
#结果
#IFS=":", using $var (var=$*)
#1: [First one]
#2: [second]
#3: [third]
#4: [one]
#5: []
#6: [Fifth]
#7: [ ]
#8: [one]
#$*被扩展为一个字`First one:second:third:one::Fifth: :one'并赋值给var,注意扩展时的\c为:
#$var替代后进行word splitting扩展为`First one'、`second'、`third'、`one'、`空字串'、`Fifth'、` '、`one'八个字,
注意使用指定IFS时每间个IFS都被认为是一个字分割符
#变量i in 八个字`First one'、`second'、`third'、`one'、`空字串'、`Fifth'、` '、`one'
###############
echo ---
echo 'IFS=":", using "$var" (var="$*")'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
###############
#结果
#1: [First one:second:third:one::Fifth: :one]
#$*被扩展为一个字`First one:second:third:one::Fifth: :one'并赋值给var,注意扩展时的\c为:
#由于""的作用,$var替代后不进行word splitting
#变量i in 一个字`First one:second:third:one::Fifth: :one'
###############
echo ---
echo 'IFS=":", using "$@"'
c=0
for i in "$@"
do echo "$((c+=1)): [$i]"
done
###############
#结果
#1: [First one]
#2: [second]
#3: [third:one]
#4: []
#5: [Fifth: :one]
#$@被扩展为五个字`First one'、`second'、`third:one'、`空字串'、`Fifth: :one'
#由于""的作用,不进行word splitting
#变量i in 五个字`First one'、`second'、`third:one'、`'、`Fifth: :one'
###############
echo ---
echo 'IFS=":", using $@'
c=0
for i in $@
do echo "$((c+=1)): [$i]"
done
#结果
1: [First one]
2: [second]
3: [third]
4: [one]
5: []
6: [Fifth]
7: [ ]
8: [one]
###############
#$@被扩展为五个字`First one'、`second'、`third:one'、`空字串'、`Fifth: :one'
#这五个字再进行word splitting,扩展为`First one'、`second'、`third'、`one'、`空字串'、`Fifth'、` '、`one'八个字
#注意这里的第3和4由上一上步的`third:one'扩展,第678由`Fifth: :one'扩展
#变量i in 八个字`First one'、`second'、`third'、`one'、`空字串'、`Fifth'、` '、`one'
###############
echo ---
var=$@
echo 'IFS=":", using $var (var=$@)'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done
###############
#结果
#1: 1: [First one second third one Fifth one]
#$@被扩展为五个字`First one'、`second'、`third:one'、`空字串'、`Fifth: :one'并word splitting成为`First one'、`second'、#`third'、`one'、`空字串'、`Fifth'、` '、`one'再由space串成一串赋值给var
#$var在替代后进行word splitting,由于不包含IFS,所以分不分都一样
#变量i in 一个字`First one second third one Fifth one'
###############
echo ---
echo 'IFS=":", using "$var" (var=$@)'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
###############
#结果
#1: 1: [First one second third one Fifth one]
#$@被扩展为五个字`First one'、`second'、`third:one'、`空字串'、`Fifth: :one'并word splitting成为`First one'、`second'、#`third'、`one'、`空字串'、`Fifth'、` '、`one'再由space串成一串赋值给var
#$var不进行word splitting
#变量i in 一个字`First one second third one Fifth one'
###############
echo ---
var="$@"
echo 'IFS=":", using "$var" (var="$@")'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
###############
#结果
#1: 1: [First one second third:one Fifth: :one]
#"$@"被扩展为五个字`First one'、`second'、`third:one'、`空字串'、`Fifth: :one'并由space串成一串赋值给var
#$var不进行word splitting
#变量i in 一个字`First one second third:one Fifth: :one'
###############
echo ---
echo 'IFS=":", using $var (var="$@")'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done
###############
#结果
#1: [First one second third]
#2: [one Fifth]
#3: [ ]
#4: [one]
#"$@"被扩展为五个字`First one'、`second'、`third:one'、`空字串'、`Fifth: :one'并由space串成一串赋值给var
#$var在替代后进行word splitting,由一个字`First one second third:one Fifth: :one'分割为`First one second'
`third'、`one Fifth'、` '、`one'四个字
#变量i in 四个字`First one second'、`third'、`one Fifth'、` '、`one'
###############
echo
# Try this script with ksh or zsh -y.
exit 0
# This example script by Stephane Chazelas,
# and slightly modified by the document author.
[ 本帖最后由 waker 于 2006-2-24 13:25 编辑 ]
r2007 回复于:2006-02-24 13:16:38
引用:原帖由 waker 于 2006-2-24 12:10 发表
echo ---
echo 'IFS=":", using "$@"'
c=0
for i in "$@"
do echo "$((c+=1)): [$i]"
done
###############
#结果
#1: [First one]
#2: [second]
#3: [third:one]
#4: []
#5: [Fifth: :one]
#$@被扩展为五个字`First one'、`second'、`third:one'、`空字串'、`Fifth: :one'
#由于""的作用,不进行word splitting
#变量i in 五个字`First'、`one'、 `second'、`third:one'、`Fifth: :one'
###############
从结果看:变量i in 五个字应该是`First one'、 `second'、`third:one'、`'、`Fifth: :one'
是笔误还是我的理解不对?
waker 回复于:2006-02-24 13:26:11
改,有很多都是cp的 :mrgreen:
r2007 回复于:2006-02-24 14:16:59
瓦兄:文中提到 var=${a[* ]}与var="${a[* ]}"不会产生任何不同的结果
bash 2.05b中,在不使用默认IFS的情况下,这两种方式还是有区别的。
请看下面的测试
r2007@www r2007 $ IFS=:
r2007@www r2007 $ a[0]=123
r2007@www r2007 $ a[1]=abc:xyz
r2007@www r2007 $ a[2]=...
r2007@www r2007 $ v1=${a[*]}
r2007@www r2007 $ v2="${a[*]}"
r2007@www r2007 $ echo $v1
123 abc xyz ...
r2007@www r2007 $ echo "$v1"
123 abc xyz ...
r2007@www r2007 $ echo $v2
123 abc xyz ...
r2007@www r2007 $ echo "$v2"
123:abc:xyz:...
r2007@www r2007 $ unset IFS
r2007@www r2007 $ echo $v1
123 abc xyz ...
r2007@www r2007 $ echo $v2
123:abc:xyz:...
r2007@www r2007 $
flw 回复于:2006-02-24 14:36:32
等我当上 shell 版主了我给你设精华 :D
johnny_jiang 回复于:2006-02-24 16:51:08
引用:原帖由 r2007 于 2006-2-24 14:16 发表
瓦兄:文中提到 var=${a[* ]}与var="${a[* ]}"不会产生任何不同的结果
bash 2.05b中,在不使用默认IFS的情况下,这两种方式还是有区别的。
请看下面的测试
r2007@www r2007 $ IFS=:
r2007@ ...
是的,在默认的情况下,也有特例
x=('1 2' '3 4')
y=${x[*]}
z="${x[*]}"
echo "$y"
1 2 3 4
echo "$z"
1 2 3 4
而且"$@"等同于"$1" "$2" ... "$N" ,所以我个人认为,$@也是被IFS分割的,这样我觉得能更好的解释waker兄的例子
waker 回复于:2006-02-24 17:09:47
呵呵,越来越复杂了,再研究一下
woodie 回复于:2006-02-24 17:38:46
引用:原帖由 waker 于 2006-2-24 12:10 发表
...
元字符(metacharacter): 当没有被引用时(when unquoted),对shell具有特殊含义,并导致字分割,bash中的元字符有且只有:
| & ; ( ) < > space tab
...
瓦兄,newline也应该属于原字符吧?
johnny_jiang 回复于:2006-02-24 19:57:27
引用:原帖由 woodie 于 2006-2-24 17:38 发表
瓦兄,newline也应该属于原字符吧?
对的,根据Learning the bash Shell - 2nd Edition (o'reilly)和man page中的陈述,在所有的expansion完成后,对那些不是从expansion中得到的引用进行移除(\,",'),也就是路径等扩展完成后,在重定向之前。
|