Bash编程(4) 参数与变量
1. 变量命名
变量命名只能使用数字、下划线、字母,且仅能以下划线或字母开头。
变量很少使用单个字母,单个字母一般用于循环或读取一次性文件的时候。
例:
while IFS=: read login a b c name e do printf "%-12s %s\n" "$login" "$name" done < /etc/passwd
变量名最好能望名知意
2. 变量作用域
若脚本a调用脚本b,则a无法得知b中的变量,除非将b中的变量写入环境变量中。
脚本中在变量前使用export内置命令,则可以将该变量设置为环境变量。
如:
var=whatever
export var
# bash中可简写为export var=whatever
导出(export)一个变量并不是在任何地方都可以访问该变量,除了子进程外。
例:showvar用于检验变量是否被设置
if [[ ${x+Z} = Z ]] ## $x被设置 then echo ${x+Z} echo Z if [[ -n $x ]] ## $x非空 then printf " \$x = %s\n" "$x" else printf " \$x is set but empty\n" fi else printf " %s is not set\n" "\$x" fi
若导出了一个变量,其一直保留在环境中,除非直接unset命令。
$ unset x $ mv variable showvar $ ./showvar $x is not set $ x=3 $ ./showvar $x is not set $ export x=4 $ ./showvar $x = 4 $ x= ## bash中,对一个变量重新赋值,并不会从环境变量中移除该变量 $ ./showvar $x is set but empty
设置在子shell中的变量对调用它的脚本不可见。子shell包含命令替换,如$(command) 或`command`; 管道中的所有元素;括号中的内容,如( command )
例:
printf "%s\n" ${RANDOM}{,,,,,} | while read num do (( num > ${biggest:=0} )) && biggest=$num done printf "The largest number is: %d\n" "$biggest"
执行该脚本,发现biggest为0,原因在于循环是管道的一部分,将会在子shell中执行它,而子shell中的变量对该执行脚本不可见。
bash4.2中,新的选项lastpipe,允许管道中最后的进程在当前shell中执行,通过调用:shopt -s lastpipe
3. Shell变量
shell自带变量BASH_VERSION表示bash的版本。
例:
case $BASE_VERSION in [12].*) echo "You need at least bash3.0 to run this script" >&2; exit 2;; esac
提示符PS1,PS2用于命令行中的shell交互时,PS3在使用内置命令select时使用,PS4用于在执行跟踪模式下,每行之前的打印。
Shell变量包含:
BASH BASHOPTS BASHPID BASH_ALIASES
BASH_ARGC BASH_ARGV BASH_CMDS BASH_COMMAND
BASH_EXECUTION_STRING BASH_LINENO BASH_REMATCH BASH_SOURCE
BASH_SUBSHELL BASH_VERSINFO BASH_VERSION COMP_CWORD
COMP_KEY COMP_LINE COMP_POINT COMP_TYPE
COMP_WORDBREAKS COMP_WORDS COPROC DIRSTACK
EUID FUNCNAME GROUPS HISTCMD
HOSTNAME HOSTTYPE LINENO MACHTYPE
MAPFILE OLDPWD OPTARG OPTIND
OSTYPE PIPESTATUS PPID PWD
RANDOM READLINE_LINE READLINE_POINT REPLY
SECONDS SHELLOPTS SHLVL UID
BASH_COMPAT BASH_ENV BASH_XTRACEFD CDPATH
CHILD_MAX COLUMNS COMPREPLY EMACS
FCEDIT FIGNORE FUNCNEST GLOBIGNORE
HISTCONTROL HISTFILE HISTFILESIZE HISTIGNORE
HISTSIZE HISTTIMEFORMAT HOME HOSTFILE
IFS IGNOREEOF INPUTRC LANG
LC_ALL LC_COLLATE LC_CTYPE LC_MESSAGES
LC_NUMERIC LC_NUMERIC LINES MAIL
MAILCHECK MAILPATH OPTERR PATH
POSIXLY_CORRECT PROMPT_COMMAND PROMPT_DIRTRIM PS1
PS2 PS3 PS4 SHELL
TIMEFORMAT TMOUT TMPDIR auto_resume
histchars
4. 参数扩展
(1) ${var:-default}和${var-default}:使用默认值
## ${var:-default}用于检查变量未被设置或为空,若为空或未被设置,则使用默认值
$ var= $ ./sa ${var:-default} :default: ## 如果取掉":",则${var-default}仅检测变量是否为unset $ ./sa ${var-default} :: $ unset var $ ./sa ${var-default} :default: # 为filename设置默认值 defaultfile=$HOME/.bashrc ## parse options here filename=${filename:-"$defaultfile"}
(2) ${var:+altername}, ${var+alternate}: 使用替换值
## $var设置但为空,因此替换值不生效 $ var= $ ./sa "${var:+alternate}" :: ## $var被设置,且不为空,则替换值生效 $ ./sa ${var:+alternate} :alternate: ## 取消冒号后,即使变量为空,只要被设置,替换值将生效 $ var= $ ./sa "${var+alternate}" ## $var被设置 :alternate: $ unset var $ ./sa "${var+alternate}" ## $var未被设置 :: $ var=value $ ./sa "${var+alternate}" ## $var被设置且非空 :alternate:
常用于变量中增加字符串。例:
## 变量为空时,不想在变量中增加分隔符 $ var= $ for n in a b c d e f g > do > var="$var $n" > done $ ./sa "$var" : a b c d e f g: # 为了防止a前面的空格,可以使用参数扩展 $ var= $ for n in a b c d e f g > do > var="${var:+"$var "}$n" # $var被设置且非空时,则替换为"$var " > done $ ./sa "$var" :a b c d e f g: # 上面的方法是如下2个方法方法的简写 # 1 if [ -n "$var" ] then var="$var $n" else var=$n fi # 2 [ -n "$var" ] && var="$var $n" || var=$n
(3) ${var:=default}, ${var=default}: 赋初始值
${var:=default}与${var:-default}类似,不同之处在于它也向变量赋初始值。
$ unset n $ while : > do > echo :$n: > [ ${n:=0} -gt 3 ] && break ## 如果未被设置或为空,则将$n设为0 > n=$(( $n + 1 )) > done :: :1: :2: :3: :4:
(4) ${var:?message}, ${var?message}: 如果为空或未被设置,则显示错误信息到标准错误输出
$ ./checkarg ./checkarg: line 2: 1: An argument is required $ ./checkarg x ./checkarg: line 2: 2: Two arguments are required $ ./checkarg '' '' ./checkarg: line 5: 1: A non-empty argument is required $ ./checkarg x '' ./checkarg: line 5: 2: Two non-empty arguments are required $ ./checkarg x x Thank you.
(5) ${#var}: 变量内容的长度
read passwd if [ ${#passwd} -lt 8 ] then printf "Password is too short: %d characters\n" "$#" >&2 exit 1 fi
(6) ${var%PATTERN}: 从变量结尾移除最短匹配
$ var=Toronto $ var=${var%o*} $ printf "%s\n" "$var" Toront $ printf "%s\n" "${var%o*}" Tor # dname, 打印文件路径的部分 case $1 in */*) printf "%s\n" "${1%/*}" ;; *) [ -e "$1" ] && printf "%s\n" "$PWD" || echo "." ;; esac $ ./dname /etc/passwd /etc $ ./dname bin .
(7) ${var%%PATTERN}: 从变量结尾移除最长匹配
$ var=Toronto music@ds01:~/songwang4/test$ ./sa "${var%%o*}" :T:
(8) ${var#PATTERN}: 从变量起始处移除最短匹配
$ var=Toronto $ ./sa "${var#*o}" :ronto:
(9) ${var##PATTERN}: 从变量起始处移除最长匹配
scriptname=${0##*/} ## /home/chris/bin/script => script
(10) ${var//PATTERN/STRING}: 使用STRING替换所有的PATTERN实例
## 密码隐藏显示 $ passwd=zx01.=+-a $ printf "%s\n" "${passwd//?/*}" ********* ## 单斜杠"/"仅替换第一个匹配的字符 $ printf "%s\n" "${passwd/a/*}" zx01.=+-*
(11) ${var:OFFSET:LENGTH}: 返回$var的子串
OFFSET表示起始位置,LENGTH表示截取长度。
$ var=Toronto $ ./sa "${var:3:2}" :on: $ ./sa "${var:3}" :onto: ## 负数表示从字符串结尾开始 $ ./sa "${var: -3}" ## 注意-3前面的空格,防止与变量默认值扩展混淆 :nto:
(12) ${!var}: 间接引用
若变量中含有另一个变量的名称,bash可以使用间接引用。
$ x=yes $ a=x $ ./sa ${!a} :yes: ## 使用eval也可实现类似功能 $ eval "./sa \$$a" :yes:
(13) ${var^PATTERN}: 大写转换
如果匹配PATTERN,则var的第一个字符转换为大写;当为^^时,匹配到PATTERN的所有字符转换为大写;如果PATTERN为空,则所有都匹配。
$ var=toronto $ ./sa "${var^}" ## PATTERN为空且为^,时,第一个字符大写转换 :Toronto: $ ./sa "${var^[n-z]}" :Toronto: $ ./sa "${var^^[a-o]}" ## 匹配从a到o的所有字符 :tOrONtO $ ./sa "${var^^}" ## 匹配所有 :TORONTO:
(14) ${var,PATTERN}:小写转换
与${var^PATTERN}用法相同,区别在于小写转换
$ var=TORONTO $ ./sa "${var,,}" :toronto: $ ./sa "${var,,[N-Q]}" :ToRonTo:
(15) ${var~}:大小写取反
$ var=Toronto $ ./sa "${var~}" :toronto: $ ./sa "${var~~}" :tORONTO:
5. 位置参数
位置参数由$1,...${10}引用,全部参数可以使用"$@"或"$*",大于9的索引参数需要以大括号封装。
不带参数的shift命令将会移除第一个参数,并将剩余的参数前移一位,如历史的$2将会变为$1。带参数的shift将会移动指定的大小。
$ shift 3 ## 移除前3个参数 $ shift "$#" ## 移除所有参数 $ shift "$(( $# -2 ))" ## 保留最后2位参数 ## 使用每一个参数,可以通过如下2种方法 ## 1. 使用$@ for param in "#@" ## 或 for param do : do something with $param done ## 2. 使用shift while (( $# )) do : do something with $1 shift done
6. 数组
(1) 整型索引数组
数组元素索引从0开始。Bash中的数组是稀疏的,即索引号可以不连续。
# BASH_VERSINFO表示当前运行shell的版本信息,第一个元素表示主版本号,第二个表示次版本号 $ printf "%s\n" "${BASH_VERSINFO[0]}" 4 $ printf "%s\n" "${BASH_VERSINFO[1]}" 3
可以使用*和@打印数组所有元素。当使用"*"且数组被引号括起,则数组元素不会拆分,若没有引号则会被拆分;当使用"@"时,均会被拆分
$ printf "%s\n" "${BASH_VERSINFO[*]}" # 不拆分 4 3 48 1 release x86_64-pc-linux-gnu $ printf "%s\n" ${BASH_VERSINFO[*]} # 拆分 4 3 48 1 release x86_64-pc-linux-gnu $ printf "%s\n" ${BASH_VERSINFO[@]} # 使用@时,默认均拆分,对仅限于数组元素间的拆分,而不会涉及到元素内容 4 3 48 1 release x86_64-pc-linux-gnu ## 提取数组中第二个和第三个元素 $ printf "%s\n" "${BASH_VERSINFO[@]:1:2}" 3 48 ## 当使用*或@时,"#"返回数组长度,如果指定数组元素时,则返回数组元素的长度 $ printf "%s\n" "${#BASH_VERSINFO[*]}" 6 $ printf "%s\n" "${#BASH_VERSINFO[2]}" "${#BASH_VERSINFO[5]}" 2 19 ## 数组通过下标赋值 $ name[0]=1 $ name[42]=2 ## 非连续下标的好处,原因在于对它们的操作将变得简单,直接取下一个未被设置的元素即可 $ unset a $ a[${#a[@]}]="1 $RANDOM" $ a[${#a[@]}]="2 $RANDOM" $ a[${#a[@]}]="3 $RANDOM" $ a[${#a[@]}]="4 $RANDOM" $ printf "%s\n" "${a[@]}" 1 20457 2 19494 3 30222 4 19320 ## 数组也可以仅使用如下一个命令生成 $ province=( Quebec Ontario Manitoba ) $ printf "%s\n" "${province[@]}" Quebec Ontario Manitoba ## +=用于在数组索引末位添加值 $ province+=( Saskatchewan ) $ province+=( Alberta "British Columbia" "Nova Scotia" ) $ printf "%-25s %-25s %s\n" ${province[@]} Quebec Ontario Manitoba Saskatchewan Alberta British Columbia Nova Scotia $ printf "%-25s %-25s %s\n" "${province[@]}" Quebec Ontario Manitoba Saskatchewan Alberta British Columbia Nova Scotia
(2) 关联数组
bash 4后,允许使用字符串作为下标,且数组必须先声明。
$ declare -A array $ for subscript in a b c d e > do > array[$subscript]="$subscrupt $RANDOM" > done $ printf ":%s:\n" "${array["c"]}" ## 打印单个元素 : 25475: $ printf ":%s:\n" "${array[@]}" ## 打印整个数组 : 26862: : 32278: : 25475: : 12284: : 22720: