shell教程
OutOfMemory.CN技术专栏-> shell-> shell教程-> 第九章:变量访问

第九章:变量访问

变量使用得当,可以使脚本变得更加强大和有弹性。但这要求我们学习变量的精妙之处及其细微的差别。内建变量影响Bash脚本行为的变量。$BASHBash二进制程序文件的路径bash$echo$BASH/bin/bash$BASH_ENV该环境变量保存一个Bash启动文件路径,当启动一个脚本程序时会去读该环境变量指定的文件。$BASH_SUBSHELL一个指示子shell(subshell)等级的变量。它


变量使用得当,可以使脚本变得更加强大和有弹性。但这要求我们学习变量的精妙之处及其细微的差别。

内建变量

影响Bash脚本行为的变量。

$BASH

Bash二进制程序文件的路径

 bash$ echo $BASH
 /bin/bash

$BASH_ENV

环境变量保存一个Bash启动文件路径,当启动一个脚本程序时会去读该环境变量指定的文件。

$BASH_SUBSHELL

一个指示子shell(subshell)等级的变量。它是Bash版本3新加入的。

参考例子 20-1的用法.

$BASH_VERSINFO[n]

这个数组含有6个元素,指示了安装的Bash版本的信息。它和$BASH_VERSION相似,但它们还是有一些小小的不同。

# Bash版本信息:

for n in 0 1 2 3 4 5
do
  echo "BASH_VERSINFO[$n] = ${BASH_VERSINFO[$n]}"
done

# BASH_VERSINFO[0] = 3                      # 主版本号.
# BASH_VERSINFO[1] = 00                     # 次版本号.
# BASH_VERSINFO[2] = 14                     # 补丁级.
# BASH_VERSINFO[3] = 1                      # 编译版本.
# BASH_VERSINFO[4] = release                # 发行状态.
# BASH_VERSINFO[5] = i386-redhat-linux-gnu  # 结构体系
                                            # (和变量$MACHTYPE相同).

$BASH_VERSION

安装在系统里的Bash版本。

 bash$ echo $BASH_VERSION
 3.00.14(1)-release
 	      

 tcsh% echo $BASH_VERSION
 BASH_VERSION: Undefined variable.
 	      

检查$BASH_VERSION是检测哪个shell在运行的好办法。$SHELL变量不一定能给出正确的答案。

$DIRSTACK

在目录堆栈里面最顶端的值(它受pushdpopd的控制)

这个内建的变量和dirs命令相符,但dirs是给出整个目录堆栈的内容。

$EDITOR

由脚本调用的默认的编辑器,一般是vi或是emacs.

$EUID

有效用户ID

当前用户无论是什么标识都会被认为是这个有效用户ID,这可能依赖于su.

变量$UID不一定和$EUID相同。

$FUNCNAME

当前函数的名字

xyz23 ()
{
  echo "$FUNCNAME now executing."  # 打印:xyz23 now executing.
}

xyz23

echo "FUNCNAME = $FUNCNAME"        # FUNCNAME =
                                   # 在一个函数体外则没有值输出.

$GLOBIGNORE

通配符(globbing)扩展的一列文件名模式。

$GROUPS

目前用户所属的组

它是当前用户在/etc/passwd文件中记录的所属的组列表(数组)。

 root# echo $GROUPS
 0
 
 
 root# echo ${GROUPS[1]}
 1
 
 
 root# echo ${GROUPS[5]}
 6
 	      

$HOME

用户的家目录,通常是/home/username (参考例子 9-14)

$HOSTNAME

在系统启动时由一个初始化脚本中用hostname命令给系统指派一个名字。然而,gethostname()函数能设置Bash内部变量E$HOSTNAME。参考例子 9-14.

$HOSTTYPE

机器类型

$MACHTYPE一样标识系统硬件。

 bash$ echo $HOSTTYPE
 i686
$IFS

内部字段分隔符

此变量决定Bash如何分割字段,或是解释字符串时的字标识分割。

$IFS默认是空白字符(空格,制表符和新行符),它可以被重新设置。例如,在解释一个以逗号分割的数据文件里可设置成逗号分割。注意$*使用了保存在$IFS中的第一个字符。 参考例子 5-1.

 bash$ echo $IFS | cat -vte
 $
 
 
 bash$ bash -c 'set w x y z; IFS=":-;"; echo "$*"'
 w:x:y:z
 	      

