4.4. 特殊变量类型

局部变量

局部变量只在代码块或一个函数里有效 (参考函数局部变量)

环境变量

这种变量会影响Shell的行为和用户接口

Note

在大多数情况下,每个进程都会有一个"环境表", 它由一组由进程使用的环境变量组成。这样看来,Shell看起来和其他的进程一样。

每次一个Shell启动时,它都会创建新的合适的环境变量。如果它增加或是更新一个环境变量,都会使这个Shell的环境表得到更新(译者注:换句话说,更改或增加的变量会立即生效),并且这个Shell所有的子进程(即它执行的命令)能继承它的环境变量。(译者注:准确地说,应该是后继生成的子进程才会继承Shell的新环境变量,已经运行的子进程并不会得到它的新环境变量)。

Caution

分配给环境变量的总空间是有限的,如果创建太多的环境变量或有些环境变量的值太长而占用太多空间会出错。

 bash$ eval "`seq 10000 | sed -e 's/.*/export var&=ZZZZZZZZZZZZZZ/'`"
 
 bash$ du
 bash: /usr/bin/du: Argument list too long
 	          

(多谢Stéphane Chazelas的澄清和提供了上面的例子)

如果一个脚本要设置一个环境变量,则需要将它导出(”exported”),也就是说要通知到脚本的环境表。这就是export命令的功能。

Note

一个脚本只能导出(export)变量到子进程,也就是说只能导出到由此脚本生成的命令或进程中。在一个命令行中运行的脚本不能导出一个变量影响到命令行的环境。子进程不能导出变量到生成它的父进程中。

---

位置参数

命令行传递给脚本的参数是: $0, $1, $2, $3 . . .

$0是脚本的名字,$1是第一个参数,$2是第二个参数,$3是第三个,以此类推。[1] After $9, 在位置参数$9之后的参数必须用括号括起来,例如:${10}, ${11}, ${12}.

特殊变量$*和$@ 表示所有的位置参数。


