第三部分 超越基本

第九章 变量重游

如果变量使用恰当,将会增加脚本的能量和灵活性。但前提是这需要仔细学习变量的细节知识。

9.1 内部变量

$BASH 这个变量将指向Bash的二进制执行文件的位置

echo $BASH   #/bin/bash

$BASH_ENV 这个环境变量将指向一个Bash启动文件,这个启动文件将在调用一个脚本是被读取

$BASE_SUBSHELL 这个变量将提醒subshell的层次,这是一个在version3才被添加到Bash中的新特性。见Example 20.1

$BASH_VERSINFO[n] 这个Bash安装信息的一个6元素的数组。与下边的$BASH_VERSION很像,但这个更加详细

#Bash version info :
for n in 0 1 2 3 4 5
doecho "BASH_VERSINFO[$n]=${BASH_VERSINFO[$n]}"
done
#BASH_VERSINFO[0]=4   #主版本号
#BASH_VERSINFO[1]=1   #次版本号
#BASH_VERSINFO[2]=2   #Patch次数
#BASH_VERSINFO[3]=1   #Build version
#BASH_VERSINFO[4]=release   #Release status
#BASH_VERSINFO[5]=x86_64-redhat-linux-gnu   #Architecture

$BASH_VERSION 安装在系统上的Base的版本号

echo $BASH_VERSION   #4.1.2(1)-release

$DIRSTACK 在目录栈中最上边的值(将受到pushd和popd的影响)
#这个内建的变量与dirs命令是保持一致的,但是dirs命令将显示目录栈的整改内容

$EDITOR 脚本调用的默认编辑器,一般是vi或是emacs

$EUID "effective"用户ID号
#当前用户被假定的任何id号。可能在su命令中使用
#注意:$EUID并不一定与$UID相同

$FUNCNAME 当前函数的名字

xyz23(){echo "$FUNCNAME now executing."   #xyz23正在执行
}
xyz23
echo "FUNCNAME=$FUNCNAME"   #FUNCNAME=
#出了函数就变为Null了。

$GLOBIGNORE 一个文件名的模式匹配列表,如果在file globbing中匹配到的文件包含这个列表中的某个文件,那么这个文件将被从匹配的文件中去掉

$GROUPS 当前用户属于的组

$HOME 用户的home目录,一般都是/home/username(见Example 9.14)

$HOSTNAME hostname命令将在一个init脚本中,在启动的时候分配一个系统名字
gethostname()函数将用来设置这个$HOSTNAME内部变量。(见Example 9.14)

$HOSTTYPE 主机类型

echo $HOSTTYPE   #x86_64

$IFS 内部域分割符
这个变量用来觉得Bash在解释字符串是如何识别域或者单词边界
$IFS默认为空白(空格,tab,新行),但可以修改,比如在分析逗号分隔的数据文件时。
注意:$IFS中的第一个字符,具体见Example 5.1

[root@localhost aaa]# set a b c d
[root@localhost aaa]# IFS="="
[root@localhost aaa]# echo "$*"
a=b=c=d

Example 9.1 $FIS和空白

#!/bin/bash
#$IFS处理空白的方法,与处理其他字符不同output_args_one_per_line(){for argdo echo "[$arg]"done
}
echo;echo "IFS=\" \""
echo "----------"IFS=" "
var1="a  b c  "
output_args_one_per_line $var1echo;echo "IFS=:"
echo "----------"IFS=:
var2=":a::b:c:::"
output_args_one_per_line $var2
echoexit 0#[root@localhost ~]# sh test.sh
#
#IFS=" "
#----------
#[a]
#[b]
#[c]
#
#IFS=:
#----------
#[]
#[a]
#[]
#[b]
#[c]
#[]
#[]

Example 12.37 也是使用$IFS的另一个启发性的例子。

$IGNOREEOF 忽略EOF:告诉shell在log out之前要忽略多少文件结束符

$LC_COLLATE 常在.bashrc或/etc/profile中设置,这个变量用来在文件名扩展和模式匹配校对顺序。
如果$LC_COLLATE被错误的设置,那么将会在filename globbing中引起错误的结果
注意:在2.05以后的Bash版本中,filename globbing将不再对[]中的字符区分大小写
比如:ls [A-M]* 将匹配File.txt也会匹配file1.txt。为了恢复[]的习惯用法,设置$LC_COLLATE的值为c,使用export LC_COLLATE=c在/etc/profile或者是~/.bashrc中

$LC_CTYPE 这个内部变量用来控制globbing和模式匹配的字符串解释

$LINENO 这个变量记录它所在的shell脚本中的行号。这个变量一般用于调试目的

last_cmd_arg=$_
echo "At line number $LINENO,variable \"v1\" =$v1"
echo "Last command argument processed=$last_cmd_arg"

$MACHTYPE 系统类型;提示系统硬件

[root@localhost aaa]# echo $MACHTYPE
x86_64-redhat-linux-gnu

$OLDPWD 老的工作目录(你所在的之前的目录)

$OSTYPE 操作系统类型

[root@zhhs aaa]# echo $OSTYPE
linux-gnu

$PATH 指向Bash外部命令所在的位置,一般为/usr/bin,/usr/X11R6/bin等
当给出一个命令时,Bash将自动对$PATH中的目录做一张hash表。$PATH中以:分隔的目录列表将被存储在环境变量中。一般的,系统存储的$PATH定义在/ect/processed或~/.bashrc中(见Appendix G)

echo $PATH
/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin:/sbin:/usr/sbin
PATH=${PATH}:/opt/bin
#将把/opt/bin目录附加到$PATH变量中。在脚本中,这是一个添加目录到$PATH中的便捷方法。这样在这个脚本退出的时候,$PATH将恢复

#注意:当前的动作目录"./"一般都在$PATH中被省去

$PIPESTATUS 数组变量将保存最后一个运行的前台管道的退出码。

这个退出码和最后一个命令运行的退出码并不一定相同

[root@localhost aaa]# echo $PIPESTATUS
0
[root@localhost aaa]# ls -al |ww
-bash: ww: command not found
[root@localhost aaa]#  echo $PIPESTATUS
141[root@localhost aaa]# ls -al | ww
-bash: ww: command not found
[root@localhost aaa]# echo $?
127

$PIPESTATUS数组的每个成员都会保存一个管道命令的退出码,$PIPESTATUS[0]保存第一个,$PIPESTATUS[1]保存第二个,以此类推

注意:$PIPESTATUS变量在一个login shell中可能会包含一个错误的0值(3.0一下的版本会有这个问题)

who | grep nobody |sort
echo ${PIPESTATUS[*]}
0

包含在脚本中的时候会产生一个期望值 0 1 0

注意:在某些上下文$PIPESTATUS可能不会给出正确的结果

[root@localhost aaa]# echo $BASH_VERSION
4.1.2(1)-release[root@localhost aaa]# ls | ww | wc
-bash: ww: command not found0       0       0
[root@localhost aaa]# echo ${PIPESTATUS[@]}
141 127 0
#Chet Ramey把上边输出不正确的原因归咎于ls的行为。
#因为如果把ls的结果放到管道上,并且这个输出没有被读取,那么SIGPIPE将会kil掉它,并且退出码变为141,而不是我们期望的0.这种情况也会发生在tr命令中

注意:$PIPESTATUS是一个不稳定变量。在任何命令插入之前,并且管道询问之后,这么变量需要立即被捕捉

$PPID 一个进程的$PPID就是它的父进程的进程id(pid);可以使用pidof命令对比一下

$PROMPT_COMMAND 这个变量保存一个在主提示符($PS1)显示之前需要执行的命令

$PS1 主提示符,具体见命令行上的显示

$PS2 第二提示符,当你需要额外的输入的时候将会显示,默认为">"

$PS3 第三提示符,在一个select循环中显示(见Example 10.29)

$PS4 第四提示符,当使用-x选项调用脚本时,这个提示符将出现在每行的输出前边。默认为"+"

$PWD 工作目录

#!/bin/bash
E_WRONG_DIRECTORY=73
TargetDirectory=/home/bozo/projects/GreatAmericanNovelcd $TargetDirectory
echo "Deleting stale files in $TargetDirectory."if [ "$PWD" != "$TargetDirectory" ];then   #防止删错目录echo "Wrong directory"echo "In $PWD,rather than $TargetDirectory"echo "Bailing out"exit $E_WRONG_DIRECTORY
firm -rf *
rm .[A-Za-z0-9]*   #删除隐藏文件
rm -f .[^.]*..?*   #删除以多个.开头的文件echo "Done"
echo "Old files deleted in $TargetDirectory"
echo exit 0

$REPLY read命令如果没有给变量,那么输入将保存在$REPLY中。在select菜单中也可以,但是只提供选择的变量的项数,而不是变量本身的值

#!/bin/bash
#repli.sh
#REPLY是read命令结果保存的默认变量echo -n "What is your favorite vegetable?"
readecho "Your favorite vegetable is $REPLY"   #当在没有变量提供给read命令时,REPLY才保存最后一个read命令读入的值echo -n "What is your favorite fruit?"
read fruit
echo "Your favorite fruit is $fruit"echo "Value of \$REPLY is still $REPLY"echo
exit 0

$SECONDS 这个脚本已经运行的时间(单位:秒)

#!/bin/bash
TIME_LIMIT=10
INTERVAL=1echo
echo "Hit Control-C to exit before $TIME_LIMIT seconds"
echowhile [ "$SECONDS" -le "$TIME_LIMIT" ]
doif [ "$SECONDS" -eq 1 ];thenunits=secondelseunits=secondsfiecho "This script has been running $SECONDS $units"sleep $INTERVAL
doneecho -e "\a"
exit 0

