正如上面的例子所示,按照惯例,Shell 变量都使用大写字符。Bash 维护着两种类型的变量列表
全局变量或环境变量在所有 Shell 中都可用。 可以使用 env 或 printenv 命令来显示环境变量。这些程序包含在 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 |
局部变量仅在当前 Shell 中可用。 使用不带任何选项的 set 内置命令将显示所有变量(包括环境变量)和函数的列表。 输出将根据当前的语言环境进行排序,并以可重用的格式显示。
以下是一个 diff 文件,通过比较 printenv 和 set 的输出而生成,去掉了 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 |
![]() | Awk |
---|---|
GNU Awk 编程语言在第 6 章中进行了解释。 |
除了将变量划分为局部变量和全局变量之外,我们还可以根据变量包含的内容类型将它们分为几类。在这方面,变量有 4 种类型
字符串变量
整数变量
常量变量
数组变量
我们将在第 10 章中讨论这些类型。 目前,我们将使用整数和字符串值作为我们的变量。
变量区分大小写,默认情况下大写。 为局部变量赋予小写名称是一种有时会应用的约定。 但是,您可以随意使用您想要的名称或混合使用大小写。 变量也可以包含数字,但不允许以数字开头的名称
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 ~> |
像上面示例中创建的变量仅对当前 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 中此变量的值保持不变。
Bash 使用某些 Shell 变量的方式与 Bourne Shell 相同。 在某些情况下,Bash 会为变量分配一个默认值。 下表概述了这些普通 Shell 变量
表 3-1. 保留的 Bourne Shell 变量
变量名 | 定义 |
---|---|
CDPATH | 一个以冒号分隔的目录列表,用作 cd 内置命令的搜索路径。 |
HOME | 当前用户的主目录;cd 内置命令的默认值。此变量的值也用于波浪号扩展。 |
IFS | 分隔字段的字符列表;在 Shell 将单词拆分为扩展的一部分时使用。 |
如果此参数设置为文件名并且MAILPATH变量未设置,Bash 会通知用户指定文件中收到的邮件。 | |
MAILPATH | 一个以冒号分隔的文件名列表,Shell 会定期检查新邮件。 |
OPTARG | 由 getopts 内置命令处理的最后一个选项参数的值。 |
OPTIND | 由 getopts 内置命令处理的最后一个选项参数的索引。 |
PATH | 一个以冒号分隔的目录列表,Shell 在其中查找命令。 |
PS1 | 主提示符字符串。 默认值为 "'\s-\v\$ '"。 |
PS2 | 辅助提示符字符串。 默认值为 "'> '"。 |
这些变量由 Bash 设置或使用,但其他 Shell 通常不会对它们进行特殊处理。
表 3-2. 保留的 Bash 变量
变量名 | 定义 |
---|---|
auto_resume | 此变量控制 Shell 与用户和作业控制的交互方式。 |
BASH | 用于执行当前 Bash 实例的完整路径名。 |
BASH_ENV | 如果在调用 Bash 来执行 Shell 脚本时设置了此变量,则会扩展其值,并将其用作启动文件的名称,以便在执行脚本之前读取。 |
BASH_VERSION | 当前 Bash 实例的版本号。 |
BASH_VERSINFO | 一个只读数组变量,其成员保存此 Bash 实例的版本信息。 |
COLUMNS | 由 select 内置命令用于在打印选择列表时确定终端宽度。 在收到 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 字符作为唯一输入时的操作。 |
INPUTRC | Readline 初始化文件的名称,覆盖默认值/etc/inputrc. |
LANG | 用于确定任何未通过以LC_. |
开头的变量专门选择的类别的语言环境类别。 | LC_ALLLANG此变量覆盖了LC_的值以及任何其他指定语言环境类别的变量。 |
LC_COLLATE | 此变量确定在对文件名扩展的结果进行排序时使用的排序规则,并确定文件名扩展和模式匹配中的范围表达式、等效类和排序序列的行为。 |
LC_CTYPE | 此变量确定字符的解释和文件名扩展和模式匹配中字符类的行为。 |
LC_MESSAGES | 此变量确定用于翻译以 "$" 符号开头的双引号字符串的语言环境。 |
LC_NUMERIC | 此变量确定用于数字格式的语言环境类别。 |
LINENO | 当前正在执行的脚本或 Shell 函数中的行号。 |
LINES | 由 select 内置命令用于确定打印选择列表的列长度。 |
MACHTYPE | 一个字符串,以标准的 GNU CPU-COMPANY-SYSTEM 格式完全描述 Bash 在其上运行的系统类型。 |
MAILCHECK | Shell 应多久(以秒为单位)检查MAILPATH或MAIL变量中指定的文件中的邮件。 |
OLDPWD | 由 cd 内置命令设置的先前工作目录。 |
OPTERR | 如果设置为值 1,Bash 会显示由 getopts 内置命令生成的错误消息。 |
OSTYPE | 一个字符串,描述 Bash 运行的操作系统。 |
PIPESTATUS | 一个数组变量,包含最近执行的前台管道(可能只包含单个命令)中进程的退出状态值列表。 |
POSIXLY_CORRECT | 如果此变量在 bash 启动时存在于环境中,则 Shell 进入 POSIX 模式。 |
PPID | Shell 的父进程的进程 ID。 |
PROMPT_COMMAND | 如果已设置,则该值将被解释为在打印每个主提示符(PS1). |
PS3 | 此变量的值用作 select 命令的提示符。 默认为 "'#? '" |
PS4 | 当设置-x选项时,该值是在命令行回显之前打印的提示符; 默认为 "'+ '"。 |
PWD | 由 cd 内置命令设置的当前工作目录。 |
RANDOM | 每次引用此参数时,都会生成一个介于 0 和 32767 之间的随机整数。 将一个值分配给此变量会初始化随机数生成器。 |
REPLY | read 内置命令的默认变量。 |
SECONDS | 此变量扩展为自 Shell 启动以来的秒数。 |
SHELLOPTS | 一个以冒号分隔的启用 Shell 选项列表。 |
SHLVL | 每次启动新的 Bash 实例时增加 1。 |
TIMEFORMAT | 此参数的值用作格式化字符串,用于指定如何显示以 time 保留字开头的管道的时间信息。 |
TMOUT | 如果设置为大于零的值,TMOUT则被视为 read 内置命令的默认超时时间。 在交互式 shell 中,该值被解释为在 shell 为交互式时发出主提示符后等待输入的秒数。 如果在该秒数后没有收到输入,Bash 将终止。 |
UID | 当前用户的数字实际用户 ID。 |
有关更多信息,请查阅 Bash 手册页、info 页或 doc 页。 一些变量是只读的,一些是自动设置的,而另一些变量在设置为非默认值时会失去其含义。
shell 特殊处理几个参数。 这些参数只能被引用;不允许对其进行赋值。
表 3-3. 特殊 bash 变量
字符 | 定义 |
---|---|
$* | 扩展为位置参数,从 1 开始。 当扩展发生在双引号内时,它将扩展为单个单词,每个参数的值之间用IFS特殊变量的第一个字符分隔。 |
$@ | 扩展为位置参数,从 1 开始。 当扩展发生在双引号内时,每个参数扩展为单独的单词。 |
$# | 扩展为位置参数的数量(十进制)。 |
$? | 扩展为最近执行的前台管道的退出状态。 |
$- | 连字符扩展为调用时指定的当前选项标志,通过 set 内置命令指定,或由 shell 本身设置的标志(例如-i). |
$$ | 扩展为 shell 的进程 ID。 |
$! | 扩展为最近执行的后台(异步)命令的进程 ID。 |
$0 | 扩展为 shell 或 shell 脚本的名称。 |
$_ | 下划线变量在 shell 启动时设置,包含作为参数列表传入的正在执行的 shell 或脚本的绝对文件名。 随后,它扩展为上一个命令的最后一个参数(经过扩展)。 它也被设置为每个执行的命令的完整路径名,并放置在导出到该命令的环境中。 检查邮件时,此参数保存邮件文件的名称。 |
![]() | $* 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(零)不同的退出代码。
除了使脚本更具可读性之外,变量还可以使您更快地在另一个环境或用于另一个目的的应用脚本。 考虑以下示例,一个非常简单的脚本,用于将 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 |
![]() | 大型目录和低带宽 |
---|---|
以上纯粹是一个每个人都能理解的示例,使用小型目录和同一子网上的主机。 根据您的带宽、目录的大小和远程服务器的位置,使用此机制进行备份可能需要很长时间。 对于较大的目录和较低的带宽,请使用 rsync 来保持两端目录的同步。 |