3.2.1. 变量类型
如上例所示,shell 变量按照惯例使用大写字符。Bash 保留了两种类型的变量的列表:
3.2.1.1. 全局变量
全局变量或环境变量在所有 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
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.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
3.2.1.3. 变量引用的内容
除了将变量分为局部变量和全局变量外,我们还可以根据变量所包含的内容的种类进行分类。在这方面,变量有 4 种类型:
- 字符串变量
- 整数变量
- 常量变量
- 数组变量
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命令将它们导出。导出的变量称为环境变量。设置和导出通常一步完成:
export VARNAME=”value“
子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 ~>
当第一次尝试在子 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."
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.
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。运行的外壳是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 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
Large directories and low bandwidth | |
---|---|
上面纯粹是一个大家可以理解的例子,使用一个小目录和一个主机在同一个子网上。根据您的带宽、目录的大小和远程服务器的位置,使用这种机制进行备份可能会花费大量时间。对于较大的目录和较低的带宽,使用rsync使两端的目录保持同步。 |