$SHELLOPTS 这个变量保存shell允许的选项,这个变量是只读的

[root@localhost aaa]#  echo $SHELLOPTS
braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitor

$SHLVL shell层次,就是shell层叠的层次,如果是命令行,那$SHLVL就是1,如果命令执行的脚本中,$SHLVL就是2,以此类推

$TMOUT 如果$TMOUT环境变量被设置为一个非0的值,那么在过了这个指定时间后,shell提示符将会超时,这回引起一个logout。
在2.05版本的Bash中,已经支持一个带有read命令的脚本中使用$TMOUT变量

#!/bin/bash
TMOUT=5echo "What is your favrite song?"
echo "Quickly now,you only have $TMOUT seconds to answer"
read songif [ -z "$song" ];thensong="(no answer)"
fi
echo "Your favorite song is $song"exit 0

这里有一个更复杂的方法来在一个脚本中实现超时功能。一种办法就是建立一个时间循环,在超时的时候通知脚本。不过,这也需要一个信号处理机制,在超时的时候来产生中断(参见Example 29.5)

Example 9.2 时间输入

#!/bin/bash
#timed-input.shTIMELIMIT=3PrintAnswer(){if [ "$answer" = TIMEOUT ];thenecho $answerelseecho "Your favorite veggie is $answer"kill $!   #$! 是运行在后台的最后一个工作的PIDfi
}TimeOn(){sleep $TIMELIMIT && kill -s 14 $$ &   #等待3秒,然后发送一个信号给脚本#$$是当前运行脚本的PID
}Int14Vector(){answer="TIMEOUT"PrintAnswerexit 14
}trap Int14Vector 14
#捕获的信号等于14时,执行Int14Vector函数#示例:trap "commands" signal-list
#意思为:接受到与signa-list清单中相同的信号时,执行""中的commandsecho "What is your favorite vegetable"
TimeOn
read answer
PrintAnswerexit 0

Example 9.3 再来一个时间输入

#!/bin/bash
#timeout.shINTERVAL=5   #timeout间隔timedout_read(){timeout=$1varname=$2old_tty_settings=`stty -g`stty -icanon min 0 time ${timeout}0#min n和-icanon配合使用,设置每次一完整读入的最小字符数为<N>read $varnamestty "$old_tty_settings"
}echo;echo -n "What's your name? Quick!"
timedout_read $INTERVAL your_nameechoif [ ! -z "$your_name" ];thenecho "Your name is $your_name"
elseecho "Timed out"
fiecho
exit 0

stty

stty 修改中断命令行的相关设置
语法: stty (选项) (参数)
选项:
-a : 以容易阅读的方式打印当前的所有配置
-g : 以stty可读方式打印当前的所有配置
实例:
在命令行下,禁止输出大写:

stty iuclc   #开启
stty -iuclc   #关闭

在命令行下禁止输出小写:

stty olcuc   #开启
stty -olcuc   #关闭

屏蔽显示

stty -echo   #禁止回显
stty echo   #打开回显

忽略回车符:

stty igncr   #开启
stty -igncr   #恢复

改变Ctrl+D的方法:
stty eof “string”

定时输入:

timeout_read()
{timeout=$1old_stty_settings=`stty -g`stty -icanon min 0 time 100eval read varnamestty "$old_stty_setting"
}

更简单的方法是:

read -t 10 varname

Example 9.4 Timed read

#!/bin/bash
#t-out.sh
TIMELIMIT=4read -p "input variable values:" -t $TIMELIMIT variable <&1   #Bash 1.x和Bash 2.x需要使用<&1,Bash 3.x不需要
echoif [ -z "$variable" ];thenecho "Time out,variable still unset"
elseecho "variable = $variable"
fi
exit 0

$UID 用户ID号

注意:变量$ENV,$LOGNAME,$MAIL,$TERM,$USER,$USERNAME并不是Bash的内建变量。它们经常被设置成环境变量,它们一般都放在Bash的安装文件中。
$SHELL:登录用户的shell名字,可能是从/etc/passwd设置的,也可能是在一个init脚本中设置的,同样,他也不是Bash的内建变量

位置参数

$0,$1,$2,…等

$# #命令行或者是位置参数的个数(见Example 33.2)

$* #所有的位置参数,被作为一个单词
注意:"$*"必须被引用

$@ #与$*同义,但是每个参数都是一个独立的"“引用字串,这就意味着参数被完整的传递
注意:”$@"必须被引用

Example 9.6 arglist:通过$*和$@列出所有的参数

#!/bin/bash
#arglist.sh
#多使用几个参数来调用这个脚本,比如one tow three
E_BADARGS=65if [ ! -n "$1" ];thenecho "Usage:`basename $0` argument1 argument2 etc"exit $E_BADATGS
fi
echoindex=1   #初始化数量
echo "Listing args with \"\$*\":"
for arg in "$*"   #如果"$*"不被引用,那么将不能正常工作
doecho "Arg #$index = $arg"let "index += 1"
done   #$*认为所有的参数为一个单词
echo "Entire arg list seen as single word"
echo
#Listing args with "$*":
#Arg #1 = one tow three
#Entire arg list seen as single wordindex=1   #重置数量
echo "Listing args with \"\$@\":"
for agr in "$@"
doecho "Arg #$index = $agr"let "index += 1"
done   #$@认为每个参数都是一个单词
echo "Arg list seen as separate words"
echo
#Listing args with "$@":
#Arg #1 = one
#Arg #2 = tow
#Arg #3 = three
#Arg list seen as separate wordsindex=1   #重置数量
echo "Listing args with \$*(unquoted):"
for arg in $*
doecho "Arg #$index = $arg"let "index += 1"
done   #未""引用的$*,把参数作为独立的单词
echo "Arg list seen as separate words"
#Listing args with $*(unquoted):
#Arg #1 = one
#Arg #2 = tow
#Arg #3 = three
#Arg list seen as separate wordsexit 0

在shift命令后边,$@将保存命令行中剩余的参数,而$1被丢掉了

#!/bin/bash
#使用./scriptname 1 2 3 4 5 来调用这个脚本
echo "$@"   #1 2 3 4 5
shift
echo "$@"   #2 3 4 5
shift
echo "$@"   #3 4 5#每个shift都丢弃$1,"$@"将包含剩下的参数
#$@也作为工具使用,用来过滤传给脚本的输入
#cat "$@"结构接受从stdin传来的输入,也接受从参数中指定的文件来输入

注意:$*和$@的参数有时会不一致,发生令人迷惑的行为,这依赖于$IFS的设置

Example 9.7 不一致的$*和$@行为(这个脚本看的人好乱)

#!/bin/bash
#
#"$*"和"$@"的古怪行为,依赖于它们是否被""引用。单词拆分和换行的不一致处理
echo
echo 'IFS 不变,使用"$*"'
c=0
for i in "$*"
doecho "$((c+=1)):[$i]"
done
echo "----------"echo 'IFS 不变,使用$*'
c=0
for i in $*
doecho "$((c+=1)):[$i]"
done
echo "----------"echo 'IFS 不变,使用"$@"'
c=0
for i in "$@"
doecho "$((c+=1)):[$i]"
done
echo "----------"echo 'IFS 不变,使用$@'
c=0
for i in $@
doecho "$((c+=1)):[$i]"
done
echo "----------"IFS=:
echo 'IFS=":",using "$*"'
c=0
for i in "$*"
doecho "$((c+=1)):[$i]"
done
echo "----------"echo 'IFS=":",using $*'
c=0
for i in $*
doecho "$((c+=1)):[$i]"
done
echo "----------"echo 'IFS=":",using "$@"'
c=0
for i in "$@"
doecho "$((c+=1)):[$i]"
done
echo "----------"echo 'IFS=":",using $@'
c=0
for i in $@
doecho "$((c+=1)):[$i]"
done
echo "----------"var=$*
echo 'IFS=":",using "$var"(var=$*)'
c=0
for i in "$var"
doecho "$((c+=1)):[$i]"
done
echo "----------"echo 'IFS=":",using $var(var=$*)'
c=0
for i in $var
doecho "$((c+=1)):[$i]"
done
echo "----------"var="$*"
echo 'IFS=":",using "$var"(var="$*")'
c=0
for i in $var
doecho "$((c+=1)):[$i]"
done
echo "----------"echo 'IFS=":",using $var(var="$*")'
c=0
for i in "$var"
doecho "$((c+=1)):[$i]"
done
echo "----------"var=$@
echo 'IFS=":",using "$var"(var=$@)'
c=0
for i in "$var"
doecho "$((c+=1)):[$i]"
done
echo "----------"echo 'IFS=":",using $var(var=$@)'
c=0
for i in $var
doecho "$((c+=1)):[$i]"
done
echo "----------"var="$@"
echo 'IFS=":",using "$var"(var="$@")'
c=0
for i in $var
doecho "$((c+=1)):[$i]"
done
echo "----------"echo 'IFS=":",using $var(var="$@")'
c=0
for i in "$var"
doecho "$((c+=1)):[$i]"
done
echo "----------"exit 0

注意:$@和$*中的参数只有在""中才会不同

Example 9.8 当$IFS为空时的$*和$@

