变量和参数
变量赋值时,'='左右都不能有空格。 语句结尾不需要分号;除了在变量赋值和for循环语句头中,bash中的变量使用必须在变量前加"$";以单引号括起来的变量将不被解释为变量,如'$str'被解释为字符串。
1, 反斜杠
当命令接收含有元字符如$,*,?等的参数时,就必须将这些元字符用引号引起来。可以用反斜杠将其转义。
2. 单引号
单引号强制shell将引号内的所有字符视为其字面意义。包括 *,`,?, $ 等。因此单引号内不可再内嵌单引号,即便反斜杠也没有特殊意义。
3. 双引号
双引号会确切的处理转义字符,变量,算术,命令替换(反引号括起来的命令)。双引号内的$,`,",\等如需用到字面意义都需用"\"转义。
注:形式为$(string)或`string`的表达式即为命令替换。
4. let命令
算术运算:可用test[], $((...)), 命令替换`expr $i + 1`, let命令四种方法。
1:使用 expr 外部程式
2:使用 $(( ))
3:使用 $[ ]
4:使用let 命令
r=`expr 4 + 5`
r=$(( 4 + 5 ))
r=$[ 4 + 5 ]
let n=n+1
m=$[ m + 1]
m=`expr $m + 1`
m=$(($m + 1))
let m=m+1
使用算术操作符
let "n = $n + 1" # let "n = n + 1" 也可以.
: $((n = $n + 1)) # ":" 是必需的, 因为如果没有":"的话, Bash将会尝试把"$((n = $n + 1))"解释为一个命令.
n=$(($n + 1))
: $[ n = $n + 1 ] , ":" 是必需的, 因为如果没有":"的话,Bash将会尝试把"$[ n = $n + 1 ]"解释为一个命令.
n=$[ $n + 1 ] # 即使"n"被初始化为字符串, 这句也能够正常运行.
(( n = n + 1 )) #这句是一种更简单方法.
C风格的增量操作.
let "n++" # let "++n" 也可以.
(( n++ )) # (( ++n ) 也可以.
$(( n++ )) # : $(( ++n )) 也可以.
echo -n "$n "
$[ n++ ] # : $[ ++n ]] 也可以.
变量替换$:注意$variable事实上只是${variable}的简写形式. 在某些上下文中$variable可能会引起错误,这时候你就需要用${variable}了。
被一对双引号(" ")括起来的变量替换是不会被阻止的. 所以双引号被称为部分引用, 有时候又被称为"弱引用". 但是如果使用单引号的话(' '), 那么变量替换就会被禁止了, 变量名只会被解释成字面的意思, 不会发生变量替换. 所以单引号被称为全引用, 有时候也被称为"强引用".
强烈注意, 在赋值的的时候, 等号前后一定不要有空格. 如果出现空格会怎么样? "VARIABLE =value"14 脚本将尝试运行一个"VARIABLE"的命令, 带着一个"=value"参数.
hello="A B C D" echo $hello # A B C D echo "$hello" # A B C D
就象你看到的echo $hello 和 echo "$hello" 将给出不同的结果.引用一个变量将保留其中的空白, 当然, 如果是变量替换就不会保留了。
变量赋值=,赋值操作(前后都不能有空白). 因为=和-eq都可以用做条件测试操作, 所以不要与这里的赋值操作相混淆.
Bash变量是不区分类型的,本质上, Bash变量都是字符串. 但是依赖于具体的上下文, Bash也允许比较操作和整数操作. 其中的关键因素就是, 变量中的值是否只有数字。 Bash不能够处理浮点运算. 它会把包含小数点的数字看作字符串.
特殊的变量类型:
局部变量:这种变量只有在代码块或者函数中(参见函数中的局部变量)才可见.
环境变量:这种变量将影响用户接口和shell的行为。
如果一个脚本要设置一个环境变量, 那么需要将这些变量"export"出来, 也就是需要通知到脚本
本地的环境. 这是export命令的功能.
一个脚本只能够export变量到这个脚本所产生的子进程, 也就是说只能够对这个脚本所产生的命令和进程起作用. 如果脚本是从命令行中调用的, 那么这个脚本所export的变量是不能影响命令行环境的. 也就是说, 子进程是不能够export变量来影响产生自己的父进程的环境的.
位置参数:从命令行传递到脚本的参数: $0, $1, $2, $3 . . .$0就是脚本文件自身的名字, $1 是第一个参数, $2是第二个参数, $3是第三个参数, 然后是第四个. [1] $9之后的位置参数就必须用大括号括起来了, 比如, ${10}, ${11}, ${12}.两个比较特殊的变量$*和$@ 表示所有的位置参数.
shift命令会重新分配位置参数, 其实就是把所有的位置参数都向左移动一个位置.$1 <--- $2, $2 <--- $3, $3 <--- $4, 等等.原来的$1就消失了, 但是$0 (脚本名)是不会改变的. 如果传递了大量的位置参数到脚本中, 那么shift命令允许你访问的位置参数的数量超过10个, 当然{}标记法也提供了这样的功能.
$0参数是由调用这个脚本的进程所设置的. 按照约定, 这个参数一般就是脚本的名字.
$HOSTNAME返回主机名,和hostname命令返回结果相同。
$HOSTTYPE,主机类型。就像$MACHTYPE, 用来识别系统硬件. $OSTYPE操作系统类型
$IFS,内部域分隔符。$IFS处理其他字符与处理空白字符不同. IFS=: , var=":a::b:c:::" # [][a][][b][c][] [] [] ;IFS=" ",var=" a b c "#[a][b][c]
$PWD工作目录(你当前所在的目录)
$REPLY,当没有参数变量提供给read命令的时候, 这个变量会作为默认变量提供给read命令
$! 运行在后台的最后一个作业的PID(进程ID)
$$ 脚本自身的进程ID. $$变量在脚本中经常用来构造"唯一的"临时文件名 . 这么做通常比调用mktemp命令来的简单.
引用
引用的字面意思就是将字符串用双引号括起来. 它的作用就是保护字符串中的特殊字符不被shell或者shell脚本重新解释, 或者扩展. (我们这里所说的"特殊"指的是一些字符在shell中具有的特殊意义,而不是字符的字面意思, 比如通配符 -- *.)
引用还可以改掉echo's不换行的"毛病".
bash$ echo $(ls -l) total 8 -rw-rw-r-- 1 bo bo 13 Aug 21 12:57 t.sh -rw-rw-r-- 1 bo bo 78 Aug 21 12:57 u.sh bash$ echo "$(ls -l)" total 8 -rw-rw-r-- 1 bo bo 13 Aug 21 12:57 t.sh -rw-rw-r-- 1 bo bo 78 Aug 21 12:57 u.sh
|
引用变量:使用双引号还能够阻止单词分割(word splitting). [3] 如果一个参数被双引号扩起来的话, 那么这个参数将认为是一个单元, 即使这个参数包含有空白, 那里面的单词也不会被分隔开. "单词分割(Word splitting)", 在这种上下文中, 意味着将一个字符串分隔成一些不连续的, 分离的参数.
当在命令行中使用时, 如果在双引号中包含"!"的话, 那么会产生一个错误(译者注: 比如,echo "hello!"). 这是因为感叹号被解释成历史命令了. 但是如果在脚本中, 就不会存在这个问题, 因为在脚本中Bash历史机制是被禁用的.
指定变量的类型: 使用declare或者typeset:
declare或者typeset内建命令(这两个命令是完全一样的)允许指定变量的具体类型.
-r 只读 : declare -r var1, (declare -r var1与readonly var1是完全一样的)
-i 整型: declare -i number , 如果把一个变量指定为整型的话, 那么即使没有expr或者let命令, 也允许使用特定的算术运算.
n=6/3 echo "n = $n" # n = 6/3
declare -i n ; n=6/3; echo "n = $n" # n = 2
-a 数组: declare -a indices 变量indices将被视为数组.
-f 函数: declare -f. 如果在脚本中使用declare -f, 而不加任何参数的话, 那么将会列出这个脚本之前定义的所有函数. declare -f function_name如果在脚本中使用declare -f function_name这种形式的话, 将只会列出这个函数的名字.
-x export: declare -x var3 这句将会声明一个变量, 并作为这个脚本的环境变量被导出.
-x var=$value: declare -x var3=373 declare命令允许在声明变量类型的同时给变量赋值.
变量的间接引用
假设一个变量的值是第二个变量的名字. 那么我们如何从第一个变量中取得第二个变量的值呢? 比如,如果a=letter_of_alphabet并且letter_of_alphabet=z, 那么我们能够通过引用变量a来获得z么? 这确实是可以做到的, 它被称为间接引用. 它使用eval var1=\$$var2这种不平常的形式.
a=letter_of_alphabet # 变量"a"的值是另一个变量的名字.
letter_of_alphabet=z
直接引用.: echo "a = $a" # a = letter_of_alphabet
间接引用.eval a=\$$a echo "Now a = $a" # 现在 a = z
$RANDOM: 产生随机整数
$RANDOM是Bash的内部函数 (并不是常量), 这个函数将返回一个伪随机 [1] 整数, 范围在0 - 32767之间. 它不应该被用来产生密匙.
双圆括号结构
双圆括号结构
与let命令很相似, ((...))结构允许算术扩展和赋值. 举个简单的例子, a=$(( 5 + 3 )), 将把变量"a"设为"5 + 3", 或者8. 然而, 双圆括号结构也被认为是在Bash中使用C语言风格变量操作的一种处理机制.
(( a = 23 )) # C语言风格的变量赋值, "="两边允许有空格. echo "a (initial value) = $a"
(( a++ )) # C语言风格的后置自加.echo "a (after a++) = $a"
数组: 新版本的Bash支持一维数组. 数组元素可以使用符号variable[xx]来初始化. 另外, 脚本可以使用declare -a variable语句来指定一个数组. 如果想解引用一个数组元素(也就是取值), 可以使用大括号, 访问形式为${variable[xx]}.
area[11]=23 area[13]=37 area[51]=UFOs echo ${area[11]} # 需要{大括号}.
# 数组成员不一定非得是相邻或连续的. 数组的部分成员可以不被初始化. 数组中允许空缺元素, 实际上, 保存着稀疏数据的数组("稀疏数组")。# 没被初始化的数组成员打印为空值(null变量). 从0开始计算数组下标(也就是数组的第一个元素为[0], 而不是[1])
另一种给数组变量赋值的方法. array_name=( XXX YYY ZZZ ... ), area2=( zero one two three four ), array_name=([xx]=XXX [yy]=YYY ...), area3=([17]=seventeen [24]=twenty-four)
for index in 1 2 3 4 5
do
printf " %s\n" "${Line[index]}"
done
为了计算数组的元素个数, 可以使用${#array_name[@]}或${#array_name[*]}.${#array_name}是数组第一个元素的长度, 也就是${array_name[0]}的长度(字符个数).
echo ${#array[1]} # 第二个数组元素的长度. echo ${#array[*]} # 数组中的元素个数. echo ${#array[@]} # 数组中的元素个数. array2=( [0]="first element" [1]="second element" [3]="fourth element" ) array=( zero one two three four five ) echo ${array[0]} # 0 echo ${array:0} #第一个元素的参数扩展, 从位置0(#0)开始(即第一个字符). echo ${array:1} #ero 第一个元素的参数扩展, 从位置1(#1)开始(即第2个字符).
大部分标准字符串操作都可以用于数组中.
提取尾部的子串
echo ${arrayZ[@]:0} # one two three four five five # 所有元素.
echo ${arrayZ[@]:1} # two three four five five # element[0]后边的所有元素.
echo ${arrayZ[@]:1:2} # two three # 只提取element[0]后边的两个元素.
子串删除
# 从字符串的开头删除最短的匹配, 匹配的子串也可以是正则表达式.
echo ${arrayZ[@]#f*r} # one two three five five # 匹配将应用于数组的所有元素. 匹配到了"four", 并且将它删除.
# 从字符串的开头删除最长的匹配
echo ${arrayZ[@]##t*e} # one two four five five # 匹配将应用于数组的所有元素. 匹配到了"three", 并且将它删除.
# 从字符串的结尾删除最短的匹配
echo ${arrayZ[@]%h*e} # one two t four five five# 匹配将应用于数组的所有元素. 匹配到了"hree", 并且将它删除.
# 从字符串的结尾删除最长的匹配
echo ${arrayZ[@]%%t*e} # one two four five five# 匹配将应用于数组的所有元素. 匹配到了"three", 并且将它删除.
子串替换
# 第一个匹配到的子串将会被替换
echo ${arrayZ[@]/fiv/XYZ} # one two three four XYZe XYZe
# 所有匹配到的子串都会被替换
echo ${arrayZ[@]//iv/YY} # one two three four fYYe fYYe
# 删除所有的匹配子串
# 如果没有指定替换字符串的话, 那就意味着'删除'
echo ${arrayZ[@]//fi/} # one two three four ve ve
# 替换字符串前端子串
echo ${arrayZ[@]/#fi/XY} # one two three four XYve XYve
# 替换字符串后端子串
echo ${arrayZ[@]/%ve/ZZ} # one two three four fiZZ fiZZ
echo ${arrayZ[@]/%o/XX} # one twXX three four five five# 为什么?
${array_name[@]}或${array_name[*]}都与数组中的所有元素相关.同样的, 为了计算数组的元素个数, 可以使用${#array_name[@]}或{#array_name[*]}.${#array_name}是数组第一个元素的长度, 也就是${array_name[0]}的长度(字符个数)
script_contents=( $(cat "$0") ) # 将这个脚本的内容($0) ,赋值给数组.
空数组与包含有空元素的数组, 这两个概念不同
array1=( '' ) # "array1"包含一个空元素.
array2=( ) # 没有元素. . . "array2"为空.
# 添加一个元素到这个数组. array0=( "${array0[@]}" "new1" ) array1=( "${array1[@]}" "new1" ) array2=( "${array2[@]}" "new1" ) # 或 array0[${#array0[*]}]="new2" array1[${#array1[*]}]="new2" array2[${#array2[*]}]="new2"
在子shell中设置和操作变量之后, 如果尝试在子shell作用域之外使用同名变量的话, 将会产生令人不快的结果.子shell和父shell看起来其实就是隔离的。
将echo的输出通过管道传递给read命令可能会产生不可预料的结果. 在这种情况下, read命令的行为就好像它在子shell中运行一样. 可以使用set命令来代替。
通过管道将输出传递到任何循环中, 都会引起类似的问题.
for f in $(find $HOME -type f -atime +30 -size 100k) # 这里没使用管道
另一种方法:将脚本中读取变量的部分放到一个代码块中, 这样一来, 它们就能在相同的子shell中共享了.
find $HOME -type f -atime +30 -size 100k | {
foundone=false
while read f
....
}
将数组传递给函数的技术, 然后"返回"一个数组给脚本的主体.使用命令替换将数组中的所有元素(元素之间用空格分隔)赋值给一个变量, 这样就可以将数组传递到函数中了. 之前提到过一种返回值的策略, 就是将要从函数中返回的内容, 用echo命令输出出来, 然后使用命令替换或者( ... )操作符, 将函数的输出(也就是我们想要得返回值)保存到一个变量中. 如果我们想让函数"返回"数组, 当然也可以使用这种策略.
Pass_Array (){
passed_array=( `echo "$1"` )
echo "${passed_array[@]}
}
original_array=( element1 element2 element3 element4 element5 )
argument=`echo ${original_array[@]}`
returned_array=( `Pass_Array "$argument"` )
利用双括号结构, 就可以让我们使用C风格的语法, 在for循环和while循环中, 设置或者增加变量.
一项很有用的技术是, 重复地将一个过滤器的输出(通过管道)传递给这个相同的过滤器, 但是这两次使用不同的参数和选项. 尤其是tr和grep, 非常适合于这种情况.
在错误的情况下, if-grep test可能不会返回期望的结果, 因为出错文本是输出到stderr上, 而不是stdout.将stderr重定向到stdout上, 就可以解决这个问题
if ls -l nonexistent_filename 2>&1 | grep -q 'No such file ordirectory'