例子 4-5. 位置参数

   1 #!/bin/bash
   2 
   3 # 至少以10个参数运行这个脚本,例如:
   4 # ./scriptname 1 2 3 4 5 6 7 8 9 10
   5 MINPARAMS=10
   6 
   7 echo
   8 
   9 echo "The name of this script is \"$0\"."
  10 # 用./表示当前目录
  11 echo "The name of this script is \"`basename $0`\"."
  12 # 去掉路径名(查看'basename'命令)
  13 
  14 echo
  15 
  16 if [ -n "$1" ]              # 被测试的变量被双引号引起
  17 then
  18  echo "Parameter #1 is $1"  # 使用引号来使#被转义
  19 fi 
  20 
  21 if [ -n "$2" ]
  22 then
  23  echo "Parameter #2 is $2"
  24 fi 
  25 
  26 if [ -n "$3" ]
  27 then
  28  echo "Parameter #3 is $3"
  29 fi 
  30 
  31 # ...
  32 
  33 
  34 if [ -n "${10}" ]  # 大于 $9的参数必须用花括号括起来.
  35 then
  36  echo "Parameter #10 is ${10}"
  37 fi 
  38 
  39 echo "-----------------------------------"
  40 echo "All the command-line parameters are: "$*""
  41 
  42 if [ $# -lt "$MINPARAMS" ]
  43 then
  44   echo
  45   echo "This script needs at least $MINPARAMS command-line arguments!"
  46 fi  
  47 
  48 echo
  49 
  50 exit 0

花括号 for positional parameters leads to a fairly simple way of referencing the last argument passed to a script on the command line. This also requires indirect referencing.

   1 args=$#           # 传给脚本的参数个数.
   2 lastarg=${!args}
   3 # 也可以用:       lastarg=${!#}
   4 #           (多谢, Chris Monson.)
   5 # 注意lastarg=${!$#}是不会工作的.

由不同的执行名字来调用脚本,一些脚本能够以不同的操作来执行。如果要能办到这一点,脚本需要检查变量$0来确定脚本是如何被调用的。也有可能存在符号链接的路径来调用脚本的情况。参考例子 12-2.

Tip

如果一个脚本需要提供一个命令行参数来执行,但是这个参数没有提供,这可能引起一个空值(null值)被赋给一个脚本中使用的变量,从而使脚本产生非预期的效果. One way to prevent this is to append an extra character to both sides of the assignment statement using the expected positional parameter.

   1 variable1_=$1_  # Rather than variable1=$1
   2 # This will prevent an error, even if positional parameter is absent.
   3 
   4 critical_argument01=$variable1_
   5 
   6 # The extra character can be stripped off later, like so.
   7 variable1=${variable1_/_/}
   8 # Side effects only if $variable1_ begins with an underscore.
   9 # This uses one of the parameter substitution templates discussed later.
  10 # (Leaving out the replacement pattern results in a deletion.)
  11 
  12 #  A more straightforward way of dealing with this is
  13 #+ to simply test whether expected positional parameters have been passed.
  14 if [ -z $1 ]
  15 then
  16   exit $E_MISSING_POS_PARAM
  17 fi
  18 
  19 
  20 #  However, as Fabian Kreutz points out,
  21 #+ the above method may have unexpected side-effects.
  22 #  A better method is parameter substitution:
  23 #         ${1:-$DefaultVal}
  24 #  See the "Parameter Substition" section
  25 #+ in the "Variables Revisited" chapter.

---


例子 4-6. wh,whois 查询服务器信息

   1 #!/bin/bash
   2 # ex18.sh
   3 
   4 # Does a 'whois domain-name' lookup on any of 3 alternate servers:
   5 #                    ripe.net, cw.net, radb.net
   6 
   7 # Place this script -- renamed 'wh' -- in /usr/local/bin
   8 
   9 # Requires symbolic links:
  10 # ln -s /usr/local/bin/wh /usr/local/bin/wh-ripe
  11 # ln -s /usr/local/bin/wh /usr/local/bin/wh-cw
  12 # ln -s /usr/local/bin/wh /usr/local/bin/wh-radb
  13 
  14 E_NOARGS=65
  15 
  16 
  17 if [ -z "$1" ]
  18 then
  19   echo "Usage: `basename $0` [domain-name]"
  20   exit $E_NOARGS
  21 fi
  22 
  23 # Check script name and call proper server.
  24 case `basename $0` in    # Or:    case ${0##*/} in
  25     "wh"     ) whois $1@whois.ripe.net;;
  26     "wh-ripe") whois $1@whois.ripe.net;;
  27     "wh-radb") whois $1@whois.radb.net;;
  28     "wh-cw"  ) whois $1@whois.cw.net;;
  29     *        ) echo "Usage: `basename $0` [domain-name]";;
  30 esac 
  31 
  32 exit $?

---

shift命令使位置参数都左移一位。

$1 <--- $2, $2 <--- $3, $3 <--- $4, 以此类推.

原来旧的$1值会消失,但是$0 (脚本名称)不会改变. 如果你把大量的位置参数传给脚本,那么可以使用shift命令存取超过10的位置参数, 虽然这个功能也能由{bracket}花括号 做到.


例子 4-7. 使用shift

   1 #!/bin/bash
   2 # 用 'shift'命令逐步存取所有的位置参数
   3 
   4 #  给这个脚本一个命名,比如说shft,
   5 #+ 然后以一些参数来调用这个脚本,例如
   6 #          ./shft a b c def 23 skidoo
   7 
   8 until [ -z "$1" ]  # 直到所有的位置参数被存取完...
   9 do
  10   echo -n "$1 "
  11   shift
  12 done
  13 
  14 echo               # 换行.
  15 
  16 exit 0

Note

同样,shift命令也可以在需要传递一些参数的函数上以类似的方式工作.参考例子 33-15.

注:

[1]

$0参数由调用脚本的进程设置。依照习惯,这个参数是脚本的名字。详细的细节可以查看C函数execv的手册页。