$IFS处理空白字符和其他的字符不相同。


例子 9-1. $IFS和空白符

#!/bin/bash
# $IFS处理空白字符和其他字符不相同。

output_args_one_per_line()
{
  for arg
  do echo "[$arg]"
  done
}

echo; echo "IFS=\" \""
echo "-------"

IFS=" "
var=" a  b c   "
output_args_one_per_line $var  # output_args_one_per_line函数相当于`echo " a  b c   "`
#
# [a]
# [b]
# [c]


echo; echo "IFS=:"
echo "-----"

IFS=:
var=":a::b:c:::"               # 像上面一样, 但用":"代替了" ".
output_args_one_per_line $var
#
# []
# [a]
# []
# [b]
# [c]
# []
# []
# []

# 在awk中字段分隔符"FS"也有相同的特性.

# 多谢Stephane Chazelas.

echo

exit 0

(多谢S.C.澄清了问题和举的例子)

参考例子 12-37来看一个关于理解$IFS的教学例子。

$IGNOREEOF

忽略EOF:在退出控制台前有多少文件结尾标识(end-of-files,control-D)会被shell忽略。

$LC_COLLATE

它常常在.bashrc或/etc/profile文件里被设置,它控制文件名扩展和模式匹配的展开顺序。如果设置不当,LC_COLLATE会在文件名通配符(filename globbing)里引起不可预料的结果。

到Bash2.05版本止,文件名通配符不再区分在方括号里的字符串范围中的大小写了。例如,ls [A-M]*会匹配File1.txt和file1.txt。为了保持方括号区分大小写的惯例,在/etc/profile文件和/或在~/.bashrc文件里由命令export LC_COLLATE=C把LC_COLLATE环境变量设置成C可以达到目的。

$LC_CTYPE

这个内部变量控制通配符(globbing)和模式匹配中的字符解释。

$LINENO

这个变量表示在本shell脚本中该变量出现时所在的行数。它只在脚本中它出现时有意义,它一般可用于调试。

# *** 开始调试代码块 ***
last_cmd_arg=$_  # 保存.

echo "At line number $LINENO, variable \"v1\" = $v1"
echo "Last command argument processed = $last_cmd_arg"
# *** 调试代码结束 ***

$MACHTYPE

机器类型

识别系统的硬件类型。

 bash$ echo $MACHTYPE
 i686
$OLDPWD

上一次工作的目录("OLD-print-working-directory",你上一次进入工作的目录)

$OSTYPE

操作系统类型

 bash$ echo $OSTYPE
 linux
$PATH

可执行程序文件的搜索路径。一般有/usr/bin/, /usr/X11R6/bin/, /usr/local/bin,等等。

当给出一个命令时,shell会自动在一个哈希表里搜索由PATH变量里所列的路径寻找该命令程序。$PATH变量被保存在环境变量里,是一串由冒号(:)分割的目录名的列表。通常,系统把此变量的值在/etc/profile文件和/或在~/.bashrc文件中被定义赋值。(参考附录 G).

 bash$ echo $PATH
 /bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin:/sbin:/usr/sbin

PATH=${PATH}:/opt/bin 能把/opt/bin目录加到当前现有的目录列表中去。在一个脚本中,它可以用这种方法临时地加一个目录到目录列表中去。当一个脚本退出时,此变量会恢复回原先的$PATH值(一个子进程[比如一个脚本],不能改变父进程的环境变量[比如启动脚本的shell])。

保存在$PATH目录列表中的当前"工作目录"(./)通常因为会引发安全漏洞而被忽略。

$PIPESTATUS

数组变量保存了最后执行的前台管道的退出状态。相当有趣的是,它不一定和最后执行的命令的退出状态一样。

 bash$ echo $PIPESTATUS
 0
 
 bash$ ls -al | bogus_command
 bash: bogus_command: command not found
 bash$ echo $PIPESTATUS
 141
 
 bash$ ls -al | bogus_command
 bash: bogus_command: command not found
 bash$ echo $?
 127
 	      

$PIPESTATUS数组的成员保存了每一个在管道里执行的命令各自的退出状态。$PIPESTATUS[0]保存了管道里第一个命令的退出状态,$PIPESTATUS[1]保存了管道里第二个命令的退出状态,以此类推。

