3.2. 变量

3.2.1. 变量的类型

正如上面的例子所示,按照惯例,Shell 变量都使用大写字符。Bash 维护着两种类型的变量列表

3.2.1.1. 全局变量

全局变量或环境变量在所有 Shell 中都可用。 可以使用 envprintenv 命令来显示环境变量。这些程序包含在 sh-utils 包中。

以下是一个典型的输出

franky ~> printenv
CC=gcc
CDPATH=.:~:/usr/local:/usr:/
CFLAGS=-O2 -fomit-frame-pointer
COLORTERM=gnome-terminal
CXXFLAGS=-O2 -fomit-frame-pointer
DISPLAY=:0
DOMAIN=hq.garrels.be
e=
TOR=vi
FCEDIT=vi
FIGNORE=.o:~
G_BROKEN_FILENAMES=1
GDK_USE_XFT=1
GDMSESSION=Default
GNOME_DESKTOP_SESSION_ID=Default
GTK_RC_FILES=/etc/gtk/gtkrc:/nethome/franky/.gtkrc-1.2-gnome2
GWMCOLOR=darkgreen
GWMTERM=xterm
HISTFILESIZE=5000
history_control=ignoredups
HISTSIZE=2000
HOME=/nethome/franky
HOSTNAME=octarine.hq.garrels.be
INPUTRC=/etc/inputrc
IRCNAME=franky
JAVA_HOME=/usr/java/j2sdk1.4.0
LANG=en_US
LDFLAGS=-s
LD_LIBRARY_PATH=/usr/lib/mozilla:/usr/lib/mozilla/plugins
LESSCHARSET=latin1
LESS=-edfMQ
LESSOPEN=|/usr/bin/lesspipe.sh %s
LEX=flex
LOCAL_MACHINE=octarine
LOGNAME=franky
LS_COLORS=no=00:fi=00:di=01;34:ln=01;36:pi=40;33:so=01;35:bd=40;33;01:cd=40;33;01:or=01;05;37;41:mi=01;05;37;41:ex=01;32:*.cmd=01;32:*.exe=01;32:*.com=01;32:*.btm=01;32:*.bat=01;32:*.sh=01;32:*.csh=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.gz=01;31:*.bz2=01;31:*.bz=01;31:*.tz=01;31:*.rpm=01;31:*.cpio=01;31:*.jpg=01;35:*.gif=01;35:*.bmp=01;35:*.xbm=01;35:*.xpm=01;35:*.png=01;35:*.tif=01;35:
MACHINES=octarine
MAILCHECK=60
MAIL=/var/mail/franky
MANPATH=/usr/man:/usr/share/man/:/usr/local/man:/usr/X11R6/man
MEAN_MACHINES=octarine
MOZ_DIST_BIN=/usr/lib/mozilla
MOZILLA_FIVE_HOME=/usr/lib/mozilla
MOZ_PROGRAM=/usr/lib/mozilla/mozilla-bin
MTOOLS_FAT_COMPATIBILITY=1
MYMALLOC=0
NNTPPORT=119
NNTPSERVER=news
NPX_PLUGIN_PATH=/plugin/ns4plugin/:/usr/lib/netscape/plugins
OLDPWD=/nethome/franky
OS=Linux
PAGER=less
PATH=/nethome/franky/bin.Linux:/nethome/franky/bin:/usr/local/bin:/usr/local/sbin:/usr/X11R6/bin:/usr/bin:/usr/sbin:/bin:/sbin:.
PS1=\[\033[1;44m\]franky is in \w\[\033[0m\]
PS2=More input>
PWD=/nethome/franky
SESSION_MANAGER=local/octarine.hq.garrels.be:/tmp/.ICE-unix/22106
SHELL=/bin/bash
SHELL_LOGIN=--login
SHLVL=2
SSH_AGENT_PID=22161
SSH_ASKPASS=/usr/libexec/openssh/gnome-ssh-askpass
SSH_AUTH_SOCK=/tmp/ssh-XXmhQ4fC/agent.22106
START_WM=twm
TERM=xterm
TYPE=type
USERNAME=franky
USER=franky
_=/usr/bin/printenv
VISUAL=vi
WINDOWID=20971661
XAPPLRESDIR=/nethome/franky/app-defaults
XAUTHORITY=/nethome/franky/.Xauthority
XENVIRONMENT=/nethome/franky/.Xdefaults
XFILESEARCHPATH=/usr/X11R6/lib/X11/%L/%T/%N%C%S:/usr/X11R6/lib/X11/%l/%T/%N%C%S:/usr/X11R6/lib/X11/%T/%N%C%S:/usr/X11R6/lib/X11/%L/%T/%N%S:/usr/X11R6/lib/X11/%l/%T/%N%S:/usr/X11R6/lib/X11/%T/%N%S
XKEYSYMDB=/usr/X11R6/lib/X11/XKeysymDB
XMODIFIERS=@im=none
XTERMID=
XWINHOME=/usr/X11R6
X=X11R6
YACC=bison -y

3.2.1.2. 局部变量

局部变量仅在当前 Shell 中可用。 使用不带任何选项的 set 内置命令将显示所有变量(包括环境变量)和函数的列表。 输出将根据当前的语言环境进行排序,并以可重用的格式显示。

以下是一个 diff 文件,通过比较 printenvset 的输出而生成,去掉了 set 命令同时显示的函数

franky ~> diff set.sorted printenv.sorted | grep "<" | awk '{ print $2 }'
BASE=/nethome/franky/.Shell/hq.garrels.be/octarine.aliases
BASH=/bin/bash
BASH_VERSINFO=([0]="2"
BASH_VERSION='2.05b.0(1)-release'
COLUMNS=80
DIRSTACK=()
DO_FORTUNE=
EUID=504
GROUPS=()
HERE=/home/franky
HISTFILE=/nethome/franky/.bash_history
HOSTTYPE=i686
IFS=$'
LINES=24
MACHTYPE=i686-pc-linux-gnu
OPTERR=1
OPTIND=1
OSTYPE=linux-gnu
PIPESTATUS=([0]="0")
PPID=10099
PS4='+
PWD_REAL='pwd
SHELLOPTS=braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitor
THERE=/home/franky
UID=504

NoteAwk
 

GNU Awk 编程语言在第 6 章中进行了解释。

3.2.1.3. 按内容划分的变量

除了将变量划分为局部变量和全局变量之外,我们还可以根据变量包含的内容类型将它们分为几类。在这方面,变量有 4 种类型

  • 字符串变量

  • 整数变量

  • 常量变量

  • 数组变量

我们将在第 10 章中讨论这些类型。 目前,我们将使用整数和字符串值作为我们的变量。

3.2.2. 创建变量

变量区分大小写,默认情况下大写。 为局部变量赋予小写名称是一种有时会应用的约定。 但是,您可以随意使用您想要的名称或混合使用大小写。 变量也可以包含数字,但不允许以数字开头的名称

prompt> export 1number=1
bash: export: `1number=1': not a valid identifier

要在 Shell 中设置变量,请使用

VARNAME="value"

在等号周围放置空格会导致错误。 在将值分配给变量时,养成引用内容字符串的良好习惯:这将减少您出错的机会。

一些使用大小写、数字和空格的示例

franky ~> MYVAR1="2"

franky ~> echo $MYVAR1
2

franky ~> first_name="Franky"

franky ~> echo $first_name
Franky

franky ~> full_name="Franky M. Singh"

franky ~> echo $full_name
Franky M. Singh

franky ~> MYVAR-2="2"
bash: MYVAR-2=2: command not found

franky ~> MYVAR1 ="2"
bash: MYVAR1: command not found

franky ~> MYVAR1= "2"
bash: 2: command not found

franky ~> unset MYVAR1 first_name full_name

franky ~> echo $MYVAR1 $first_name $full_name
<--no output-->

franky ~>

3.2.3. 导出变量

像上面示例中创建的变量仅对当前 Shell 可用。 这是一个局部变量:当前 Shell 的子进程将不知道此变量。 为了将变量传递给子 Shell,我们需要使用 export 内置命令导出它们。 导出的变量被称为环境变量。 设置和导出通常在一个步骤中完成

exportVARNAME="value"

子 Shell 可以更改它从父 Shell 继承的变量,但子 Shell 所做的更改不会影响父 Shell。 示例中演示了这一点

franky ~> full_name="Franky M. Singh"

franky ~> bash

franky ~> echo $full_name


franky ~> exit

franky ~> export full_name

franky ~> bash

franky ~> echo $full_name
Franky M. Singh

franky ~> export full_name="Charles the Great"

franky ~> echo $full_name
Charles the Great

franky ~> exit

franky ~> echo $full_name
Franky M. Singh

franky ~>

当第一次尝试读取full_name在子 Shell 中,它不存在(echo 显示一个空字符串)。 子 Shell 退出,并且full_name在父 Shell 中导出 - 变量可以在分配值后导出。 然后启动一个新的子 Shell,其中从父 Shell 导出的变量可见。 该变量被更改为保存另一个名称,但父 Shell 中此变量的值保持不变。

3.2.4. 保留变量

3.2.4.1. Bourne Shell 保留变量

Bash 使用某些 Shell 变量的方式与 Bourne Shell 相同。 在某些情况下,Bash 会为变量分配一个默认值。 下表概述了这些普通 Shell 变量

表 3-1. 保留的 Bourne Shell 变量

变量名定义
CDPATH一个以冒号分隔的目录列表,用作 cd 内置命令的搜索路径。
HOME当前用户的主目录;cd 内置命令的默认值。此变量的值也用于波浪号扩展。
IFS分隔字段的字符列表;在 Shell 将单词拆分为扩展的一部分时使用。
MAIL如果此参数设置为文件名并且MAILPATH变量未设置,Bash 会通知用户指定文件中收到的邮件。
MAILPATH一个以冒号分隔的文件名列表,Shell 会定期检查新邮件。
OPTARGgetopts 内置命令处理的最后一个选项参数的值。
OPTINDgetopts 内置命令处理的最后一个选项参数的索引。
PATH一个以冒号分隔的目录列表,Shell 在其中查找命令。
PS1主提示符字符串。 默认值为 "'\s-\v\$ '"
PS2辅助提示符字符串。 默认值为 "'> '"

3.2.4.2. Bash 保留变量

这些变量由 Bash 设置或使用,但其他 Shell 通常不会对它们进行特殊处理。

表 3-2. 保留的 Bash 变量

变量名定义
auto_resume此变量控制 Shell 与用户和作业控制的交互方式。
BASH用于执行当前 Bash 实例的完整路径名。
BASH_ENV如果在调用 Bash 来执行 Shell 脚本时设置了此变量,则会扩展其值,并将其用作启动文件的名称,以便在执行脚本之前读取。
BASH_VERSION当前 Bash 实例的版本号。
BASH_VERSINFO一个只读数组变量,其成员保存此 Bash 实例的版本信息。
COLUMNSselect 内置命令用于在打印选择列表时确定终端宽度。 在收到 SIGWINCH 信号时自动设置。
COMP_CWORD的索引${COMP_WORDS}包含当前光标位置的单词。
COMP_LINE当前命令行。
COMP_POINT相对于当前命令开头的当前光标位置的索引。
COMP_WORDS一个数组变量,由当前命令行中的各个单词组成。
COMPREPLY一个数组变量,Bash 从中读取由可编程补全功能调用的 Shell 函数生成的可能补全。
DIRSTACK一个数组变量,包含目录堆栈的当前内容。
EUID当前用户的数字有效用户 ID。
FCEDIT-e选项作为 fc 内置命令的默认值使用的编辑器。
FIGNORE在执行文件名补全时要忽略的以冒号分隔的后缀列表。
FUNCNAME当前正在执行的任何 Shell 函数的名称。
GLOBIGNORE一个以冒号分隔的模式列表,用于定义文件名扩展要忽略的文件名集。
GROUPS一个数组变量,包含当前用户所属的组的列表。
histchars最多三个字符,用于控制历史扩展、快速替换和令牌化
HISTCMD历史记录编号,或当前命令在历史记录列表中的索引。
HISTCONTROL定义是否将命令添加到历史记录文件。
HISTFILE用于保存命令历史记录的文件名。 默认值为~/.bash_history.
HISTFILESIZE历史记录文件中包含的最大行数,默认为 500。
HISTIGNORE一个以冒号分隔的模式列表,用于确定应将哪些命令行保存在历史记录列表中。
HISTSIZE历史记录列表中要记住的最大命令数,默认为 500。
HOSTFILE包含与/etc/hosts格式相同的文件名,在 Shell 需要完成主机名时应读取该文件。
HOSTNAME当前主机的名称。
HOSTTYPE一个字符串,描述 Bash 运行的机器。
IGNOREEOF控制 Shell 在收到 EOF 字符作为唯一输入时的操作。
INPUTRCReadline 初始化文件的名称,覆盖默认值/etc/inputrc.
LANG用于确定任何未通过以LC_.
开头的变量专门选择的类别的语言环境类别。LC_ALLLANG此变量覆盖了LC_的值以及任何其他指定语言环境类别的变量。
LC_COLLATE此变量确定在对文件名扩展的结果进行排序时使用的排序规则,并确定文件名扩展和模式匹配中的范围表达式、等效类和排序序列的行为。
LC_CTYPE此变量确定字符的解释和文件名扩展和模式匹配中字符类的行为。
LC_MESSAGES此变量确定用于翻译以 "$" 符号开头的双引号字符串的语言环境。
LC_NUMERIC此变量确定用于数字格式的语言环境类别。
LINENO当前正在执行的脚本或 Shell 函数中的行号。
LINESselect 内置命令用于确定打印选择列表的列长度。
MACHTYPE一个字符串,以标准的 GNU CPU-COMPANY-SYSTEM 格式完全描述 Bash 在其上运行的系统类型。
MAILCHECKShell 应多久(以秒为单位)检查MAILPATHMAIL变量中指定的文件中的邮件。
OLDPWDcd 内置命令设置的先前工作目录。
OPTERR如果设置为值 1,Bash 会显示由 getopts 内置命令生成的错误消息。
OSTYPE一个字符串,描述 Bash 运行的操作系统。
PIPESTATUS一个数组变量,包含最近执行的前台管道(可能只包含单个命令)中进程的退出状态值列表。
POSIXLY_CORRECT如果此变量在 bash 启动时存在于环境中,则 Shell 进入 POSIX 模式。
PPIDShell 的父进程的进程 ID。
PROMPT_COMMAND如果已设置,则该值将被解释为在打印每个主提示符(PS1).
PS3此变量的值用作 select 命令的提示符。 默认为 "'#? '"
PS4当设置-x选项时,该值是在命令行回显之前打印的提示符; 默认为 "'+ '"
PWDcd 内置命令设置的当前工作目录。
RANDOM每次引用此参数时,都会生成一个介于 0 和 32767 之间的随机整数。 将一个值分配给此变量会初始化随机数生成器。
REPLYread 内置命令的默认变量。
SECONDS此变量扩展为自 Shell 启动以来的秒数。
SHELLOPTS一个以冒号分隔的启用 Shell 选项列表。
SHLVL每次启动新的 Bash 实例时增加 1。
TIMEFORMAT此参数的值用作格式化字符串,用于指定如何显示以 time 保留字开头的管道的时间信息。
TMOUT如果设置为大于零的值,TMOUT则被视为 read 内置命令的默认超时时间。 在交互式 shell 中,该值被解释为在 shell 为交互式时发出主提示符后等待输入的秒数。 如果在该秒数后没有收到输入,Bash 将终止。
UID当前用户的数字实际用户 ID。

有关更多信息,请查阅 Bash 手册页、info 页或 doc 页。 一些变量是只读的,一些是自动设置的,而另一些变量在设置为非默认值时会失去其含义。

3.2.5. 特殊参数

shell 特殊处理几个参数。 这些参数只能被引用;不允许对其进行赋值。

表 3-3. 特殊 bash 变量

字符定义
$*扩展为位置参数,从 1 开始。 当扩展发生在双引号内时,它将扩展为单个单词,每个参数的值之间用IFS特殊变量的第一个字符分隔。
$@扩展为位置参数,从 1 开始。 当扩展发生在双引号内时,每个参数扩展为单独的单词。
$#扩展为位置参数的数量(十进制)。
$?扩展为最近执行的前台管道的退出状态。
$-连字符扩展为调用时指定的当前选项标志,通过 set 内置命令指定,或由 shell 本身设置的标志(例如-i).
$$扩展为 shell 的进程 ID。
$!扩展为最近执行的后台(异步)命令的进程 ID。
$0扩展为 shell 或 shell 脚本的名称。
$_下划线变量在 shell 启动时设置,包含作为参数列表传入的正在执行的 shell 或脚本的绝对文件名。 随后,它扩展为上一个命令的最后一个参数(经过扩展)。 它也被设置为每个执行的命令的完整路径名,并放置在导出到该命令的环境中。 检查邮件时,此参数保存邮件文件的名称。

Note$* vs. $@
 

"$*" 的实现一直存在问题,实际上应该替换为 "$@" 的行为。 在几乎所有程序员使用 "$*" 的情况下,他们都指的是 "$@""$*" 可能会导致错误,甚至在您的软件中造成安全漏洞。

位置参数是 shell 脚本名称后面的单词。 它们被放入变量$1, $2, $3等等。 根据需要,变量被添加到内部数组中。$#包含参数的总数,如下面的简单脚本所示

#!/bin/bash

# positional.sh
# This script reads 3 positional parameters and prints them out.

POSPAR1="$1"
POSPAR2="$2"
POSPAR3="$3"

echo "$1 is the first positional parameter, \$1."
echo "$2 is the second positional parameter, \$2."
echo "$3 is the third positional parameter, \$3."
echo
echo "The total number of positional parameters is $#."

执行时可以给出任意数量的参数

franky ~> positional.sh one two three four five
one is the first positional parameter, $1.
two is the second positional parameter, $2.
three is the third positional parameter, $3.

The total number of positional parameters is 5.

franky ~> positional.sh one two
one is the first positional parameter, $1.
two is the second positional parameter, $2.
 is the third positional parameter, $3.

The total number of positional parameters is 2.

有关评估这些参数的更多信息,请参见第 7 章第 9.7 节

其他特殊参数的一些示例

franky ~> grep dictionary /usr/share/dict/words
dictionary

franky ~> echo $_
/usr/share/dict/words

franky ~> echo $$
10662

franky ~> mozilla &
[1] 11064

franky ~> echo $!
11064

franky ~> echo $0
bash

franky ~> echo $?
0

franky ~> ls doesnotexist
ls: doesnotexist: No such file or directory

franky ~> echo $?
1

franky ~>

用户 franky 开始输入 grep 命令,这导致了_变量的赋值。 他的 shell 的进程 ID 是 10662。 将作业置于后台后,!包含后台作业的进程 ID。 运行的 shell 是 bash。 当出现错误时,?包含与 0(零)不同的退出代码。

3.2.6. 使用变量进行脚本重用

除了使脚本更具可读性之外,变量还可以使您更快地在另一个环境或用于另一个目的的应用脚本。 考虑以下示例,一个非常简单的脚本,用于将 franky 的主目录备份到远程服务器

#!/bin/bash

# This script makes a backup of my home directory.

cd /home

# This creates the archive
tar cf /var/tmp/home_franky.tar franky > /dev/null 2>&1

# First remove the old bzip2 file.  Redirect errors because this generates some if the archive
# does not exist.  Then create a new compressed file.
rm /var/tmp/home_franky.tar.bz2 2> /dev/null
bzip2 /var/tmp/home_franky.tar

# Copy the file to another host - we have ssh keys for making this work without intervention.
scp /var/tmp/home_franky.tar.bz2 bordeaux:/opt/backup/franky > /dev/null 2>&1

# Create a timestamp in a logfile.
date >> /home/franky/log/home_backup.log
echo backup succeeded >> /home/franky/log/home_backup.log

首先,如果您每次需要时手动命名文件和目录,则更可能出错。 其次,假设 franky 想将此脚本提供给 carol,那么 carol 需要进行相当多的编辑才能使用该脚本来备份她的主目录。 如果 franky 想使用此脚本备份其他目录,情况也是如此。 为了便于重用,请使所有文件、目录、用户名、服务器名称等成为变量。 因此,您只需要编辑一次值,而不必遍历整个脚本来检查参数出现的位置。 这是一个例子

#!/bin/bash
                                                                                                 
# This script makes a backup of my home directory.

# Change the values of the variables to make the script work for you:
BACKUPDIR=/home
BACKUPFILES=franky
TARFILE=/var/tmp/home_franky.tar
BZIPFILE=/var/tmp/home_franky.tar.bz2
SERVER=bordeaux
REMOTEDIR=/opt/backup/franky
LOGFILE=/home/franky/log/home_backup.log

cd $BACKUPDIR

# This creates the archive
tar cf $TARFILE $BACKUPFILES > /dev/null 2>&1
                                                                                                 
# First remove the old bzip2 file.  Redirect errors because this generates some if the archive 
# does not exist.  Then create a new compressed file.
rm $BZIPFILE 2> /dev/null
bzip2 $TARFILE

# Copy the file to another host - we have ssh keys for making this work without intervention.
scp $BZIPFILE $SERVER:$REMOTEDIR > /dev/null 2>&1

# Create a timestamp in a logfile.
date >> $LOGFILE
echo backup succeeded >> $LOGFILE

Note大型目录和低带宽
 

以上纯粹是一个每个人都能理解的示例,使用小型目录和同一子网上的主机。 根据您的带宽、目录的大小和远程服务器的位置,使用此机制进行备份可能需要很长时间。 对于较大的目录和较低的带宽,请使用 rsync 来保持两端目录的同步。