3.2.1. 变量类型
如上例所示,shell 变量按照惯例使用大写字符。Bash 保留了两种类型的变量的列表:
3.2.1.1. 全局变量
全局变量或环境变量在所有 shell 中都可用。env或printenv命令可用于显示环境变量。这些程序带有sh-utils包。
下面是一个典型的输出:
franky ~> printenvCC=gccCDPATH=.:~:/usr/local:/usr:/CFLAGS=-O2 -fomit-frame-pointerCOLORTERM=gnome-terminalCXXFLAGS=-O2 -fomit-frame-pointerDISPLAY=:0DOMAIN=hq.garrels.bee=TOR=viFCEDIT=viFIGNORE=.o:~G_BROKEN_FILENAMES=1GDK_USE_XFT=1GDMSESSION=DefaultGNOME_DESKTOP_SESSION_ID=DefaultGTK_RC_FILES=/etc/gtk/gtkrc:/nethome/franky/.gtkrc-1.2-gnome2GWMCOLOR=darkgreenGWMTERM=xtermHISTFILESIZE=5000history_control=ignoredupsHISTSIZE=2000HOME=/nethome/frankyHOSTNAME=octarine.hq.garrels.beINPUTRC=/etc/inputrcIRCNAME=frankyJAVA_HOME=/usr/java/j2sdk1.4.0LANG=en_USLDFLAGS=-sLD_LIBRARY_PATH=/usr/lib/mozilla:/usr/lib/mozilla/pluginsLESSCHARSET=latin1LESS=-edfMQLESSOPEN=|/usr/bin/lesspipe.sh %sLEX=flexLOCAL_MACHINE=octarineLOGNAME=frankyLS_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=octarineMAILCHECK=60MAIL=/var/mail/frankyMANPATH=/usr/man:/usr/share/man/:/usr/local/man:/usr/X11R6/manMEAN_MACHINES=octarineMOZ_DIST_BIN=/usr/lib/mozillaMOZILLA_FIVE_HOME=/usr/lib/mozillaMOZ_PROGRAM=/usr/lib/mozilla/mozilla-binMTOOLS_FAT_COMPATIBILITY=1MYMALLOC=0NNTPPORT=119NNTPSERVER=newsNPX_PLUGIN_PATH=/plugin/ns4plugin/:/usr/lib/netscape/pluginsOLDPWD=/nethome/frankyOS=LinuxPAGER=lessPATH=/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/frankySESSION_MANAGER=local/octarine.hq.garrels.be:/tmp/.ICE-unix/22106SHELL=/bin/bashSHELL_LOGIN=--loginSHLVL=2SSH_AGENT_PID=22161SSH_ASKPASS=/usr/libexec/openssh/gnome-ssh-askpassSSH_AUTH_SOCK=/tmp/ssh-XXmhQ4fC/agent.22106START_WM=twmTERM=xtermTYPE=typeUSERNAME=frankyUSER=franky_=/usr/bin/printenvVISUAL=viWINDOWID=20971661XAPPLRESDIR=/nethome/franky/app-defaultsXAUTHORITY=/nethome/franky/.XauthorityXENVIRONMENT=/nethome/franky/.XdefaultsXFILESEARCHPATH=/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%SXKEYSYMDB=/usr/X11R6/lib/X11/XKeysymDBXMODIFIERS=@im=noneXTERMID=XWINHOME=/usr/X11R6X=X11R6YACC=bison -y
3.2.1.2. 局部变量
局部变量仅在当前 shell 中可用。使用不带任何选项的set命令将显示所有变量(包括环境变量)和函数的列表。输出将根据当前语言环境进行排序并以可重复使用的格式显示。
下面是一个比较printenv和set输出的 diff 文件:
ranky ~> diff set.sorted printenv.sorted | grep "<" | awk '{ print $2 }'BASE=/nethome/franky/.Shell/hq.garrels.be/octarine.aliasesBASH=/bin/bashBASH_VERSINFO=([0]="2"BASH_VERSION='2.05b.0(1)-release'COLUMNS=80DIRSTACK=()DO_FORTUNE=EUID=504GROUPS=()HERE=/home/frankyHISTFILE=/nethome/franky/.bash_historyHOSTTYPE=i686IFS=$'LINES=24MACHTYPE=i686-pc-linux-gnuOPTERR=1OPTIND=1OSTYPE=linux-gnuPIPESTATUS=([0]="0")PPID=10099PS4='+PWD_REAL='pwdSHELLOPTS=braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitorTHERE=/home/frankyUID=504
3.2.1.3. 变量引用的内容
除了将变量分为局部变量和全局变量外,我们还可以根据变量所包含的内容的种类进行分类。在这方面,变量有 4 种类型:
- 字符串变量
- 整数变量
- 常量变量
- 数组变量
3.2.2. 创建变量
默认情况下,变量区分大小写,默认大写。给局部变量一个小写的名字是一种约定。但是,您可以自由使用所需的名称或混合大小写。变量也可以包含数字,但不允许以数字开头的名称:
prompt> export 1number=1bash: export: `1number=1': not a valid identifier
要在 shell 中设置变量,请使用
VARNAME=”value”
在等号周围放置空格会导致错误。在为变量赋值时使用双引号是一个好习惯:这将减少出错的机会。
franky ~> MYVAR1="2"franky ~> echo $MYVAR12franky ~> first_name="Franky"franky ~> echo $first_nameFrankyfranky ~> full_name="Franky M. Singh"franky ~> echo $full_nameFranky M. Singhfranky ~> MYVAR-2="2"bash: MYVAR-2=2: command not foundfranky ~> MYVAR1 ="2"bash: MYVAR1: command not foundfranky ~> MYVAR1= "2"bash: 2: command not foundfranky ~> unset MYVAR1 first_name full_namefranky ~> echo $MYVAR1 $first_name $full_name<--no output-->franky ~>
3.2.3. 导出变量
像上面示例中创建的变量仅对当前 shell 可用。它是一个局部变量:当前 shell 的子进程不会知道这个变量。为了将变量传递给子shell,我们需要使用export命令将它们导出。导出的变量称为环境变量。设置和导出通常一步完成:
export VARNAME=”value“
子shell可以更改它从父shell继承的变量,但子外壳所做的更改不会影响父外壳。这在示例中得到了证明:
franky ~> full_name="Franky M. Singh"franky ~> bashfranky ~> echo $full_namefranky ~> exitfranky ~> export full_namefranky ~> bashfranky ~> echo $full_nameFranky M. Singhfranky ~> export full_name="Charles the Great"franky ~> echo $full_nameCharles the Greatfranky ~> exitfranky ~> echo $full_nameFranky M. Singhfranky ~>
当第一次尝试在子 shell 中读取full_name的值时,它不存在(echo显示一个空字符串)。子shell 退出,full_name在父shell 中被导出——一个变量在被赋值后可以被导出。然后启动一个新的子shell,其中从父级导出的变量是可见的。该变量已更改为保留另一个名称,但该变量在父级中的值保持不变。
3.2.4. 保留变量
3.2.4.1. Bourne shell 保留变量
Bash 使用某些 shell 变量与 Bourne shell.相同。在某些情况下,Bash 会为变量分配一个默认值。下表概述了这些普通的 shell 变量:
Table 3-1. 保留变量-Bourne shell
| 变量名称 | 定义 |
|---|---|
| CDPATH | 以冒号分隔的目录列表,用作cd内置命令的搜索路径。 |
| HOME | 当前用户的主目录;cd内置的默认值。此变量的值也用于波浪号。 |
| IFS | 分隔字段的字符列表;当 shell 将单词拆分为扩展命令时使用 |
| 如果此参数设置为文件名且未设置MAILPATH变量,则 Bash 会通知用户指定文件中的邮件。 | |
| MAILPATH | 以冒号分隔的文件名列表,shell 定期检查新邮件。 |
| OPTARG | 内置命令:getopts处理的最后一个选项参数的值。 |
| OPTIND | getopts处理的最后一个选项参数的索引。 |
| PATH | 以冒号分隔的目录列表,shell 在其中查找命令。 |
| PS1 | shell的主要提示字符。默认值为”‘\s-\v\$ ‘“。 |
| PS2 | 辅助提示字符串。默认值为”‘> ‘“。 |
3.2.4.2. Bash 保留变量
这些变量由 Bash 设置或使用,但其他 shell 通常不会对它们进行特殊处理。
Table 3-2. Reserved Bash variables
| 变量名称 | 定义 |
|---|---|
| auto_resume | 这个变量控制了shell如何处理用户任务 |
| BASH | 执行当前实例bash的完全路径名称 |
| BASH_ENV | I若设置此变量。那么在执行脚本前,该值会被作为启动文件而扩展 |
| BASH_VERSION | bash的版本号 |
| BASH_VERSINFO | 一组可读的变量,记住当前bash的版本实例。 |
| COLUMNS | select函数用来判定选中的列表的宽度,基于sigwinch信号自动设置 |
| COMP_CWORD | 一个当前游标的指针 |
| COMP_LINE | 当前命令行 |
| COMP_POINT | 当前命令的开始位置的指针 |
| COMP_WORDS | |
| COMPREPLY | |
| DIRSTACK | 当前字典表里的数组值 |
| EUID | 用户ID |
| FCEDIT | |
| FIGNORE | |
| FUNCNAME | 当前执行的shell函数 |
| GLOBIGNORE | |
| GROUPS | 包含当前用户的用户组列表 |
| histchars | |
| HISTCMD | |
| HISTCONTROL | |
| HISTFILE | 历史命令存储的地址,默认 ~/.bash_history. |
| HISTFILESIZE | 历史命令文件的最大行数 |
| HISTIGNORE | |
| HISTSIZE | 历史命令列表的最大长度 |
| HOSTFILE | host文件的地址,和/etc/hosts 格式一样 |
| HOSTNAME | |
| HOSTTYPE | |
| IGNOREEOF | |
| INPUTRC | 读取行文件的初始化文件,重写了默认的/etc/inputrc. |
| LANG | |
| LC_ALL | |
| LC_COLLATE | |
| LC_CTYPE | |
| LC_MESSAGES | |
| LC_NUMERIC | |
| LINENO | |
| LINES | |
| MACHTYPE | |
| MAILCHECK | shell应该多久(秒)检查邮箱 |
| OLDPWD | cd之前进入的目录 |
| OPTERR | |
| OSTYPE | |
| PIPESTATUS | |
| POSIXLY_CORRECT | |
| PPID | shell父进程的ID |
| PROMPT_COMMAND | I |
| PS3 | |
| PS4 | |
| PWD | 当前工作目录的完全路径 |
| RANDOM | 随机数生产,0-32767 |
| REPLY | |
| SECONDS | shell启动以来的秒数 |
| SHELLOPTS | |
| SHLVL | |
| TIMEFORMAT | |
| TMOUT | |
| UID |
检查一下bash man,会有更详细的说明。一些变量是只读的,一些是自动设置的。
3.2.5. 特殊参数
shell对一些参数进行了特殊处理,这些参数只能引用,不允许赋值。
Table 3-3. Special bash variables
| Character | Definition |
|---|---|
| $* | 扩展指定位置的参数,位置从1开始。当扩展出现在双引号内时,它会扩展为一个单词,每个参数的值由IFS特殊变量的第一个字符分隔。 |
| $@ | 扩展指定位置的参数,位置从1开始。当扩展出现在双引号内时,每个参数都扩展为一个单独的单词。 |
| $# | 扩展指定位置的参数的数量 |
| $? | 最近执行管道符的退出状态 |
| $- | 连字符扩展为调用时指定的当前选项,由set命令或由 shell 本身设置的那些(例如-i)。 |
| $$ | 拓展进程ID |
| $! | 最近执行的后台命令的进程ID |
| $0 | 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."echoecho "The total number of positional parameters is $#."
franky ~> positional.sh one two three four fiveone 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 twoone 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.
franky ~> grep dictionary /usr/share/dict/wordsdictionaryfranky ~> echo $_/usr/share/dict/wordsfranky ~> echo $$10662franky ~> mozilla &[1] 11064franky ~> echo $!11064franky ~> echo $0bashfranky ~> echo $?0franky ~> ls doesnotexistls: doesnotexist: No such file or directoryfranky ~> echo $?1franky ~>
用户franky开始输入grep命令,这导致$_的赋值。他的 shell 的进程 ID 是 10662。在后台放置一个作业后,!保存后台作业的进程 ID。运行的外壳是bash。出错时,? 拥有一个不同于 0(零)的退出代码。
3.2.6. Script recycling with variables
除了使脚本更具可读性之外,变量还可以让您更快地将脚本应用于另一个环境或用于其他目的。考虑以下示例,一个非常简单的脚本,它将 franky的主目录备份到远程服务器:
#!/bin/bash# This script makes a backup of my home directory.cd /home# This creates the archivetar 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/nullbzip2 /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.logecho 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=/homeBACKUPFILES=frankyTARFILE=/var/tmp/home_franky.tarBZIPFILE=/var/tmp/home_franky.tar.bz2SERVER=bordeauxREMOTEDIR=/opt/backup/frankyLOGFILE=/home/franky/log/home_backup.logcd $BACKUPDIR# This creates the archivetar 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/nullbzip2 $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 >> $LOGFILEecho backup succeeded >> $LOGFILE
![]() |
Large directories and low bandwidth |
|---|---|
| 上面纯粹是一个大家可以理解的例子,使用一个小目录和一个主机在同一个子网上。根据您的带宽、目录的大小和远程服务器的位置,使用这种机制进行备份可能会花费大量时间。对于较大的目录和较低的带宽,使用rsync使两端的目录保持同步。 |