在一个登录的shell里$PIPESTATUS变量可能包含了一个无用的0值。(在Bash 3.0以前)

 tcsh% bash
 
 bash$ who | grep nobody | sort
 bash$ echo ${PIPESTATUS[*]}
 0
 	      

如果在一个脚本包含上面的命令,就会产生0 1 0的输出。

多谢Wayne Pollock指出这一点并提供上面的例子。

在某些上下文中,$PIPESTATUS变量会给出一些不可预料的结果。

 bash$ echo $BASH_VERSION
 3.00.14(1)-release
 
 bash$ $ ls | bogus_command | wc
 bash: bogus_command: command not found
 0       0       0
 
 bash$ echo ${PIPESTATUS[@]}
 141 127 0
 	      

Chet Ramey贡献了上面描述ls输出的行为的例子。如果ls写到一个没有进程在读的管道,SIGPIPE信号会杀死它并使它的退出状态为141。否则ls的退出状态为预料之中的0。这个和tr的情况一样。

$PIPESTATUS是一个"挥发性"变量。它需要在管道结束之后并在任何命令干涉之前立即查询。

 bash$ $ ls | bogus_command | wc
 bash: bogus_command: command not found
 0       0       0
 
 bash$ echo ${PIPESTATUS[@]}
 0 127 0
 
 bash$ echo ${PIPESTATUS[@]}
 0
 	      

$PPID

一个进程的$PPID变量保存它的父进程的进程ID(pid)。[1]

用这个变量和pidof命令比较。

$PROMPT_COMMAND

这个变量在主提示符前($PS1显示之前)执行它的值里保存的命令。

$PS1

这是主提示符(第一提示符),它能在命令行上看见。

$PS2

副提示符(第二提示符),它在期望有附加的输入时能看见。它显示像">"的提示。

$PS3

第三提示符。它在一个select循环里显示 (参考例子 10-29).

$PS4

第四提示符,它在用-x选项调用一个脚本时的输出的每一行开头显示。它通常显示像"+"的提示。

$PWD

工作目录(即你现在所处的目录)

它类似于内建命令pwd

#!/bin/bash

E_WRONG_DIRECTORY=73

clear # 清屏.

TargetDirectory=/home/bozo/projects/GreatAmericanNovel

cd $TargetDirectory
echo "Deleting stale files in $TargetDirectory."

if [ "$PWD" != "$TargetDirectory" ]
then    # 防止意外工作在错误的目录中.
  echo "Wrong directory!"
  echo "In $PWD, rather than $TargetDirectory!"
  echo "Bailing out!"
  exit $E_WRONG_DIRECTORY
fi

rm -rf *
rm .[A-Za-z0-9]*    # 删除点文件.
# rm -f .[^.]* ..?*   删除以多个点开始为文件名的文件.
# (shopt -s dotglob; rm -f *)   也可以.
# 多谢S.C.指出来.

# 文件名除了"/"字符外可以包含ASCII值在0 - 255范围的所有字符
# 删除以奇怪的字符开头的文件作为练习由读者实现.

# 如果需要,这儿有多种其他的操作.

echo
echo "Done."
echo "Old files deleted in $TargetDirectory."
echo


exit 0

$REPLY

没有变量提供给read命令时的默认变量.这也适用于select命令的目录,但只是提供被选择的变量项目编号而不是变量本身的值.

#!/bin/bash
# reply.sh

# REPLY 是一个read命令的默认变量.

echo
echo -n "What is your favorite vegetable? "
read

echo "Your favorite vegetable is $REPLY."
#  如果没有变量提供且仅在这种情况,REPLY保存"read"命令上次读到的值
#

echo
echo -n "What is your favorite fruit? "
read fruit
echo "Your favorite fruit is $fruit."
echo "but..."
echo "Value of \$REPLY is still $REPLY."
#  $REPLY仍然被设置了它先前的值,
#+ 因为变量$fruit保存了新的"read"读到的值.

echo

exit 0

$SECONDS

脚本已运行的秒数.

#!/bin/bash

TIME_LIMIT=10
INTERVAL=1

echo
echo "Hit Control-C to exit before $TIME_LIMIT seconds."
echo

while [ "$SECONDS" -le "$TIME_LIMIT" ]
do
  if [ "$SECONDS" -eq 1 ]
  then
    units=second
  else
    units=seconds
  fi

  echo "This script has been running $SECONDS $units."
  #  在一个缓慢或负担过重的机器上,
  #+ 脚本可能偶尔会跳过一个计数.
  sleep $INTERVAL
