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
	          

注意:从内核版本 2.6.23 开始,此 “错误” 已被修复。

(感谢 St�phane Chazelas 的澄清,并提供以上示例。)

如果脚本设置了环境变量,则需要“导出”它们,即报告给脚本本地的环境。 这是 export 命令的功能。

Note

脚本只能将变量export到子进程,即仅导出到该特定脚本启动的命令或进程。 从命令行调用的脚本不能将变量导出回命令行环境。 子进程 无法将变量导出回生成它们的父进程。

定义子进程是由另一个进程(其父进程)启动的子程序。

位置参数

从命令行传递给脚本的参数 [1]$0, $1, $2, $3 . . .

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

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

示例 4-5. 位置参数

#!/bin/bash

# Call this script with at least 10 parameters, for example
# ./scriptname 1 2 3 4 5 6 7 8 9 10
MINPARAMS=10

echo

echo "The name of this script is \"$0\"."
# Adds ./ for current directory
echo "The name of this script is \"`basename $0`\"."
# Strips out path name info (see 'basename')

echo

if [ -n "$1" ]              # Tested variable is quoted.
then
 echo "Parameter #1 is $1"  # Need quotes to escape #
fi 

if [ -n "$2" ]
then
 echo "Parameter #2 is $2"
fi 

if [ -n "$3" ]
then
 echo "Parameter #3 is $3"
fi 

# ...


if [ -n "${10}" ]  # Parameters > $9 must be enclosed in {brackets}.
then
 echo "Parameter #10 is ${10}"
fi 

echo "-----------------------------------"
echo "All the command-line parameters are: "$*""

if [ $# -lt "$MINPARAMS" ]
then
  echo
  echo "This script needs at least $MINPARAMS command-line arguments!"
fi  

echo

exit 0

位置参数的括号表示法提供了一种相当简单的方法来引用传递给脚本的命令行上的最后一个参数。 这也需要 间接引用

args=$#           # Number of args passed.
lastarg=${!args}
# Note: This is an *indirect reference* to $args ...


# Or:       lastarg=${!#}             (Thanks, Chris Monson.)
# This is an *indirect reference* to the $# variable.
# Note that lastarg=${!$#} doesn't work.

某些脚本可以执行不同的操作,具体取决于调用它们的名称。 为了使此功能正常工作,脚本需要检查$0,它被调用的名称。 [3] 还必须存在指向脚本所有备用名称的符号链接。 请参阅 示例 16-2

Tip

如果脚本期望命令行参数,但在没有参数的情况下调用,则可能会导致空变量赋值,这通常是不希望的结果。 防止这种情况的一种方法是在赋值语句的两边都附加一个额外的字符,使用预期的位置参数。

variable1_=$1_  # Rather than variable1=$1
# This will prevent an error, even if positional parameter is absent.

critical_argument01=$variable1_

# The extra character can be stripped off later, like so.
variable1=${variable1_/_/}
# Side effects only if $variable1_ begins with an underscore.
# This uses one of the parameter substitution templates discussed later.
# (Leaving out the replacement pattern results in a deletion.)

#  A more straightforward way of dealing with this is
#+ to simply test whether expected positional parameters have been passed.
if [ -z $1 ]
then
  exit $E_MISSING_POS_PARAM
fi


#  However, as Fabian Kreutz points out,
#+ the above method may have unexpected side-effects.
#  A better method is parameter substitution:
#         ${1:-$DefaultVal}
#  See the "Parameter Substition" section
#+ in the "Variables Revisited" chapter.

---

示例 4-6. wh whois 域名查找

#!/bin/bash
# ex18.sh

# Does a 'whois domain-name' lookup on any of 3 alternate servers:
#                    ripe.net, cw.net, radb.net

# Place this script -- renamed 'wh' -- in /usr/local/bin

# Requires symbolic links:
# ln -s /usr/local/bin/wh /usr/local/bin/wh-ripe
# ln -s /usr/local/bin/wh /usr/local/bin/wh-apnic
# ln -s /usr/local/bin/wh /usr/local/bin/wh-tucows

E_NOARGS=75


if [ -z "$1" ]
then
  echo "Usage: `basename $0` [domain-name]"
  exit $E_NOARGS
fi

# Check script name and call proper server.
case `basename $0` in    # Or:    case ${0##*/} in
    "wh"       ) whois $1@whois.tucows.com;;
    "wh-ripe"  ) whois $1@whois.ripe.net;;
    "wh-apnic" ) whois $1@whois.apnic.net;;
    "wh-cw"    ) whois $1@whois.cw.net;;
    *          ) echo "Usage: `basename $0` [domain-name]";;
esac 

exit $?

---

shift 命令重新分配位置参数,实际上将它们向左移动一个位置。

$1 <--- $2, $2 <--- $3, $3 <--- $4等等。

旧的$1消失了,但 $0(脚本名称)不会改变。 如果你对脚本使用大量位置参数,shift 允许你访问那些超过10的参数,尽管 {括号} 表示法 也允许这样做。

示例 4-7. 使用 shift

#!/bin/bash
# shft.sh: Using 'shift' to step through all the positional parameters.

#  Name this script something like shft.sh,
#+ and invoke it with some parameters.
#+ For example:
#             sh shft.sh a b c def 83 barndoor

until [ -z "$1" ]  # Until all parameters used up . . .
do
  echo -n "$1 "
  shift
done

echo               # Extra linefeed.

# But, what happens to the "used-up" parameters?
echo "$2"
#  Nothing echoes!
#  When $2 shifts into $1 (and there is no $3 to shift into $2)
#+ then $2 remains empty.
#  So, it is not a parameter *copy*, but a *move*.

exit

#  See also the echo-params.sh script for a "shiftless"
#+ alternative method of stepping through the positional params.

shift 命令可以接受一个数字参数,指示要移动多少个位置。

#!/bin/bash
# shift-past.sh

shift 3    # Shift 3 positions.
#  n=3; shift $n
#  Has the same effect.

echo "$1"

exit 0

# ======================== #


$ sh shift-past.sh 1 2 3 4 5
4

#  However, as Eleni Fragkiadaki, points out,
#+ attempting a 'shift' past the number of
#+ positional parameters ($#) returns an exit status of 1,
#+ and the positional parameters themselves do not change.
#  This means possibly getting stuck in an endless loop. . . .
#  For example:
#      until [ -z "$1" ]
#      do
#         echo -n "$1 "
#         shift 20    #  If less than 20 pos params,
#      done           #+ then loop never ends!
#
# When in doubt, add a sanity check. . . .
#           shift 20 || break
#                    ^^^^^^^^

Note

shift 命令以类似的方式作用于传递给函数的参数。 请参阅 示例 36-18

注释

[1]

请注意,函数也接受位置参数

[2]

调用脚本的进程设置了$0参数。 按照惯例,此参数是脚本的名称。 请参阅 execvmanpage(手册页)。

但是,从命令行$0是 shell 的名称。

bash$ echo $0
bash

tcsh% echo $0
tcsh

[3]

如果脚本是 sourcedsymlinked,那么这将不起作用。 检查 $BASH_Source 更安全。