#!/bin/bash
#
#如果$IFS被设置为空时,那么"$*"和"$@"将不会像期望的那样echo位置参数mecho(){echo "$1,$2,$3"
}
IFS=""
set a b cmecho "$*"
mecho $*mecho "$@"
mecho $@#当$IFS设置为空时,$*和$@的行为依赖于正在运行的Bash或者sh的版本,所以在脚本中使用这种"feature"不是明智的行为exit 0

其它的特殊参数

$- 传递给脚本的falg(使用set命令)。参考Example 11.15
注意:这起初是ksh的特征,后来引进到Bash中,但不幸的是,在Bash中它看上去也不能可靠的工作。
使用它的一个可能的方法就是让这个脚本进行自我测试(查看是否是交互的)

$! 在后台运行的最后一个PID

#!/bin/bash
#lastpid.sh
LOG=$0.log
TIMEOUT=5
COMMADN1="sleep 100"echo "Logging PIDs background commands for script:$0" >> "$LOG"
echo >> "$LOG"echo -n "PID of \"$COMMADN1\": " >> "$LOG"
${COMMAND1} &
echo $! >> "$LOG"possibly_hanging_job & { sleep ${TIMEOUT}; eval 'kill -9 $!' &> /dev/null; }
#(possibly_hanging_job)制造一个出错的程序
#强制结束一个错误的程序

$_ 保存之前执行的命令的最后一个参数

Example 9.9 下划线变量

#!/bin/bashecho $_   #/bin/bash
#只是调用/bin/bash来运行这个脚本du > /dev/null
echo $_   #duls -al > /dev/null
echo $_   #-al(最后的参数):
echo $_   #:

$? 命令,函数或脚本本身的退出状态(见Example 23.7)

$$ 脚本自身进程的ID,这个变量经常用来构造一个"unique"的临时文件名
(参考Example A.13,Example 29.6,Example 12.28,Example 11.25)

9.2 操作字符串

Bash支持超多的字符串操作,操作的种类和数量令人惊异。但不幸的是,这些工具缺乏集中性。一些是参数替换的子集,但另一些则属于UNIX的expr命令。这就导致了命令语法的不一致和功能的重叠,当然也会引起混乱

字符串长度