done

echo -e "\a"  # Beep!(BB声)

exit 0

$SHELLOPTS

已经激活的shell选项列表,它是一个只读变量.

 bash$ echo $SHELLOPTS
 braceexpand:hashall:histexpand:monitor:history:interactive-comments:emacs
 	      

$SHLVL

SHELL的嵌套级别.指示了Bash被嵌套了多深.在命令行里,$SHLVL是1,因此在一个脚本里,它是2.

$TMOUT

如果$TMOUT环境变量被设为非零值时间值time,那么经过time这么长的时间后,shell提示符会超时.这将使此shell退出登录.

在Bash版本2.05b以上,可以在脚本中把$TMOUTread命令结合使用.

# 在Bash版本2.05b以上运行.

TMOUT=3    # 提示输入时间为三秒.

echo "What is your favorite song?"
echo "Quickly now, you only have $TMOUT seconds to answer!"
read song

if [ -z "$song" ]
then
  song="(no answer)"
  # 默认输出.
fi

echo "Your favorite song is $song."

有其他更复杂的在脚本中实现定时输入的方法.另一个方法是设置一个定时循环,超时时给脚本发送一个信号.这个办法要求有一个处理例程来捕捉(trap)(参考例子 29-5)由定时循环产生的信号.(哇哦!)


例子 9-2. 定时输入

#!/bin/bash
# timed-input.sh

# TMOUT=3    在新一点的版本中,这个也可以.


TIMELIMIT=3  # 在这个实例中设置成三秒,但可以设置成其它的值。

PrintAnswer()
{
  if [ "$answer" = TIMEOUT ]
  then
    echo $answer
  else       # 不要和上面那个例子弄混了.
    echo "Your favorite veggie is $answer"
    kill $!  # 不再需要后台运行的TimerOn函数了,杀掉它
             # $!变量是上一个在后台运行的作业进程的PID
  fi

}



TimerOn()
{
  sleep $TIMELIMIT && kill -s 14 $$ &
  # 等3秒,然后给脚本发送sigalarm信号.
}

Int14Vector()
{
  answer="TIMEOUT"
  PrintAnswer
  exit 14
}

trap Int14Vector 14   # 设置定时中断(14)能暗中给定时间限制

echo "What is your favorite vegetable "
TimerOn
read answer
PrintAnswer


#  无可否认,这是一个定时输入的复杂的实现,
#+ 然而"read"命令的"-t"选项可以简化这个任务。
#  参考后面的"t-out.sh"脚本

#  如果你想要真正优雅的东西...
#+ 可以考虑用C或C++写你的应用程序,
#+ 使用合适的函数库,例如'alarm'或是'setitimer'.

exit 0

另外一种选择是使用stty.


例子 9-3. 再来一个定时输入

#!/bin/bash
# timeout.sh

#  由Stephane Chazelas所写,
#+ 由本书作者作了些修改.

INTERVAL=5                # 超时间隔

timedout_read() {
  timeout=$1
  varname=$2
  old_tty_settings=`stty -g`
  stty -icanon min 0 time ${timeout}0
  eval read $varname      # 或只是读$varname变量
  stty "$old_tty_settings"
  # 请参考"stty"的man手册.
}

echo; echo -n "What's your name? Quick! "
timedout_read $INTERVAL your_name

#  这个可能不一定在每种终端都能运行.
#  最大的超时值依赖于终端.
#+ (通常是25.5秒).

echo

if [ ! -z "$your_name" ]  # 如果在超时之前名字被键入...
then
  echo "Your name is $your_name."
else
  echo "Timed out."
fi

echo

# 这个脚本和"timed-input.sh"脚本的行为稍微有点不同.
# 每一次击键,计时器都会重新设置(即重新开始).

exit 0

可能最容易的方法就是使用read命令的-t选项了。


例子 9-4. 定时read

#!/bin/bash
# t-out.sh
# 从"syngin seven"的建议中得到灵感(多谢).


TIMELIMIT=4         # 4秒

read -t $TIMELIMIT variable <&1
#                           ^^^
#  在这儿, Bash 1.x and 2.x需要"<&1",
#  但Bash 3.x则不需要.

echo

if [ -z "$variable" ]  # 值为null?
then
  echo "Timed out, variable still unset."
