3.2.1. 变量类型

如上例所示,shell 变量按照惯例使用大写字符。Bash 保留了两种类型的变量的列表:

3.2.1.1. 全局变量

全局变量或环境变量在所有 shell 中都可用。envprintenv命令可用于显示环境变量。这些程序带有sh-utils包。

下面是一个典型的输出:

  1. franky ~> printenv
  2. CC=gcc
  3. CDPATH=.:~:/usr/local:/usr:/
  4. CFLAGS=-O2 -fomit-frame-pointer
  5. COLORTERM=gnome-terminal
  6. CXXFLAGS=-O2 -fomit-frame-pointer
  7. DISPLAY=:0
  8. DOMAIN=hq.garrels.be
  9. e=
  10. TOR=vi
  11. FCEDIT=vi
  12. FIGNORE=.o:~
  13. G_BROKEN_FILENAMES=1
  14. GDK_USE_XFT=1
  15. GDMSESSION=Default
  16. GNOME_DESKTOP_SESSION_ID=Default
  17. GTK_RC_FILES=/etc/gtk/gtkrc:/nethome/franky/.gtkrc-1.2-gnome2
  18. GWMCOLOR=darkgreen
  19. GWMTERM=xterm
  20. HISTFILESIZE=5000
  21. history_control=ignoredups
  22. HISTSIZE=2000
  23. HOME=/nethome/franky
  24. HOSTNAME=octarine.hq.garrels.be
  25. INPUTRC=/etc/inputrc
  26. IRCNAME=franky
  27. JAVA_HOME=/usr/java/j2sdk1.4.0
  28. LANG=en_US
  29. LDFLAGS=-s
  30. LD_LIBRARY_PATH=/usr/lib/mozilla:/usr/lib/mozilla/plugins
  31. LESSCHARSET=latin1
  32. LESS=-edfMQ
  33. LESSOPEN=|/usr/bin/lesspipe.sh %s
  34. LEX=flex
  35. LOCAL_MACHINE=octarine
  36. LOGNAME=franky
  37. 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:
  38. MACHINES=octarine
  39. MAILCHECK=60
  40. MAIL=/var/mail/franky
  41. MANPATH=/usr/man:/usr/share/man/:/usr/local/man:/usr/X11R6/man
  42. MEAN_MACHINES=octarine
  43. MOZ_DIST_BIN=/usr/lib/mozilla
  44. MOZILLA_FIVE_HOME=/usr/lib/mozilla
  45. MOZ_PROGRAM=/usr/lib/mozilla/mozilla-bin
  46. MTOOLS_FAT_COMPATIBILITY=1
  47. MYMALLOC=0
  48. NNTPPORT=119
  49. NNTPSERVER=news
  50. NPX_PLUGIN_PATH=/plugin/ns4plugin/:/usr/lib/netscape/plugins
  51. OLDPWD=/nethome/franky
  52. OS=Linux
  53. PAGER=less
  54. PATH=/nethome/franky/bin.Linux:/nethome/franky/bin:/usr/local/bin:/usr/local/sbin:/usr/X11R6/bin:/usr/bin:/usr/sbin:/bin:/sbin:.
  55. PS1=\[\033[1;44m\]franky is in \w\[\033[0m\]
  56. PS2=More input>
  57. PWD=/nethome/franky
  58. SESSION_MANAGER=local/octarine.hq.garrels.be:/tmp/.ICE-unix/22106
  59. SHELL=/bin/bash
  60. SHELL_LOGIN=--login
  61. SHLVL=2
  62. SSH_AGENT_PID=22161
  63. SSH_ASKPASS=/usr/libexec/openssh/gnome-ssh-askpass
  64. SSH_AUTH_SOCK=/tmp/ssh-XXmhQ4fC/agent.22106
  65. START_WM=twm
  66. TERM=xterm
  67. TYPE=type
  68. USERNAME=franky
  69. USER=franky
  70. _=/usr/bin/printenv
  71. VISUAL=vi
  72. WINDOWID=20971661
  73. XAPPLRESDIR=/nethome/franky/app-defaults
  74. XAUTHORITY=/nethome/franky/.Xauthority
  75. XENVIRONMENT=/nethome/franky/.Xdefaults
  76. 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
  77. XKEYSYMDB=/usr/X11R6/lib/X11/XKeysymDB
  78. XMODIFIERS=@im=none
  79. XTERMID=
  80. XWINHOME=/usr/X11R6
  81. X=X11R6
  82. YACC=bison -y

3.2.1.2. 局部变量

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

下面是一个比较printenvset输出的 diff 文件

  1. ranky ~> diff set.sorted printenv.sorted | grep "<" | awk '{ print $2 }'
  2. BASE=/nethome/franky/.Shell/hq.garrels.be/octarine.aliases
  3. BASH=/bin/bash
  4. BASH_VERSINFO=([0]="2"
  5. BASH_VERSION='2.05b.0(1)-release'
  6. COLUMNS=80
  7. DIRSTACK=()
  8. DO_FORTUNE=
  9. EUID=504
  10. GROUPS=()
  11. HERE=/home/franky
  12. HISTFILE=/nethome/franky/.bash_history
  13. HOSTTYPE=i686
  14. IFS=$'
  15. LINES=24
  16. MACHTYPE=i686-pc-linux-gnu
  17. OPTERR=1
  18. OPTIND=1
  19. OSTYPE=linux-gnu
  20. PIPESTATUS=([0]="0")
  21. PPID=10099
  22. PS4='+
  23. PWD_REAL='pwd
  24. SHELLOPTS=braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitor
  25. THERE=/home/franky
  26. UID=504

3.2.1.3. 变量引用的内容

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

  • 字符串变量
  • 整数变量
  • 常量变量
  • 数组变量

3.2.2. 创建变量

默认情况下,变量区分大小写,默认大写。给局部变量一个小写的名字是一种约定。但是,您可以自由使用所需的名称或混合大小写。变量也可以包含数字,但不允许以数字开头的名称:

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

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

VARNAME=”value”

在等号周围放置空格会导致错误。在为变量赋值时使用双引号是一个好习惯:这将减少出错的机会。

  1. franky ~> MYVAR1="2"
  2. franky ~> echo $MYVAR1
  3. 2
  4. franky ~> first_name="Franky"
  5. franky ~> echo $first_name
  6. Franky
  7. franky ~> full_name="Franky M. Singh"
  8. franky ~> echo $full_name
  9. Franky M. Singh
  10. franky ~> MYVAR-2="2"
  11. bash: MYVAR-2=2: command not found
  12. franky ~> MYVAR1 ="2"
  13. bash: MYVAR1: command not found
  14. franky ~> MYVAR1= "2"
  15. bash: 2: command not found
  16. franky ~> unset MYVAR1 first_name full_name
  17. franky ~> echo $MYVAR1 $first_name $full_name
  18. <--no output-->
  19. franky ~>

3.2.3. 导出变量

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

export VARNAME=”value

子shell可以更改它从父shell继承的变量,但子外壳所做的更改不会影响父外壳。这在示例中得到了证明:

  1. franky ~> full_name="Franky M. Singh"
  2. franky ~> bash
  3. franky ~> echo $full_name
  4. franky ~> exit
  5. franky ~> export full_name
  6. franky ~> bash
  7. franky ~> echo $full_name
  8. Franky M. Singh
  9. franky ~> export full_name="Charles the Great"
  10. franky ~> echo $full_name
  11. Charles the Great
  12. franky ~> exit
  13. franky ~> echo $full_name
  14. Franky M. Singh
  15. 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 将单词拆分为扩展命令时使用
MAIL 如果此参数设置为文件名且未设置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 或正在执行的脚本的绝对文件名。随后,它在展开后到前一个命令的最后一个参数。
它还表示为执行的每个命令的完整路径名,并放置在导出到该命令的环境中。检查邮件时,此参数保存邮件文件的名称。
3.2 变量 - 图1 $* vs. $@
“$“的实现一直是个问题,实际上应该用”$@”的行为来代替。在编码人员使用”$“的几乎所有情况下,它们的意思是”$@”。 “$*”可能会导致您的软件出现错误甚至安全漏洞。

位置参数是 shell 脚本名称后面的单词。它们被放入变量$1、$2、$3等等。只要需要,变量就会被添加到内部数组中。 $#保存参数的总数,如以下简单脚本所示:

  1. #!/bin/bash
  2. # positional.sh
  3. # This script reads 3 positional parameters and prints them out.
  4. POSPAR1="$1"
  5. POSPAR2="$2"
  6. POSPAR3="$3"
  7. echo "$1 is the first positional parameter, \$1."
  8. echo "$2 is the second positional parameter, \$2."
  9. echo "$3 is the third positional parameter, \$3."
  10. echo
  11. echo "The total number of positional parameters is $#."
  1. franky ~> positional.sh one two three four five
  2. one is the first positional parameter, $1.
  3. two is the second positional parameter, $2.
  4. three is the third positional parameter, $3.
  5. The total number of positional parameters is 5.
  6. franky ~> positional.sh one two
  7. one is the first positional parameter, $1.
  8. two is the second positional parameter, $2.
  9. is the third positional parameter, $3.
  10. The total number of positional parameters is 2.
  1. franky ~> grep dictionary /usr/share/dict/words
  2. dictionary
  3. franky ~> echo $_
  4. /usr/share/dict/words
  5. franky ~> echo $$
  6. 10662
  7. franky ~> mozilla &
  8. [1] 11064
  9. franky ~> echo $!
  10. 11064
  11. franky ~> echo $0
  12. bash
  13. franky ~> echo $?
  14. 0
  15. franky ~> ls doesnotexist
  16. ls: doesnotexist: No such file or directory
  17. franky ~> echo $?
  18. 1
  19. franky ~>

用户franky开始输入grep命令,这导致$_的赋值。他的 shell 的进程 ID 是 10662。在后台放置一个作业后,!保存后台作业的进程 ID。运行的外壳是bash。出错时,? 拥有一个不同于 0(零)的退出代码。

3.2.6. Script recycling with variables

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

  1. #!/bin/bash
  2. # This script makes a backup of my home directory.
  3. cd /home
  4. # This creates the archive
  5. tar cf /var/tmp/home_franky.tar franky > /dev/null 2>&1
  6. # First remove the old bzip2 file. Redirect errors because this generates some if the archive
  7. # does not exist. Then create a new compressed file.
  8. rm /var/tmp/home_franky.tar.bz2 2> /dev/null
  9. bzip2 /var/tmp/home_franky.tar
  10. # Copy the file to another host - we have ssh keys for making this work without intervention.
  11. scp /var/tmp/home_franky.tar.bz2 bordeaux:/opt/backup/franky > /dev/null 2>&1
  12. # Create a timestamp in a logfile.
  13. date >> /home/franky/log/home_backup.log
  14. echo backup succeeded >> /home/franky/log/home_backup.log

首先,如果每次需要时手动命名文件和目录,则更有可能出错。其次,假设franky想把这个脚本交给carol,那么 carol 必须做一些编辑才能使用脚本备份她的主目录。如果franky想要使用这个脚本来备份其他目录,也是如此。为了便于回收,请将所有文件、目录、用户名、服务器名等设置为变量。因此,您只需要编辑一次值,而无需通过整个脚本来检查参数出现的位置。这是一个例子:

  1. #!/bin/bash
  2. # This script makes a backup of my home directory.
  3. # Change the values of the variables to make the script work for you:
  4. BACKUPDIR=/home
  5. BACKUPFILES=franky
  6. TARFILE=/var/tmp/home_franky.tar
  7. BZIPFILE=/var/tmp/home_franky.tar.bz2
  8. SERVER=bordeaux
  9. REMOTEDIR=/opt/backup/franky
  10. LOGFILE=/home/franky/log/home_backup.log
  11. cd $BACKUPDIR
  12. # This creates the archive
  13. tar cf $TARFILE $BACKUPFILES > /dev/null 2>&1
  14. # First remove the old bzip2 file. Redirect errors because this generates some if the archive
  15. # does not exist. Then create a new compressed file.
  16. rm $BZIPFILE 2> /dev/null
  17. bzip2 $TARFILE
  18. # Copy the file to another host - we have ssh keys for making this work without intervention.
  19. scp $BZIPFILE $SERVER:$REMOTEDIR > /dev/null 2>&1
  20. # Create a timestamp in a logfile.
  21. date >> $LOGFILE
  22. echo backup succeeded >> $LOGFILE
3.2 变量 - 图2 Large directories and low bandwidth
上面纯粹是一个大家可以理解的例子,使用一个小目录和一个主机在同一个子网上。根据您的带宽、目录的大小和远程服务器的位置,使用这种机制进行备份可能会花费大量时间。对于较大的目录和较低的带宽,使用rsync使两端的目录保持同步。