${#string}
expr length $string
expr "$string" : '.*'
stringZ=abcABC123ABCabc
echo ${#stringZ}   #15
echo `expr length $stringZ`   #15
echo `expr "$stringZ" : '.*'`   #15

Example 9.10 在一个文本文件的段间插入空行

#!/bin/bash
#paragraph-space.sh
#在一个不空的文本文件的段间插入空行
#用法:$0 < FILENAMEMINLEN=45
#假定行的长度小于$MINLEN指定的长度,$MINLEN中的值用来描述多少个字符结束一个段while read line
doecho "$line"len=${#line}if [ "$len" -lt "$MINLEN" ];thenecho   #在短行后边添加一个空行fi
doneexit 0

从字符串的开始位置匹配字符串长度
expr match “$string” ‘$substring’ #$substring是一个正则表达式
expr “$string” : ‘$substring’ #$substring是一个正则表达式

stringZ=abcABC123ABCabc
echo `expr match "$stringZ" 'abc[A-Z]*.2'`   #8(abcABC12)
echo `expr "$stringZ" : 'abc[A-Z]*.2'`   #8(abcABC12)

索引

expr index $string $substring #匹配到子串的第一个字符的位置

stringZ=abcABC123ABCabc
echo `expr index "$stringZ" C12`   #6(匹配的是C的位置)
echo `expr index "$stringZ" 1c`   #3(匹配的是c的位置)

提取子串

${string:position} #在string中从位置$position开始提取子串。
如果$string为*或@,那么将提取从位置$position开始的位置参数

${string:position:length} #在string中从位置$position开始提取$length长度的子串

stringZ=abcABC123ABCabcecho ${stringZ:0}   #abcABC123ABCabc
echo ${stringZ:1}   #bcABC123ABCabc
echo ${stringZ:7}   #23ABCabcecho ${stringZ:7:7}   #23A

反向提取子串

stringZ=abcABC123ABCabcecho ${stringZ:(-4)}   #Cabc
echo ${stringZ: -4}   #Cabc#注意:不加空格或(),默认提取完整的字符串
echo ${stringZ:-4}   #abcABC123ABCabc

如果$string为*或@,那么将最大的提取从位置$position开始的$length个位置参数

echo ${*:2}   #输出第2个和后面所有的位置参数
echo ${@:2}   #与上同义
echo ${*:2:3}   #从第2个开始,输出后边3个位置参数

expr suber $string $position $length #在string中从位置$position开始提取$length长度的子串

stringZ=abcABC123ABCabcecho `expr suber $stringZ 1 2`   #ab
echo `expr suber $stringZ 4 3`   #ABC

expr match “$string” '($substring)' #从$string的开始位置提取$substring,$substring是一个正则表达式

expr “$string” : '($substring)' #从$string的开始位置提取$substring, $substring是一个正则表达式

stringZ=abcABC123ABCabcecho `expr "$stringZ" : '\(.......\)'`   #abcABC1
echo `expr match "$stringZ" '\(.[b-c]*[A-Z]..[0-9]\)'`   #abcABC1
echo `expr "$stringZ" : '\(.[b-c]*[A-Z]..[0-9]\)'`   #abcABC1

示例:(便于理解这里的正则)

echo `expr match "$stringZ" '\(.[b-c]*\)'`   #abc
echo `expr match "$stringZ" '\(.[b-c]*[A-Z]\)'`   #abcA;[A-Z]表示:一个字符的匹配正则
echo `expr match "$stringZ" '\(.[b-c]*[A-Z].\)'`   #abcAB,在上边的基础上加一个任意字符
echo `expr match "$stringZ" '\(.[b-c]*[A-Z]..\)'`   #abcABC
echo `expr match "$stringZ" '\(.[b-c]*[A-Z]..[0-9]\)'`   #abcABC1

子串消除

${string#substring} #从$string的左边截掉第一个匹配的$substring

${string##substring} #从$string的左边截掉最后一个匹配的$substring

stringZ=abcABC123ABCabcecho ${stringZ#a*C}   #123ABCabc
#截掉a和C之间最近的匹配echo ${stringZ##a*C}   #abc
#截掉a和C之间最远的匹配

${string%substring} #从$string的右边截掉第一个匹配的$substring

${string%%substring} #从$string的右边截掉最后一个匹配的$substring

stringZ=abcABC123ABCabcecho ${stringZ%b*c}   #abcABC123ABCa
#从$stringZ的后边开始截掉b和c之间的最近匹配echo ${stringZ%%b*c}   #a
#从$stringZ的后边开始截掉b和c之间的最远匹配

Example 9.11 利用修改文件名,来转换图片格式

#!/bin/bash
#cvt.sh
#使用来自netpbm包的macptopbm程序,这个程序主要是由Brian Henderson来维护的
#netpbm是大多数Linux发行版的标准部分OPERATION=macptopbm
SUFFIX=pbm   #新的文件名后缀if [ -n "$1" ];thendirectory=$1   #如果目录名作为第1个参数给出
elsedirectory=$PWD   #否则使用当前目录
fi#假设在目标目录中的所有文件都是MacPaint格式的图片文件,以.mac为文件名后缀
for file in $directory/*
dofilename=${file%.*c}   #去掉.mac后缀$OPERATION $file > "$filename.$SUFFIX"   #转换为新文件名rm -f $file   #转换完成后删除原有文件echo "$filename.$SUFFIX"   #从stdout输出
doneexit 0

Example 9.12 模仿getopt命令

#!/bin/bash
#getopt-simple.sh
#用法:getopt-simple.sh /values=11 /values1=22getopt_simple(){echo "getopt_simple()"echo "参数是 '$*'"until [ -z "$1" ]doecho "加工参数: '$1'"if [ ${1:0:1} = '/' ];then   #判断开头是否为'/'tmp=${1:1}   #去掉'/'parameter=${tmp%%=*}   #从右往左,去掉最后一个=后面的所有值(获取第一个=前面的值)value=${tmp##*=}   #从左往右,去掉最后一个=前面的所有值(获取最后一个=后面的值)echo "参数:'$parameter',值:'$value'"eval $parameter=$valuefishiftdone
}getopt_simple $*   #传递所有参数到getopt_simple()exit 0

子串替换

${string/substring/replacement} #使用$replacement来替换第一个匹配的$substring

${string//substring/replacement} #使用$replacement来替换所有匹配的$substring

stringZ=abcABC123ABCabcecho ${stringZ/abc/xyz}   #xyzABC123ABCabc
echo ${stringZ//zbc/xyz}   #xyzABC123ABCxyz

${string/#substrion/replacement} #如果$substring匹配$string的开头部分,那么就用$replacement来替换$substring

${string/%substring/replacement} #如果$substring匹配$string的结尾部分,那么就用$replacement来替换$substring

stringZ=abcABC123ABCabcecho ${stringZ/#abc/XYZ}   #XYZABC123ABCabc
echo ${stringZ/%abc/XYZ}   #abcABC123ABCXYZ

9.2.1 使用awk来操作字符串

Example 9.13 提取字符串的一种可选方法

#!/bin/bash
#substring-extraction.shString=23skidoo1
#注意:对于awk和Bash来说,它们使用的是不同的string索引系统:
#Bash的第一个字符是从0开始记录的
#Awk的第一个字符是从1开始记录的echo ${String:2:4}   #skid#awk中等价于${string:pos:length}的命令是substr(string,pos,length)
echo |awk '{print substr("'"${String}"'",3,4)}'   #skid
#使用一个空的echo通过管道给awk一个假的输入,这样可以不用提供一个文件名exit 0

9.2.2 更深的讨论

关于在脚本中使用字符串更深的讨论,请参考9.3节,h和expr命令列表的相关章节。
关于脚本的例子,见:
Example 12-9
Example 9-16
Example 9-17
Example 9-18
Example 9-20

9.3 参数替换

操作和扩展变量

${parameter} #与$parameter相同,就是parameter的值在特定的上下文中,只有少部分会产生${parameter}的混淆。可以组合起来一起赋值给字符串变量

your_id=${USER}-on-${HOSTNAME}
echo "$your_id"echo "Old \$PATH = $PATH"
PATH=${PATH}:/opt/bin
echo "New \$PATH = $PATH"

${parameter-default},${parameter:-default} #如果parameter没有被set,那么就使用default

echo ${username-`whoami`}

注意:${parameter-default}和${parameter:-default}大部分时候是相同的。额外的":"在parameter被声明的时候(而且被赋空值),会有一些不同

#!/bin/bash
#param-sub.sh
#一个变量是否被声明,将影响默认选项的触发,甚至于这个变量被设为空username0=
echo "username0 已声明,但设置为null"
echo "username0 = ${username0-`whoami`}"   #username0 =
echo
echo "username1 未声明"
echo "username1 = ${username-`whoami`}"   #username1 = root
echo
username2=
echo "username2 已声明,但设置为null"
echo "username2 = ${username2:-`whoami`}"   #username0 = root#再来一个
variable=
echo "${variable-0}"   #空
echo "${variable:-1}"   #1
unset variable
echo "${variable-2}"   #2
echo "${variable:-3}"   #3exit 0#如果脚本中并没有传入命令行参数,那么default parameter将被使用
DEFAULT_FILENAME=generic.data
filename=\${1:-$DEFAULT_FILENAME}

另外参见Example 3-4,Example 28-2,和 Example A-6。与"使用一个与列表来支持一个默认的命令行参数"的方法相比较。

${parameter=default},${parameter:=default} #如果parameter没有被set,那么就设置为default
这两种办法绝大多数时候用法都一样,只有在$parameter被声明并设置为空的时候,才会有区别,和上边的行为一样

echo ${username=`whoami`}

${parameter+alt_value},${parameter:+alt_value} #如果parameter被set,那么就使用alt_value,否则就是用null字符串
这两种办法绝大多数时候用法都一样,只有在$parameter被声明并设置为空的时候,才会有区别,如下:

a=${param1+xyz}   #a=
param2=
a=${param2+xyz}   #a=xyz
param3=123
a=${param3+xyz}   #a=xyza=${param4:+xyz}   #a=
param5=
a=${param5:+xyz}   #a=
param6=123
a=${param6:+xyz}   #a=xyz

${parameter?err_msg},${parameter:?err_msg} #如果parameter被set,那么就使用set值,否则就print err_msg
这两种办法绝大多数时候用法都一样,只有在$parameter被声明并设置为空的时候,才会有区别,和上边的行为一样

Example 9.14 使用参数替换和errot messages

#!/bin/bash
#
#检查一些系统的环境变量。这是个好习惯
#比如,如果$USER(在console上的用户名)没有被set,那么系统就不会认你。#${variablename?}结果可以用来在一个脚本中检查变量是否被set
: ${HOSTNAME?}${USER?}${HOME?}${MAIL?} &> /dev/null
echo
echo "Name of the machine is $HOSTNAME"
echo "You are $USER"
echo "Your home directory is $HOME"
echo "Your main INBOX is located in $MAIL"
echo
echo "If you are reading this message"
echo "critical environmental variables have been set"
echo ThisVariable=Value-of-ThisVariable
: ${ThisVariable?} &> /dev/null
echo "Value of ThisVariable is $ThisVariable"
echo: ${ZZXy23AB?"ZZXy23AB has not been set"} &> /dev/null
#如果ZZXy23AB没有被set,那么这个脚本将会在这里终止,并提示如下错误
#$0: line 23: ZZXy23AB: ZZXy23AB has not been set#你可以指定错误消息
#: ${variablename?"ERROR MESSAGE"}
#同样的结果
#dummy_variable=${ZZXy23AB?}
#dummy_variable=${ZZXy23AB?"ZZXy23AB has not been set"}#同set -u命令来比较这些检查变量是否被set的方法
echo "You will not see this message,because script already terminated"
HERE=0
exit $HERE
#事实上这个脚本将返回值1作为退出状态

Example 9.15 参数替换和"usage"messages

#!/bin/bash
#usage-message.sh: ${1?"Usage:$0 ARGUMENT"}
#如果没有命令行参数,那么脚本将在此处退出。并且打出如下错误消息
#usage-message.sh:1:Usage:usage-message.sh ARGUMENTecho "These two lines echo only if command-line parameter given"
echo "command line parameter = \"$1\""exit 0   #如果有命令行参数,将在此退出

参数替换和扩展

下边的表达式是使用expr字符串匹配操作的补充(见Example 12.9)
这些特定的使用方法绝大多数情况下都是用来分析文件目录名

变量长度/子串删除

${#var} #字符串长度($var的字符串数量)。
对于一个数组,${#array}是数组中第一个元素的长度。

一些例外:
${#}和${#@}将给出位置参数的个数
对于数组来说${#array[
]}和${$#array[@]}将给出数组元素的个数

Example 9.16 变量长度

#!/bin/bash
#length.shE_NO_ARGS=65if [ $# -eq 0 ];thenecho "请使用一个或多个命令行参数调用此脚本"exit $E_NO_ARGS
fivar01=abcdEFGH28ij
echo "var01 = ${var01}"   #var01 = abcdEFGH28ij
echo "Length of var01 = ${#var01}"   #Length of var01 = 12
#现在,让我们试试在里面嵌入一个空格
var02="abcd EFGH28ij"
echo "var02 = ${var02}"   #var02 = abcd EFGH28ij
echo "Length of var02 = ${#var02}"   #Length of var02 = 13echo "传递给脚本的命令行参数数 = ${#@}"
echo "传递给脚本的命令行参数数 = ${#*}"exit 0

${var#Pattern},${var##Pattern} #从$var开头删除最近或最远匹配$Pattern的子串

来自Example A.7例子的一部分

#来自"days-between.sh"例子的一个函数
#去掉传递进来的参数开头的0strip_leading_zero (){return=${1#0}
}

下边是曼弗雷德·施瓦布的对上边函数的一个改版

strip_leading_zero2(){   #去掉开头的0,因为如果不去的话,Bash将会把这个值作为8进制解释shopt -s extglob   #打开扩展globbinglocal val=${1##=+(0)}   #使用局部变量,匹配最长的连续的0shopt -u extglob   #关闭扩展globbing_strip_leading_zero2=${val:-0}   #如果输入0,那么返回0来代替""
}

shopt命令

shopt命令是set命令的一种替代,很多方面都和set命令一样,但它增加了很多选项。
可以使用

-p选项来查看shopt选项的设置-u表示关闭一个选项-s表示开启一个选项

以下是shopt命令的选择介绍:

cdable_vars   如果给cd内置命令的参数不是一个目录,就假设它是一个变量名,变量的值是将要转换到的目录
cdspell   纠正cd命令中目录名的较小拼写错误.检查的错误包括颠倒顺序的字符,遗漏的字符以及重复的字符.如果找到一处需修改之处,正确的路径将打印出,命令将继续.只用于交互式shell
checkhash   bash在试图执行一个命令前,先在哈希表中寻找,以确定命令是否存在.如果命令不存在,就执行正常的路径搜索
checkwinsize   bash在每个命令后检查窗口大小,如果有必要,就更新LINES和COLUMNS的值
cmdhist   bash试图将一个多行命令的所有行保存在同一个历史项中.这是的多行命令的重新编辑更方便
dotglob   Bash在文件名扩展的结果中包括以点(.)开头的文件名
execfail   如果一个非交互式shell不能执行指定给exec内置命令作为参数的文件,它不会退出.如果exec失败,一个交互式shell不会退出
expand_aliases   别名被扩展.缺省为打开
extglob   打开扩展的模式匹配特性(正常的表达式元字符来自Korn shell的文件名扩展)
histappend   如果readline正被使用,用户有机会重新编辑一个失败的历史替换
histverify   如果设置,且readline正被使用,历史替换的结果不会立即传递给shell解释器.而是将结果行装入readline编辑缓冲区中,允许进一步修改
hostcomplete   如果设置,且readline正被使用,当正在完成一个包含@的词时bash将试图执行主机名补全.缺省为打开
huponexit   如果设置,当一个交互式登录shell退出时,bash将发送一个SIGHUP(挂起信号)给所有的作业
interactive_comments   在一个交互式shell中.允许以#开头的词以及同一行中其他的字符被忽略.缺省为打开
lithist   如果打开,且cmdhist选项也打开,多行命令讲用嵌入的换行符保存到历史中,而无需在可能的地方用分号来分隔
mailwarn   如果设置,且bash用来检查邮件的文件自从上次检查后已经被访问,将显示消息”The mail in mailfile has been read”
nocaseglob   如果设置,当执行文件名扩展时,bash在不区分大小写的方式下匹配文件名
nullglob   如果设置,bash允许没有匹配任何文件的文件名模式扩展成一个空串,而不是他们本身
promptvars   如果设置,提示串在被扩展后再进行变量和参量扩展.缺省为打开
restricted_shell   如果shell在受限模式下启动就设置这个选项.该值不能被改变.当执行启动文件时不能复位该选项,允许启动文件发现shell是否受限
shift_verbose   如果该选项设置,当移动计数超出位置参量个数时,shift内置命令将打印一个错误消息
sourcepath   如果设置,source内置命令使用PATH的值来寻找作为参数提供的文件的目录.缺省为打开
source   点(.)的同义词

实例:
查看extglob选项是否开启(默认是off)

[root@master load_data]# shopt extglob
extglob         off

开启 extglob 选项:

shopt -s extglob

开启 extglob 选项:

shopt -u extglob

开启之后,以下5个模式匹配操作符将被识别:

?(pattern-list) - 所给模式匹配0次或1次;
*(pattern-list) - 所给模式匹配0次以上包括0次;
+(pattern-list) - 所给模式匹配1次以上包括1次;
@(pattern-list) - 所给模式仅仅匹配1次;
!(pattern-list) - 不匹配括号内的所给模式。

实例:
删除文件名不以jpg结尾的文件:

rm -rf !(*jpg)

删除文件名以jpg或png结尾的文件:

rm -rf *@(jpg|png)

另一个例子

echo `basename $PWD`   #当前工作目录的basename
echo "${PWD##*/}"   #当前工作目录的basename
echo
echo `basename $0`   #脚本名字
echo $0   #脚本名字
echo "${0##*/}"   #脚本名字
echo
filename=test.data
echo "${filename##*.}"   #data

${var%Pattern},${var%%Pattern} #从$var结尾删除最近或最远匹配$Pattern的子串

Example 9.17 参数替换中的模式匹配

#!/bin/bash
#patt-matching.sh
#使用# ## % %%来进行参数替换操作的模式匹配var1=abcd12345abc6789
pattern1=a*c
echo
echo "var1 = $var1"
echo "var1 = ${var1}"echo "Number of characters in ${var1} = ${#var1}"
echo
echo "pattern1 = $pattern1"
echo "----------"
echo '${var1#$pattern1} ='"${var1#$pattern1}"   #d12345abc6789
#最短的匹配,去掉$var的前3个字符
echo '${var1##$pattern1} ='"${var1##$pattern1}"   #6789
#最远的匹配,去掉$var的前12个字符
echo;echopattren2=b*9
echo "var1 = $var1"
echo
echo "pattern2 = $pattern2"
echo "----------"
echo '${var%pattern2} ='"${var%pattern2}"   #abcd12345a
echo '${var%%pattern2} ='"${var%%pattern2}"   #a#记住:#和## 从字符串的左边开始,并且去掉左边的字符串
#      %和%% 从字符串的右边开始,并且去掉右边的字符串
echo
exit 0

Example 9.18 重命名文件扩展名

#!/bin/bash
#rfe.sh
#用法:rfs old_extension new_extension
#例子:
#将制定目录的所有*.gif文件都重命名为*.jpg
#用法:rfs gif jpgE_BADARGS=65case $# in0|1)echo "Usage:`basename $0` old_file_suffix new_file_suffix"exit $E_BADARGS;;
esacfor filename in *.$1
domv $filename ${filename%$1}$2   #从筛选出的文件中先去掉以第一个参数结尾的扩展名,#然后作为扩展名把第2个参数添加上
doneexit 0

变量扩展/子串替换

这些结构都是从ksh中吸收来的
${var:pos} #变量var从位置pos开始扩展

${var:pos:len} #从位置pos开始,并扩展len长度个字符。见Example A.14(这个例子里有这种操作的一个创造性用法)

${var/Pattern/Replacement} #使用Replacement来替换var中的第一个Pattern的匹配

${var//Pattern/Replacement} #全局替换。在var中所有的匹配,都会用Replacement来替换

如果Replacement被忽略的话,那么所有匹配到的Pattern都会被删除

Example 9.19 使用模式匹配来分析比较特殊的字符串

#!/bin/bash
#
var1=abcd-1234-defg
echo "var1 = $var1"t=${var1#*-*}   #1234-defg
echo "var1 (所有,直至并包括第一次剥离) = $t"
#t=${var1#*-} 在这个例子中作用是一样的,因为#匹配这个最近的字符串,并且*匹配前边的任何字符串,包括一个空字符t=${var1##*-*}   #空
echo "如果var1包含\"-\",则返回空字符串... var1 = $t"
echo
t=${var1%*-*}   #abcd-1234
echo "var1 (所有的东西都被剥离) = $t"
echopath_name=/home/bozo/ideas/thoughts.for.today
echo "path_name = $path_name"
t=${path_name##/*/}   #thoughts.for.today
echo "path_name,去掉前缀 = $t"
#在这个特定的例子中,与t=`basename $path_name`的作用一致
#t=${path_name%/};t=${t##*/} 是一个更一般的解决办法,但有时还是不行
#如果$path_name以一个新行结束,那么`basename $path_name`将不能工作,但是上边这个表达式可以t=${path_name%/*.*}   #/home/bozo/ideas
#与t=`dirname $path_name`效果相同
echo "path_name,去掉后缀 = $t"
#在某些情况下将失效,比如:"../","/foo",#"foo/","/"
#删除后缀,尤其是在basename没有后缀的时候,但是dirname还是会使问题复杂化
echot=${path_name:11}   #ideas/thoughts.for.today
echo "$path_name,去掉前11个字符 = $t"
t=${path_name:11:5}   #ideas
echo "$path_name,去掉前11个字符,长度5 = $t"
echot=${path_name/bozo/clown}   #/home/clown/ideas/thoughts.for.today
echo "$path_name with \"bozo\" 替换为 \"clown\" = $t"t=${path_name/today/}   #/home/bozo/ideas/thoughts.for.
echo "$path_name with \"tiday\" deleted = $t"t=${path_name//o/O}   #/hOme/bOzO/ideas/thOughts.fOr.tOday
echo "$path_name with all o's 大写 = $t"t=${path_name//o/}   #/hme/bz/ideas/thughts.fr.tday
echo "$path_name with all o's deleted = $t"exit 0

${var/#Pattern/Replacement} #如果var的前缀匹配到了Pattern,那么就用Replacement来替换Pattern

${var/%Pattern/Replacement} #如果var的后缀匹配到了Pattern,那么就用Replacement来替换Pattern

Example 9.20 对字符串的前缀或后缀使用匹配模式

#!/bin/bash
#var-match.sh
#对字符串的前后缀使用匹配替换的一个样本v0=abc1234zip1234abc
echo "v0 = $v0"
echo#匹配字符串的前缀
v1=${v0/#abc/ABCDEF}
echo "v1 = $v1"   #v1 = ABCDEF1234zip1234abc#匹配字符串的后缀
v2=${v0/%abc/ABCDEF}
echo "v2 = $v2"   #v2 = abc1234zip1234ABCDEF
echo#  ----------------------
#  必须在开头或者结尾匹配,
#   否则将不会产生替换结果
#  ----------------------
v3=${v0/#123/000}
echo "v3 = $v3"   #v3 = abc1234zip1234abcv4=${v0/%123/000}
echo "v4 = $v4"   #v4 = abc1234zip1234abcexit 0

${!varprefix},${!varprefix@}* #使用变量的前缀来匹配前边所有声明过的变量

xyz23=whatever
xyz24=a=${!xyz*}   #以xyz作为前缀,匹配所有前边声明过的变量
echo "a = $a"   #a = xyz23 xyz24a=${!xyz@}   #同上
echo "a = $a"   #同上

9.4 指定类型的变量:declare或者typeset

declare或者typeset内建命令(这两个命令是完全一样的)允许指定变量的具体类型。在某些特定的语言中,这是一种指定类型的很弱的形式。declare命令是在Bash版本2或之后的版本才被加入的。typeset命令也可以工作在ksh脚本中

declare/typeset选项
-r 只读

declare -r var1

(declare -r var1与readonly var1是完全一样的),这和C语言中的const关键字一样,都是强制指定只读。如果你尝试修改一个只读变量的值,那么你将得到一个错误信息。

-i 整型

declare -i number
#这个脚本将把变量number后边的赋值视为一个整型number=3
echo "number = $number"   #number = 3number=three
echo "number = $number"   #number = 0

如果把一个变量指定为整形,那么即使没有expr和let命令,也允许使用特定的算术运算

n=6/3
echo "n = $n"   #n = 6/3declare -i n
n=6/3
echo "n = $n"   #n = 2

-a 数组

declare -a indices

变量indices将被视为数组

-f 函数

declare -f

如果使用declare -f而不带参数的话,将会列出这个脚本中之前定义的所有函数

declare -f function_name

如果使用declare -f function_name这种形式的话,将会只列出这个函数的名字。

-x export

declare -x var3

这种使用方式,将会把var3 export出来

Example 9.21 使用declare来指定变量的类型

#!/bin/bash
#
func1 (){echo This is a function
}declare -f    #列出之前所有的函数
echo
declare -i var1   #var1是个整型
var1=1234
echo "var1 declared as $var1"
var1=var1+1   #变量声明不需要使用let
echo "var1 incremented by 1 is $var1"
#尝试将变量修改为整形
echo "试图将var1更改为浮点值"
var1=123.4   #结果将是一个错误消息,并且变量并没有被修改
echo "var1 is still $var1"
echodeclare -r var2=12.34   #declare允许设置变量的属性,并且同时分配变量的值
echo "var2 declared as $var2"   #尝试修改只读变量
var2=56.78   #产生一个错误消息,并且从脚本退出echo "var2 is still $var2"   #这行将不会被执行
exit 0   #脚本将不会从此退出

注意:使用declare内建命令将会限制变量的作用域

foo (){F00="bar"
}
bar(){fooecho $FOO
}
bar   #输出bar

然而。。。

foo (){declare FOO="bar"
}
bar(){fooecho $FOO
}
bar   #输出空

9.5 变量的间接引用

假设一个变量的值是另一个变量的名字。我们有可能从第1个变量中取得第2个变量的值吗?
比如:如果a=letter_of_alphabet接着letter_of_alphabet=z,那么我们能从a中得到z吗?
答案是:当然可以,并且这被称为间接引用。它使用一个不常用的符号

eval var1=\\$$var2

Example 9.22 间接引用

#!/bin/bash
#ind-ref.sh: 间接变量引用
#存取一个变量的值的值a=letter_of_alphabet
letter_of_alphabet=z
echo
#直接引用
echo "a = $a"   #a = letter_of_alphabet#间接引用
eval a=\$$a
echo "Now a = $a"   #Now a = z
echo#现在让我们试试修改di2个引用的值
t=table_cell_3
table_cell_3=24
echo "\"table_cell_3\" = $table_cell_3"   #"table_cell_3" = 24
echo -n "dereferenced \"t\" = ";eval echo \$$t   #dereferenced "t" = 24
echot=table_cell_3
NEW_VAL=123
table_cell_3=$NEW_VAL
echo "Changing value of \"table_cell_3\" to $NEW_VAL"   #Changing value of "table_cell_3" to 123
echo "\"table_cell_3\" now $table_cell_3"   #"table_cell_3" now 123
echo -n "dereferenced \"t\" now ";eval echo \$$t   #dereferenced "t" now 123
#eval将获得两个参数echo和\$$t(与$table_cell_3等价)
echoexit 0

间接引用到底有什么应用价值?他给Bash添加了一种类似C语言指针的功能,在Example 34.3中有例子。并且还有一些其它的有趣的应用

尼尔斯·拉特克展示了如何建立一个动态变量名字并且取出其中的值。当sourcing(包含)配置文件时,这很有用

#!/bin/bash
#
#---------------------------
#这部分内容可能来自于单独的文件.
isdnMyProviderRemoteNet=172.16.0.100
isdnYourProviderRemoteNet=10.0.0.10
isdnOnlineService="MyProvider"
#---------------------------remoteNet=$(eval "echo \$$(echo isdn${isdnOnlineService}RemoteNet)")
echo $remoteNet   #172.16.0.100
remoteNet=$(eval "echo \$$(echo isdnMyProviderRemoteNet)")
echo $remoteNet   #172.16.0.100
remoteNet=$(eval "echo \$isdnMyProviderRemoteNet")
echo $remoteNet   #172.16.0.100
remoteNet=$(eval "echo $isdnMyProviderRemoteNet")
echo $remoteNet   #172.16.0.100

Example 9.23 传递一个间接引用给awk

#!/bin/bash
#
ARGS=2
E_WRONGARGS=65if [ $# -ne "$ARGS" ];thenecho "Usage: `basename $0` filename number"exit $E_WRONGARGS
fifilename=$1
number=$2#-----awk脚本开始
awk "
{total += \$${number}
}
END
{print total
}
" "$filename"
#-----awk脚本结束exit 0
#经测试,此awk的命令是将$filename文件中每行中$number列的数字累加。

注意:Bash并不支持指针的算术运算,并且这严格的限制了间接引用的使用。事实上,在脚本语言中,间接引用本来就是丑陋的部分

9.6 $RANDOM 产生随机数

$RANDOM是Bash的内部函数(并不是常量),这个函数将返回一个范围在0-32767之间的一个伪随机数。它不应该被用来产生秘钥

Example 9.24 产生随机数

#!/bin/bash
#
#$RANDOM在每次调用的时候,返回一个不同的随机数
#指定的范围是:0-32767(有符号的16-bit整数)MAXCOUNT=10
count=1
echo
echo "$MAXCOUNT random numbers:"
echo "----------"
while [ "$count" -le $MAXCOUNT ]
donumber=$RANDOMecho $numberlet "count += 1"
done
echo "----------"#如果你需要在一个特定范围内产生一个随机数int,那么使用modulo(模)操作。
#这将返回一个除法操作的余数RANGE=500echonumber=$RANDOMlet "number %= $RANGE"echo "Random number less than $RANGE --- $number"echo#如果你需要产生一个比你指定的最小边界大的随机数
#那么建立一个test循环,来丢弃所有产生对比这个数小的随机数FLOOR=200
number=0
while [ "$number" -le $FLOOR ]
donumber=$RANDOM
done
echo "Random number greater than $FLOOR --- $number"
echo#结合上边两个例子的技术,来达到获得在指定的上下线之间来产生随机数
number=0
while [ "$number" -le $FLOOR ]
donumber=$RANDOMlet "number %= $RANGE"
done
echo "Random number between $FLOOR and $RANGE --- $number"
echo#产生一个二元选择,就是true和false两个值
BINARY=2
T=1
number=$RANDOM
let "number %= $BINARY"
if [ "$number" -eq $T ];thenecho "true"
elseecho "false"
fiecho#掷骰子
SPOTS=6
die1=0
die2=0
#是否让SPOTS=7?比加1更好呢?
#答案是:不能让SPOTS=7。因为这样的话会有0,骰子没有0
let "die1 = $RANDOM % $SPOTS +1"
let "die2 = $RANDOM % $SPOTS +1"
#上边的算术操作是先取余,在加1
let "throw = $die1 + $die2"
echo "Throw of the dice = $throw"
echoexit 0

Example 9.25 从一副扑克牌中取出一张随机牌

#!/bin/bash
#pic-card.sh
#这是一个从数组中取出随机元素的一个例子Suites="梅花
方块
红桃
黑桃"
Denominations="2
3
4
5
6
7
8
9
10
J
Q
K
A"
#注意变量的多行展开suite=($Suites)   #读到数组变量中
denomination=($Denominations)num_suites=${#suite[*]}   #计算有多少个元素
num_denominations=${#denomination[*]}echo -n "${suite[$((RANDOM%num_suites))]}"
echo "${denomination[$((RANDOM%num_denominations))]}"exit 0

Jipe展示了一系列的在一定范围中产生随机数的方法

#在6到30之间产生随机数
number=$(RANDOM%25+6)#还是产生6-30之间的随机数,但是这个数字必须被3整除
number=$(((RANDOM%30/3+1)*3))
#$RANDOM%30=0-29;/3取商=0-9;+1=1-10;*3肯定能被3整除
#注意:这可能不会在所有时候都能正常地运行。#Frank Wang建议用下边的方法来取代
number=$((RANDOM%27/3*3+6))

比尔·格拉德沃提出了一个重要的规则来产生正数(经测试,貌似有问题)

rnumber=$(((RANDOM%(max-min+divisibleBy))/divisibleBy*divisibleBy+min))

这里Bill给出了一个通用函数,这个函数返回一个在两个指定值之间的随机数

Example 9.26 两个指定值之间的随机数(这个脚本后边没看懂,放弃了)

#!/bin/bash
#random-between.shrandomBetween(){#在$max和$min之间产生一个正或负的随机数,并且可被$divisibleBy整除syntax(){   #在函数中内嵌函数echoecho "Syntax:randomBetween [min] [max] [multiple]"echoecho "最多需要3个参数,但所有参数都是完全可选的"echo "min 是最小值"echo "max 是最大值"echo "multiple 指定的答案必须是此值的倍数"echoecho "如果缺少任何值,则提供的默认区域为:0 32767 1"echo "成功完成返回0,不成功完成返回函数语法和1"echo "答案在全局变量randomBetweeAnswer中返回"echo "正确处理任何传递参数的负值"}#分配默认值,用来处理没有参数传递进来的时候local min=${1:-0}local max=${2:-32767}local divisibleBy=${3:-1}local xlocal spread#确认divisibleBy是正值[ ${divisibleBy} -lt 0 ] && divisibleBy=$((0-divisibleBy))#完整性检查
#    if [ $# -gt 3 -o ${dicisibleBy} -eq 0 -o ${min} -eq ${max} ];then
#      syntax
#      return 1
#    fi#上边的完整性检查(报错:参数太多)if [ $# -gt 3  ];thenif [ ${min} -eq ${max}  ];thenif [ ${dicisibleBy} -eq 0 ];thensyntaxreturn 1fi  fifi#观察是否min和max颠倒了if [ ${min} -gt ${max} ];then#交换它们x=${min}min=${max}max=${x}fi#如果min自己并不能够被$divisibleBy整除,那么就调整min的值,#+ 使其能够被$divisibleBy整除,前提是不能放大范围。if [ $((min/divisibleBy*divisibleBy)) -ne ${min} ];thenif [ ${min} -lt 0 ];thenmin=$((min/divisibleBy*divisibleBy))elsemin=$((((min/divisibleBy)+1)*divisibleBy))fifi#如果max自己并不能够被$divisibleBy整除,那么就调整max的值,#+ 使其能够被$divisibleBy整除,前提是不能放大范围。if [ $((min/divisibleBy*divisibleBy)) -ne ${min} ];thenif [ ${min} -lt 0 ];thenmin=$((min/divisibleBy*divisibleBy))elsemin=$((((min/divisibleBy)+1)*divisibleBy))fifi#--------------------------------------#现在,来做真正的工作#注意,为了得到对于断电来说合适的分配,随机值得范围不得不在0和abs(max-min)+divisibleBy之间,而不是abs(max-min)+1#对于端点来说,这个少了的增加将会产生合适的分配#修改这个公式,使用abs(max-min)+1来代替abs(max-min)+divisibleBy的话,#+ 也能够产生正确的答案,但是在这种情况下生成的随机值对于正好为端点倍数的#+ 这种情况来说并不完美,因为在正好为端点倍数的情况下的随机率比较低,#+ 因为你在+1而已,这比正常地公式所产生的的机率要小的多(正常为加divisibleBy)#--------------------------------------spread=$((max-min))[ ${spread} -lt 0 ] && spread=$((0-spread))let spread+=divisibleByrandomBetweenAnswer=$(((RANDOM%spread)/divisibleBy*divisibleBy+min))return 0#然而保罗·马塞尔·科埃略·阿拉高指出,当$max和$min不能被$divisibleBy整除时#+ 这个公式将会失败#他建议使用如下公式:#+ rnumber=$(((RANDOM%(max-min+1)+min)/divisibleBy*divisibleBy))
}#让我们来测试一下这个函数
min=-14
max=20
divisibleBy=3
#产生一个数组answers,answers的下标用来表示在范围内可能出现的值,
#+ 而内容记录的是对于这个值出现的次数,如果我们循环自购多次,
#+ 一定会得到一次出现机会
declare -a answer
minimum=${min}
maximum=${max}
if [ $((minimum/divisibleBy)) -ne ${minimum} ];thenif [ ${minimum} -lt 0 ];thenminimum=$((minimum/divisibleBy*divisibleBy))elseminimum=$((((minimum/divisibleBy)+1)*divisibleBy))fi
fi#如果maximum自己并不能够被$divisibleBy整除,
#+ 那么就调整 maximum 的值,使其能够被$divisibleBy 整除,前提是不能放大范围.
if [ $((maximum/divisibleBy*divisibleBy)) -ne ${maximum} ]; thenif [ ${maximum} -lt 0 ]; thenmaximum=$((((maximum/divisibleBy)-1)*divisibleBy))elsemaximum=$((maximum/divisibleBy*divisibleBy))fi
fi # 我们需要产生一个下标全为正的数组,
#+ 所以我们需要一个 displacement 来保正都为正的结果.displacement=$((0-minimum))
for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); doanswer[i+displacement]=0
done# 现在我们循环足够多的次数来得到我们想要的答案.
loopIt=1000 # 脚本作者建议 100000,但是这实在是需要太长的时间了.for ((i=0; i<${loopIt}; ++i)); do
# 注意,我们在这里调用 randomBetween 函数时,故意将 min 和 max 颠倒顺序
#+ 我们是为了测试在这种情况下,此函数是否还能得到正确的结果.randomBetween ${max} ${min} ${divisibleBy}# 如果答案不是我们所预期的,那么就报告一个错误.[ ${randomBetweenAnswer} -lt ${min} -o ${randomBetweenAnswer} -gt ${max} ] && echo MIN or MAX error - ${randomBetweenAnswer}![ $((randomBetweenAnswer%${divisibleBy})) -ne 0 ] && echo DIVISIBLE BY error - ${randomBetweenAnswer}!# 将统计值存到 answer 之中.answer[randomBetweenAnswer+displacement]=$((answer[randomBetweenAnswer+displacement]+1))
done # 让我们察看一下结果
for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); do[ ${answer[i+displacement]} -eq 0 ] && echo "We never got an answer of $i." || echo "${i} occurred ${answer[i+displacement]} times."
doneexit 0

$RANDOM到底有多随机?最好的办法就是写个脚本来测试一下。跟踪随机数的分配情况。让我们用随机数摇一个骰子

Example 9.27 使用随机数来摇一个骰子

#!/bin/bash
RANDOM=$$   #使用脚本的进程ID来作为随机数的产生种子(也可以不设置)PIPS=6   #一个骰子有6面
MAXTHROWS=600   #如果你没别的事干,可以增加这个数值
throw=0   #抛骰子的次数ones=0
twos=0
threes=0
fours=0
fives=0
sixes=0print_result(){echoecho "noes = $ones"echo "twos = $twos"echo "threes = $threes"echo "fours = $fours"echo "fives = $fives"echo "sixes = $sixes"echo
}update_count(){case "$1" in0) let "ones += 1";;1) let "twos += 1";;2) let "threes += 1";;3) let "fours += 1";;4) let "fives += 1";;5) let "sixes += 1";;esac
}
echowhile [ "$throw" -lt "$MAXTHROWS" ]
dolet "die1 = RANDOM % $PIPS"update_count $die1let "throw += 1"
doneprint_resultexit 0

一个练习 抛1000次硬币的形式

#!/bin/bash
PIPS=2   #一枚硬币有2面
MAXTHROWS=1000   #总1000次
throw=0   #抛硬币的次数front=0
back=0print_result(){echoecho "front = $front"echo "back = $back"echo
}update_count(){case "$1" in0) let "front += 1";;1) let "back += 1";;esac
}
echowhile [ "$throw" -lt "$MAXTHROWS" ]
dolet "die1 = RANDOM % $PIPS"update_count $die1let "throw += 1"
doneprint_resultexit 0

像我们在上边的例子中看到的,最好在每次随机数产生时都是用新的种子。因为如果使用同样的种子的话,那么随机数将产生相同的序列。(C中random()函数也会有这样的行为)

Example 9.28 重新分配随机数种子

#!/bin/bash
#seeding-random.shMAXCOUNT=25random_numbers(){count=0while [ "$count" -lt "$MAXCOUNT" ];thendonumber=$RANDOMecho -n "$number"let "count += 1"done
}
echo;echo
RANDOM=1   #为随机数的产生设置RANDOM种子
random_numbersecho;echoRANDOM=1   #设置同样的种子,将会和上边产生的随机数列相同#+ 复制一个相同的随机数序列在什么时候有用呢?
random_numbersecho;echoRANDOM=2
random_numbersecho;echo#一个有想象力的方法。。。
SEED=$(head -1 /dev/urandom | od -N 1 | awk '{print $2}')
#首先从/dev/urandom(系统伪随机设备文件)中取出1行,
#+ 然后这个可以打印行转换为(8进制)数,通过使用od命令
#+ 最后使用awk来获取一个数,这个数将作为随机数产生的种子
RANDOM=$SEED
random_numbersecho;echoexit 0

随机数种子的作用就是:相同的随机数种子,拿到的随机数是相同的
例:

[root@localhost aaa]# RANDOM=10;echo $RANDOM
4230
[root@localhost aaa]# RANDOM=10;echo $RANDOM
4230
[root@localhost aaa]# RANDOM=1;echo $RANDOM
16807
[root@localhost aaa]# RANDOM=1;echo $RANDOM
16807

注意:/dev/urandom 设备文件提供了一种比单独使用$RANDOM更好的,能产生更随机的随机数的方法。
dd if=/dev/urandom of=targetfile bs=1 count=XX能够产生一个很分散的伪随机数。然而,将这个数赋值到一个脚本文件的变量中,还需要可操作性,比如使用od命令(就像上边的例子,见Example 12.13),或者使用dd命令(见Example 12.55),或者管道到md5sum命令中(见Example 33.14)

od

od指令会读取所给予的文件的内容,并将其内容以八进制字码呈现出来。
语法

od [-abcdfhilovx][-A <字码基数>][-j <字符数目>][-N <字符数目>][-s <字符串字符数>][-t <输出格式>][-w <每列字符数>][--help][--version][文件...]

参数:

-a  此参数的效果和同时指定"-ta"参数相同。
-A<字码基数>  选择要以何种基数计算字码。
-b  此参数的效果和同时指定"-toC"参数相同。
-c  此参数的效果和同时指定"-tC"参数相同。
-d  此参数的效果和同时指定"-tu2"参数相同。
-f  此参数的效果和同时指定"-tfF"参数相同。
-h  此参数的效果和同时指定"-tx2"参数相同。
-i  此参数的效果和同时指定"-td2"参数相同。
-j<字符数目>或--skip-bytes=<字符数目>  略过设置的字符数目。
-l  此参数的效果和同时指定"-td4"参数相同。
-N<字符数目>或--read-bytes=<字符数目>  到设置的字符数目为止。
-o  此参数的效果和同时指定"-to2"参数相同。
-s<字符串字符数>或--strings=<字符串字符数>  只显示符合指定的字符数目的字符串。
-t<输出格式>或--format=<输出格式>  设置输出格式。
-v或--output-duplicates  输出时不省略重复的数据。
-w<每列字符数>或--width=<每列字符数>  设置每列的最大字符数。
-x  此参数的效果和同时指定"-h"参数相同。
--help  在线帮助。
--version  显示版本信息。

实例
创建 tmp 文件:

$ echo abcdef g > tmp
$ cat tmp
abcdef g

使用 od 命令:

$ od -b tmp
0000000 141 142 143 144 145 146 040 147 012
0000011

使用单字节八进制解释进行输出,注意左侧的默认地址格式为八字节:

$ od -c tmp
0000000   a   b   c   d   e   f       g  \n
0000011

使用ASCII码进行输出,注意其中包括转义字符

$ od -t d1 tmp
0000000   97   98   99  100  101  102   32  103   10
0000011

使用单字节十进制进行解释

$ od -A d -c tmp
0000000   a   b   c   d   e   f       g  \n
0000009

dd

dd命令用于读取、转换并输出数据。
dd可从标准输入或文件中读取数据,根据指定的格式来转换数据,再输出到文件、设备或标准输出。
参数说明:

if=文件名:指定源文件。
of=文件名:指定目的文件。
ibs=bytes:一次读入bytes个字节,即指定一个块大小为bytes个字节。
obs=bytes:一次输出bytes个字节,即指定一个块大小为bytes个字节。
bs=bytes:同时设置读入/输出的块大小为bytes个字节。
cbs=bytes:一次转换bytes个字节,即指定转换缓冲区大小。
skip=blocks:从输入文件开头跳过blocks个块后再开始复制。
seek=blocks:从输出文件开头跳过blocks个块后再开始复制。
count=blocks:仅拷贝blocks个块,块大小等于ibs指定的字节数。
conv=<关键字>,关键字可以有以下11种:conversion:用指定的参数转换文件。ascii:转换ebcdic为asciiebcdic:转换ascii为ebcdicibm:转换ascii为alternate ebcdicblock:把每一行转换为长度为cbs,不足部分用空格填充unblock:使每一行的长度都为cbs,不足部分用空格填充lcase:把大写字符转换为小写字符ucase:把小写字符转换为大写字符swab:交换输入的每对字节noerror:出错时不停止notrunc:不截短输出文件sync:将每个输入块填充到ibs个字节,不足部分用空(NUL)字符补齐。
--help:显示帮助信息
--version:显示版本信息

实例
在Linux 下制作启动盘,可使用如下命令:

dd if=boot.img of=/dev/fd0 bs=1440k

将testfile文件中的所有英文字母转换为大写,然后转成为testfile_1文件,在命令提示符中使用如下命令:

dd if=testfile_2 of=testfile_1 conv=ucase

其中testfile_2 的内容为:

$ cat testfile_2 #testfile_2的内容
HELLO LINUX!
Linux is a free unix-type opterating system.
This is a linux testfile!
Linux test

转换完成后,testfile_1 的内容如下:

$ dd if=testfile_2 of=testfile_1 conv=ucase #使用dd 命令,大小写转换记录了0+1 的读入
记录了0+1 的写出
95字节(95 B)已复制,0.000131446 秒,723 KB/s
cmd@hdd-desktop:~$ cat testfile_1 #查看转换后的testfile_1文件内容
HELLO LINUX!
LINUX IS A FREE UNIX-TYPE OPTERATING SYSTEM.
THIS IS A LINUX TESTFILE!
LINUX TEST #testfile_2中的所有字符都变成了大写字母

由标准输入设备读入字符串,并将字符串转换成大写后,再输出到标准输出设备,使用的命令为:

dd conv=ucase

输入以上命令后按回车键,输入字符串,再按回车键,按组合键Ctrl+D 退出,出现以下结果:

$ dd conv=ucase
Hello Linux! #输入字符串后按回车键
HELLO LINUX! #按组合键Ctrl+D退出,转换成大写结果
记录了0+1 的读入
记录了0+1 的写出
13字节(13 B)已复制,12.1558 秒,0.0 KB/s

Example 9.29 使用awk产生伪随机数

#!/bin/bash
#random2.sh :产生一个范围0-1的伪随机数
#使用awk的rand()函数AWKSCRIPT='{ srand();print rand()}'
#Command(s)/传到awk中的参数
#注意:srand()函数用来产生awk的随机数种子。echo -n "Random number between 0 and 1 = "echo |awk "$AWKSCRIPT"
#如果省去echo,awk将缺少标准输入,需要手动输入exit 0

9.7 双圆括号结构

((…))与let命令很像,允许算术扩展和赋值。举个简单的例子a=$((5+3)),将把a设为"5+3"或者8.然而,双圆括号也是一种在Bash中允许使用C风格的变量处理的机制

Example 9.30 C风格的变量处理

#!/bin/bash
#使用((...))处理一个C风格变量
echo(( a = 23 ))   #给一个变量赋值,从"="号两边的空格就能看出这是C风格的处理
echo "a (initial value) = $a"(( a++ ))   #变量a后+1,C风格
echo "a (after a++) = $a"(( a-- ))   #变量a后-1,C风格
echo "a (after a--) = $a"(( ++a ))   #变量a预+1,C风格
echo "a (after ++a) = $a"(( --a ))   #变量a预-1,C风格
echo "a (after --a) = $a"echo
#注意:在C语言中,预减和后减操作会有些不同的副作用
n=1;let --n && echo "True" || echo "False"   #False
n=1;let n-- && echo "True" || echo "False"   #Trueecho(( t = a<45?7:11 ))   #C风格的3元操作
echo "If a <45,then t = 7,else t = 11."
echo "t = $t"echoexit 0

见Example 10-12

shell高级编程笔记(第九章 变量重游)相关推荐

  1. shell高级编程笔记(第十章 循环和分支)

    第十章 循环和分支 对代码块进行操作是有组织的结构化的shell脚本的关键.为了达到这个目的,循环和分支提供帮助. 10.1 循环 循环就是重复一些命令的代码块,如果条件不满足就退出循环. for 循 ...

  2. 博客文件第二部分 Linux Shell高级编程技巧——第一章 深入讨论

    最近研究博客文件,稍微总结一下,以后继续补充: 道歉 由于频视出了点问题,所以临时只有这点内容,期后再补上. 条记 #录记和域 #入深探讨awk #编辑 #vi awkif.sh #转变权限 [roo ...

  3. UINX环境高级编程笔记 第3章 文件I/O

    UNIX环境高级编程 第三章 文件I/O 3.1 引言 3.2 文件描述符 3.3 函数open和openat 3.4 函数creat 3.5 函数close 3.6 函数lseek 3.7 函数re ...

  4. 《Go语言圣经》学习笔记 第九章 基于共享变量的并发

    <Go语言圣经>学习笔记 第九章 基于共享变量的并发 目录 竞争条件 sync.Mutex互斥锁 syn.RWMutex读写锁 内存同步 syn.Once初始化 竞争条件检测 示例:并发的 ...

  5. NDK 高级编程(笔记)

    Android 开发中针对 NDK 的书籍很少,<Pro Android C++ with the NDK>也是出版的比较早的一本书,有些内容可能对现在的开发并不适用.但是书中介绍的内容比 ...

  6. linux常见命令以及shell入门编程(笔记总结记录)

    linux常见命令以及shell入门编程(笔记总结记录) 文章目录 linux常见命令以及shell入门编程(笔记总结记录) 一.linux常见命令 1.不常用 2.常用 3.数据查找处理相关 1.g ...

  7. Android群英传笔记——第九章:Android系统信息和安全机制

    Android群英传笔记--第九章:Android系统信息和安全机制 本书也正式的进入尾声了,在android的世界了,不同的软件,硬件信息就像一个国家的经济水平,军事水平,不同的配置参数,代表着一个 ...

  8. 《金融学》笔记 第九章 货币需求、供给、均衡

    <金融学>笔记 第九章 货币需求.供给.均衡 前言 在<<金融学>笔记>中开了一个头,现在完善具体细节. 金融范畴篇 第一章 货币的本质 第二章 货币制度 第三章 ...

  9. R语言实战笔记--第九章 方差分析

    R语言实战笔记–第九章 方差分析 标签(空格分隔): R语言 方差分析 术语 组间因子,组内因子,水平:组间因子和组同因子的区别是,组间因子对所有测试对象进行分组,而组内因子则把所有测试对象归为同一组 ...

最新文章

  1. 面试官: 讲讲 Spring 事务有哪些坑?
  2. 读书笔记-《增长黑客》-好产品是增长的根本
  3. Intel Realsense D435 开始运行启动时报错:RuntimeError: Couldn't resolve requests 原因及解决办法
  4. Django入门(二) 理解Django生命流程周期
  5. xml与java对象转换 -- XStreamAlias
  6. Proxy(代理)--对象结构型模式
  7. python算法与程序设计基础(第二版)第八章实训答案_Python算法与程序设计基础(第2版)...
  8. 聊聊 vue 生命周期
  9. zmq 接口函数之 :zmq_socket_monitor - 注册一个监控回调函数
  10. MyBatis框架 拦截器简单使用
  11. Datawhale 暑期组队学习计划
  12. python中str表示什么意思_python的str是什么类型
  13. 【看这一篇就够了】如何删除gitHub仓库中的文件
  14. 基于WCF的通道网络传输数据压缩技术的应用研究
  15. matlab 脚本文件 函数,Matlab 脚本文件script和函数文件function的区别
  16. stvd能编辑c语言吗,STVD自动生成的stm8_interrupt_vector.c中几个疑问
  17. 项目需求的重要性和开发步骤
  18. ffmpeg 从现有视频中截取一段
  19. 评价指标 balanced accuracy
  20. 如何启用计算机睡眠功能,如何让电脑休眠_如何开启电脑休眠模式-win7之家

热门文章

  1. mcp2515 linux 设备树,MCP2515在Hi3521D上的调试
  2. T-SQL 基础学习 01
  3. 深入理解Arrays.sort()
  4. Windows如何区分鼠标双击和两次单击
  5. 网络安全必学知识点之XSS漏洞
  6. C语言队列函数中pop,C语言_队列的基本操作
  7. window.showModalDialog不兼容的解决方案
  8. 靶机渗透练习96-hacksudo:Thor
  9. 信号频率、采样频率、采样点数
  10. 使用map_server保存并使用已有地图