else
  echo "variable = $variable"
fi

exit 0

$UID

用户ID号

这是当前用户的用户标识号,它在/etc/passwd文件中记录。

这是当前用户的真实ID,即使只是临时通过su命令转换成另外一个用户也会显示成转换成的ID号。$UID是个只读变量,不能在命令行或是脚本中更改它,并且它和内建命令id是有些相似的。


例子 9-5. 我是root吗?

#!/bin/bash
# am-i-root.sh:   我是root吗?

ROOT_UID=0   # Root的$UID为0.

if [ "$UID" -eq "$ROOT_UID" ]  # 真正的"root"才能经得住考验
then
  echo "You are root."
else
  echo "You are just an ordinary user (but mom loves you just the same)."
fi

exit 0


# ============================================================= #
# 下面的代码不会执行,因为脚本在上面已经退出了.

# 另外一种判断是否是root用户的方法:

ROOTUSER_NAME=root

username=`id -nu`              # 或者...   username=`whoami`
if [ "$username" = "$ROOTUSER_NAME" ]
then
  echo "Rooty, toot, toot. You are root."
else
  echo "You are just a regular fella."
fi

也参考一下例子 2-3.

变量$ENV, $LOGNAME, $MAIL, $TERM, $USER, 和$USERNAME$ENV,都不是Bash内建的。然而常常在Bash的启动文件之一里作为环境变量设置。$SHELL变量是用户的登录shell的名字,它可以在/etc/passwd文件里设置或是在一个“初始化”的脚本里设置,并且它同样不是Bash内建的。

 tcsh% echo $LOGNAME
 bozo
 tcsh% echo $SHELL
 /bin/tcsh
 tcsh% echo $TERM
 rxvt
 
 bash$ echo $LOGNAME
 bozo
 bash$ echo $SHELL
 /bin/tcsh
 bash$ echo $TERM
 rxvt
 	      

位置参数

$0, $1, $2,等等

位置参数由命令行传给脚本或传给一个函数,或设置(set)给一个变量(参考例子 4-5例子 11-15)

$#

命令行参数[2]或者是位置参数的数量(参考例子 33-2)

$*

所有的位置参数都被当成单个单元。

"$*"必须被引号引起来。

$@

和$*相同,但每个参数都是一个引起的字符串。那是说,参数都是没有被解析或扩展,是完整无缺地被传递的。这是说在参数列表中的每一个参数都被看作是一个单独的单元。

当然,"$@"应该被引号引起来。


例子 9-6. arglist: 用$*和$@列出参数来

#!/bin/bash
# arglist.sh
# 用几个参数来运行这个脚本,比如说"one two three".

E_BADARGS=65

if [ ! -n "$1" ]
then
  echo "Usage: `basename $0` argument1 argument2 etc."
  exit $E_BADARGS
fi

echo

index=1          # 初始计数.

echo "Listing args with \"\$*\":"
for arg in "$*"  # 如果"$*"没有被引号引起来,会完全不能工作.
do
  echo "Arg #$index = $arg"
  let "index+=1"
done             # $* sees all arguments as single word.
echo "Entire arg list seen as single word."

echo

index=1          # Reset count.
                 # What happens if you forget to do this?

echo "Listing args with \"\$@\":"
for arg in "$@"
do
  echo "Arg #$index = $arg"
  let "index+=1"
done             # $@ sees arguments as separate words.
echo "Arg list seen as separate words."

echo

index=1          # Reset count.

echo "Listing args with \$* (unquoted):"
for arg in $*
do
  echo "Arg #$index = $arg"
  let "index+=1"
done             # Unquoted $* sees arguments as separate words.
echo "Arg list seen as separate words."

exit 0

在一个shift命令的后面,变量$@会保存除掉先前参数列表的位置参数$1后剩下的命令行参数。

#!/bin/bash
# 以./scriptname 1 2 3 4 5执行

echo "$@"    # 1 2 3 4 5
shift
echo "$@"    # 2 3 4 5
shift
echo "$@"    # 3 4 5

# 每次"shift"后会丢掉$1.
# 然后"$@"保存剩下的参数。

The $@ special parameter finds use as a tool for filtering input into shell scripts. The cat "$@" construction accepts input to a script either from stdin or from files given as parameters to the script. See Example 12-21 and Example 12-22.

