变量和参数

变量赋值时,'='左右都不能有空格。 语句结尾不需要分号;除了在变量赋值和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'

 

posted @ 2014-04-19 10:25  赵翔  阅读(596)  评论(0编辑  收藏  举报