依赖于$IFS变量的设置,$*和$@变量有时会表现不一致的令人迷惑的行为。


例子 9-7. $*和$@的不一致

#!/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
echo ---

echo 'IFS unchanged, using $*'
c=0
for i in $*                 # unquoted
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS unchanged, using "$@"'
c=0
for i in "$@"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS unchanged, using $@'
c=0
for i in $@
do echo "$((c+=1)): [$i]"
done
echo ---

IFS=:
echo 'IFS=":", using "$*"'
c=0
for i in "$*"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", using $*'
c=0
for i in $*
do echo "$((c+=1)): [$i]"
done
echo ---

var=$*
echo 'IFS=":", using "$var" (var=$*)'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", using $var (var=$*)'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done
echo ---

var="$*"
echo 'IFS=":", using $var (var="$*")'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", using "$var" (var="$*")'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", using "$@"'
c=0
for i in "$@"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", using $@'
c=0
for i in $@
do echo "$((c+=1)): [$i]"
done
echo ---

var=$@
echo 'IFS=":", using $var (var=$@)'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", using "$var" (var=$@)'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo ---

var="$@"
echo 'IFS=":", using "$var" (var="$@")'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", using $var (var="$@")'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done

echo

# Try this script with ksh or zsh -y.

exit 0

# 这个脚本由Stephane Chazelas所写,
# 并由本书作者做了少些修改.

$@$*仅仅在被双引号引住时不同。


例子 9-8. 当$IFS为空时的$*和$@

#!/bin/bash

#  如果$IFS被设置,但值是空的,
#+ 则"$*"和"$@"不会像希望的那样显示位置参数。

mecho ()       # 显示位置参数.
{
echo "$1,$2,$3";
}


IFS=""         # 设置了,但值是空的.
set a b c      # 位置参数.

mecho "$*"     # abc,,
mecho $*       # a,b,c

mecho $@       # a,b,c
mecho "$@"     # a,b,c

#  当$IFS为空时,
#+ $*和$@的行为依赖于Bash或是sh正在运行.
#  因此在一个脚本里使用这种“特性”是失策的。


# 多谢Stephane Chazelas.

exit 0

其它的特殊参数

$-

(使用set命令)传给脚本的标记. 参考例子 11-15.

这原本是一个ksh的结构,但被Bash采纳了,并且不幸的是,它看上去好像不能真正的在Bash脚本中工作。一个可能有用的地方是脚本用来测试自己本身是否是一个交互式的。

$!

在后台运行的最后一个作业的PID(进程ID)。

LOG=$0.log

COMMAND1="sleep 100"

echo "Logging PIDs background commands for script: $0" >> "$LOG"
# So they can be monitored, and killed as necessary.
echo >> "$LOG"

# Logging commands.

echo -n "PID of \"$COMMAND1\":  " >> "$LOG"
${COMMAND1} &
echo $! >> "$LOG"
# PID of "sleep 100":  1506

# 多谢Jacques Lederer的建议.

possibly_hanging_job & { sleep ${TIMEOUT}; eval 'kill -9 $!' &> /dev/null; }
# 强迫一个出错的程序结束退出.
# 这很有用,尤其在初始化脚本中.

# 多谢Sylvain Fourmanoit发现"!"变量创造性的用处.

$_

保存前一个命令最后一个参数的变量值。


例子 9-9. 下划线变量

#!/bin/bash

echo $_              # /bin/bash
                     # 只需调用/bin/bash来运行这个脚本.

du >/dev/null        # 从命令行里没有输出.
echo $_              # du

ls -al >/dev/null    # 从命令行里没有输出.
echo $_              # -al  (它是最后的参数)

:
echo $_              # :

$?

一个命令,函数或脚本自身的退出状态码(参考例子 23-7)

$$

脚本本身的进程PID。$$变量常被用于脚本中生成一个"唯一的"临时文件名(参考例子 A-13, 例子 29-6, 例子 12-28, 和例子 11-25). 这通常比调用mktemp还要简单。

[1]

当然,当前运行的脚本的PID就是$$。

[2]

术语"argument"和"parameter"常常可互相替换。在这个文档的上下文中,它们有相同的意思,即它们是传递给脚本或函数的变量。[译者注:翻译时,译者已经把这两个术语依据上下文意思都做了适当的翻译].

© 内存溢出 OutOfMemory.CN