Bash 进阶 P101
变量重游
内部变量
变量名($名称) | 意思 |
---|---|
BASH | Bash 的二进制程序文件的路径 |
BASH_ENV | Bash的启动文件 |
BASH_SUBSHELL | 提示子shell的层次 |
BASH_VERSINFO[n] | 6个元素的数组, 它包含了所安装的Bash的版本信息 |
BASH_VERSION | Bash版本号 |
DIRSTACK | 在目录栈中最顶端的值 |
EDITOR | 脚本所调用的默认编辑器, 通常情况下是vi或者是emacs. |
EUID | "有效"用户ID |
FUNCNAME | 当前函数的名字 |
GLOBIGNORE | 一个文件名的模式匹配列表 |
GROUPS | 目前用户所属的组 |
HOME | 用户的home目录, 一般是 /home/username |
HOSTNAME | 在一个初始化脚本中, 在系统启动的时候分配一个系统名字 |
$HOSTTYPE | 主机类型 |
IFS | 内部域分隔符 决定Bash在解释字符串时如何识别域, 或者单词边界. |
IGNOREEOF | 忽略EOF: 告诉shell在log out之前要忽略多少文件结束符(control-D) |
LC_COLLATE | 常在 .bashrc 或 /etc/profile 中设置, 这个变量用来控制文件名扩展和模式匹配的展开顺序 |
LC_CTYPE | 来控制通配(globbing)和模式匹配中的字符串解释 |
LINENO | 自身在脚本中所在的行号 |
MACHTYPE | 机器类型 |
OLDPWD | 之前的工作目录("OLD-print-working-directory", 就是之前你所在的目录) |
OSTYPE | 操作系统类型 |
PATH | 可执行文件的搜索路径, 一般为 /usr/bin/ , /usr/X11R6/bin/ , /usr/local/bin , 等等. |
PIPESTATUS | 最后一个运行的 前台 管道的退出状态码. |
PPID | 进程的 $PPID 就是这个进程的父进程的进程ID( pid ) |
PROMPT_COMMAND | 主提示符 $PS1 显示之前需要执行的命令 |
PS1 PS2 PS3 PS4 | 第几条提示符 |
PWD | 工作目录(你当前所在的目录) 与内建命令pwd作用相同 |
REPLY | 当没有参数变量提供给read命令的时候, 这个变量会作为默认变量提供给read命令. |
SECONDS | 脚本已经运行的时间 |
SHELLOPTS | hell中已经激活的选项的列表, 这是一个只读变量 |
SHLVL | Shell级别, 就是Bash被嵌套的深度. 如果是在命令行中, 那么$SHLVL为1, 如果在脚本中那么$SHLVL为2 |
TMOUT | 如果 $TMOUT 环境变量被设置为非零值 time 的话, 那么经过 time 秒后, shell提示符将会超时. 这将 会导致登出(logout). |
UID | 用户ID号 当前用户的用户标识号, 记录在 /etc/passwd 文件中 |
$0 , $1 , $2 | 位置参数, 从命令行传递到脚本, 或者传递给函数 或者set给变量 |
$# $* $@ |
命令行参数 或者位置参数的个数 所有的位置参数都被看作为一个单词 与$*相同, 但是每个参数都是一个独立的引用字符串 |
$- $! $_ $? $$ |
传递给脚本的标记 运行在后台的最后一个作业的PID(进程ID) 之前执行的命令的最后一个参数的值. 命令, 函数, 或者是脚本本身的退出状态码 脚本自身的进程ID. |
#!/bin/bash
# $IFS 处理空白与处理其他字符不同.
output_args_one_per_line()
{
for arg
do echo "[$arg]"
done
}
echo; echo "IFS=\" \""
echo "-------"
IFS=" "
var=" a b c "
output_args_one_per_line $var #
output_args_one_per_line `echo " a b c "`
#
# [a]
# [b]
# [c]
echo; echo "IFS=:"
echo "-----"
IFS=:
var=":a::b:c:::" # 与上边一样, 但是用" "替换
了":".
output_args_one_per_line $var
#
# []
# [a]
# []
# [b]
# [c]
# []
# []
# []
# 同样的事情也会发生在awk的"FS"域中.
echo
exit 0
#!/bin/bash
# reply.sh
# REPLY是提供给'read'命令的默认变量.
echo
echo -n "What is your favorite vegetable? "
read
echo "Your favorite vegetable is $REPLY."
# 当且仅当没有变量提供给"read"命令时,
#!/bin/bash
# timed-input.sh
# TMOUT=3 在新一些的Bash版本上也能运行的很好.
TIMELIMIT=3 # 这个例子中设置的是3秒. 也可以设置为其他的时间值.
PrintAnswer()
{
if [ "$answer" = TIMEOUT ]
then
echo $answer
else # 别和上边的例子弄混了.
echo "Your favorite veggie is $answer"
kill $! # 不再需要后台运行的TimerOn函数了, kill了吧.
# $! 变量是上一个在后台运行的作业的PID.
fi
}
TimerOn()
{
sleep $TIMELIMIT && kill -s 14 $$ &
# 等待3秒, 然后给脚本发送一个信号.
}
Int14Vector()
{
answer="TIMEOUT"
PrintAnswer
exit 14
}
trap Int14Vector 14 # 定时中断(14)会暗中给定时间限制.
echo "What is your favorite vegetable "
TimerOn
read answer
PrintAnswer
# 无可否认, 这是一个定时输入的复杂实现,
#+ 然而"read"命令的"-t"选项可以简化这个任务.
# 参考后边的"t-out.sh".
# 如果你需要一个真正优雅的写法...
#+ 建议你使用C或C++来重写这个应用,
#+ 你可以使用合适的函数库, 比如'alarm'和'setitimer'来完成这个任务.
exit 0
#!/bin/bash
# t-out.sh
# 从"syngin seven"的建议中得到的灵感 (感谢).
TIMELIMIT=4 # 4秒
read -t $TIMELIMIT variable <&1
# ^^^
# 在这个例子中, 对于Bash 1.x和2.x就需要"<&1"了,
# 但是Bash 3.x就不需要.
echo
if [ -z "$variable" ] # 值为null?
then
echo "Timed out, variable still unset."
else
echo "variable = $variable"
fi
exit 0
#!/bin/bash
# arglist.sh
# 多使用几个参数来调用这个脚本, 比如"one two three".
E_BADARGS=65
if [ ! -n "$1" ]
then
echo "Usage: `basename $0` argument1 argument2 etc."
exit $E_BADARGS
fi
echo
index=1 # 起始计数.
echo "Listing args with \"\$*\":"
for arg in "$*" # 如果"$*"不被""引用,那么将不能正常地工作.
do
echo "Arg #$index = $arg"
let "index+=1"
done # $* 将所有的参数看成一个单词.
echo "Entire arg list seen as single word."
echo
index=1 # 重置计数(译者注: 从1开始).
# 如果你写这句会发生什么?
echo "Listing args with \"\$@\":"
for arg in "$@"
do
echo "Arg #$index = $arg"
let "index+=1"
done # $@ 把每个参数都看成是单独的单词.
echo "Arg list seen as separate words."
echo
index=1 # 重置计数(译者注: 从1开始).
echo "Listing args with \$* (unquoted):"
for arg in $*
do
echo "Arg #$index = $arg"
let "index+=1"
done # 未引用的$*将会把参数看成单独的单词.
echo "Arg list seen as separate words."
exit 0
#!/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.
# "$@" 将包含剩下的参数.
操作字符串
expr 介绍
将表达式的值列印到标准输出,分隔符下面的空行可提升算式优先级。 可用的表达式有:
ARG1 | ARG2 若ARG1 的值不为0 或者为空,则返回ARG1,否则返回ARG2
ARG1 & ARG2 若两边的值都不为0 或为空,则返回ARG1,否则返回 0
ARG1 < ARG2 ARG1 小于ARG2 ARG1 <= ARG2 ARG1 小于或等于ARG2 ARG1 = ARG2 ARG1 等于ARG2 ARG1 != ARG2 ARG1 不等于ARG2 ARG1 >= ARG2 ARG1 大于或等于ARG2 ARG1 > ARG2 ARG1 大于ARG2
ARG1 + ARG2 计算 ARG1 与ARG2 相加之和 ARG1 - ARG2 计算 ARG1 与ARG2 相减之差
ARG1 * ARG2 计算 ARG1 与ARG2 相乘之积 ARG1 / ARG2 计算 ARG1 与ARG2 相除之商 ARG1 % ARG2 计算 ARG1 与ARG2 相除之余数
字符串 : 表达式 定位字符串中匹配表达式的模式
match 字符串 表达式 等于"字符串 :表达式" substr 字符串 偏移量 长度 替换字符串的子串,偏移的数值从 1 起计 index 字符串 字符 在字符串中发现字符的地方建立下标,或者标0 length 字符串 字符串的长度
- 记号 将记号解析为字符串,即使它是一个类似"match"或 运算符"/"那样的关键字
( 表达式 ) 表达式的值
请注意有许多运算操作符都可能需要由 shell 先实施转义。 如果参与运算的 ARG 自变量都是数字,比较符就会被视作数学符号,否则就是多义的。 模式匹配会返回""和""之间被匹配的子字符串或空(null);如果未使用""和"", 则会返回匹配字符数量或是 0。
若表达式的值既不是空也不是 0,退出状态值为 0;若表达式的值为空或为 0, 退出状态值为 1。如果表达式的句法无效,则会在出错时返回退出状态值 3。
字符串长度
- ${#string}
- expr length $string
- expr "$string" : '.*
stringZ=abcABC123ABCabc
echo ${#stringZ} # 15
echo `expr length $stringZ` # 15
echo `expr "$stringZ" : '.*'` # 15
匹配字符串开头的子串长度
- expr match "$string" '$substring'
- expr "$string" :'$substring' '$substring' 为正则表达式
stringZ=abcABC123ABCabc
# |------|
echo `expr match "$stringZ" 'abc[A-Z]*.2'` # 8
echo `expr "$stringZ" : 'abc[A-Z]*.2'` # 8
索引
expr index $string $substring
stringZ=abcABC123ABCabc
echo `expr index "$stringZ" C12` # 6
# C 字符的位置.
echo `expr index "$stringZ" 1c` # 3
# 'c' (in #3 position) matches before '1'.
提取子串
- ${string:position 从位置$position 开始提取子串}
- ${string:position:length 从位置 $position 开始提取 $length 长度的子串.}
- expr substr $string $position $length 在 $string 中从 $position 开始提取 $length 长度的子串.
- expr match "$string" '\($substring\)'
- expr "$string" : '\($substring\)'
stringZ=abcABC123ABCabc
# 0123456789.....
# 0-based indexing.
echo ${stringZ:0} # abcABC123ABCabc
echo ${stringZ:1} # bcABC123ABCabc
echo ${stringZ:7} # 23ABCabc
echo ${stringZ:7:3} # 23A
# 提取子串长度为3.
# 能不能从字符串的右边(也就是结尾)部分开始提取子串?
echo ${stringZ:-4} # abcABC123ABCabc
# 默认是提取整个字符串, 就象${parameter:-default}一样.
# 然而 . . .
echo ${stringZ:(-4)} # Cabc
echo ${stringZ: -4} # Cabc
# 这样, 它就可以工作了.
# 使用圆括号或者添加一个空格可以"转义"这个位置参数.
stringZ=abcABC123ABCabc
2 # 123456789......
3 # 以1开始计算.
4
5 echo `expr substr $stringZ 1 2` # ab
6 echo `expr substr $stringZ 4 3` # ABC
子串削除
- ${string#substring 从 $string 的 开头 位置截掉 最短 匹配的 $substring .}
- ${string##substring 从 $string 的 开头 位置截掉 最长 匹配的 $substring .}
- ${string%substring 从 $string 的 结尾 位置截掉 最短 匹配的 $substring .}
- ${string%%substring 从 $string 的 结尾 位置截掉 最长 匹配的 $substring .}
stringZ=abcABC123ABCabc
# |----|
# |----------|
echo ${stringZ#a*C} # 123ABCabc
# 截掉'a'到'C'之间最短的匹配字符串.
echo ${stringZ##a*C} # abc
# 截掉'a'到'C'之间最长的匹配字符串.
stringZ=abcABC123ABCabc
# ||
# |------------|
echo ${stringZ%b*c} # abcABC123ABCa
# 从$stringZ的结尾位置截掉'b'到'c'之间最短的匹配.
echo ${stringZ%%b*c} # a
# 从$stringZ的结尾位置截掉'b'到'c'之间最长的匹配.
#!/bin/bash
# getopt-simple.sh
getopt_simple()
{
echo "getopt_simple()"
echo "Parameters are '$*'"
until [ -z "$1" ]
do
echo "Processing parameter of: '$1'"
if [ ${1:0:1} = '/' ]
then
tmp=${1:1} # 去掉开头的'/' . . .
parameter=${tmp%%=*} # 提取参数名.
value=${tmp##*=} # 提取参数值.
echo "Parameter: '$parameter', value: '$value'"
eval $parameter=$value
fi
shift
done
}
# 把所有选项传给函数getopt_simple().
getopt_simple $*
echo "test is '$test'"
echo "test2 is '$test2'"
exit 0
---
sh getopt_example.sh /test=value1 /test2=value2
Parameters are '/test=value1 /test2=value2'
Processing parameter of: '/test=value1'
Parameter: 'test', value: 'value1'
Processing parameter of: '/test2=value2'
Parameter: 'test2', value: 'value2'
test is 'value1'
test2 is 'value2'
子串替换
- ${string/substring/replacement 替换第一个匹配}
- ${string//substring/replacement 替换第所有匹配}
- ${string/#substring/replacement 替换开头匹配}
- ${string/%substring/replacement 替换结尾匹配}
stringZ=abcABC123ABCabc
echo ${stringZ/abc/xyz} # xyzABC123ABCabc
echo ${stringZ//abc/xyz} # xyzABC123ABCxyz
echo ${stringZ/#abc/XYZ} # XYZABC123ABCabc
echo ${stringZ/%abc/XYZ} # abcABC123ABCXYZ
使用awk来处理字符串
#!/bin/bash
# substring-extraction.sh
String=23skidoo1
# 012345678 Bash
# 123456789 awk
# 注意不同的字符串索引系统:
# Bash的第一个字符是从'0'开始记录的.
# Awk的第一个字符是从'1'开始记录的.
echo ${String:2:4} # 位置 3 (0-1-2), 4 个字符长
# skid
# awk中等价于${string:pos:length}的命令是substr(string,pos,length).
echo | awk '
{ print substr("'"${String}"'",3,4) # skid
}
'
# 使用一个空的"echo"通过管道传递给awk一个假的输入,
#+ 这样就不必提供一个文件名给awk.
exit 0
参数替换
处理和(或)扩展变量
- ${parameter 与 $parameter 相同, 也就是变量 parameter 的值}
- ${parameter-default 如果变量parameter没被声明, 那么就使用默认值}
- ${parameter:-default 如果变量parameter没被设置, 那么就使用默认值.}
- ${parameter=default , ${parameter:=default} 如果变量parameter没被设置, 那么就使用默认值.}}
- ${parameter+alt_value , ${parameter:+alt_value} parameter没声明或者设置, 么就使用null ,否则使用alt_value.}}
- ${parameter?err_msg 未声明 打印 err_msg}
- ${parameter:?err_msg未设置 打印 err_msg}
#!/bin/bash
# param-sub.sh
# 一个变量是否被声明或设置,
#+ 将会影响这个变量是否使用默认值,
#+ 即使这个变量值为空(null).
username0=
echo "username0 has been declared, but is set to null."
echo "username0 = ${username0-`whoami`}"
# 不会有输出.
echo
echo username1 has not been declared.
echo "username1 = ${username1-`whoami`}"
# 将会输出默认值.
username2=
echo "username2 has been declared, but is set to null."
echo "username2 = ${username2:-`whoami`}"
# ^
# 会输出, 因为:-会比-多一个条件测试.
# 可以与上边的例子比较一下.
#
# 再来一个:
variable=
# 变量已经被声明, 但是设为空值.
echo "${variable-0}" # (没有输出)
echo "${variable:-1}" # 1
# ^
unset variable
echo "${variable-2}" # 2
echo "${variable:-3}" # 3
exit 0
#!/bin/bash
# 检查一些系统环境变量.
# 这是一种可以做一些预防性保护措施的好习惯.
# 比如, 如果$USER(用户在控制台上中的名字)没有被设置的话,
#+ 那么系统就会不认你.
: ${HOSTNAME?} ${USER?} ${HOME?} ${MAIL?}
echo
echo "Name of the machine is $HOSTNAME."
echo "You are $USER."
echo "Your home directory is $HOME."
echo "Your mail INBOX is located in $MAIL."
echo
echo "If you are reading this message,"
echo "critical environmental variables have been set."
echo
echo
# ------------------------------------------------------
# ${variablename?}结构
#+ 也能够检查脚本中变量的设置情况.
ThisVariable=Value-of-ThisVariable
# 注意, 顺便提一下,
#+ 这个字符串变量可能会被设置一些非法字符.
: ${ThisVariable?}
echo "Value of ThisVariable is $ThisVariable".
echo
echo
: ${ZZXy23AB?"ZZXy23AB has not been set."}
# 如果变量ZZXy23AB没有被设置的话,
#+ 那么这个脚本会打印一个错误信息, 然后结束.
# 你可以自己指定错误消息.
# : ${variablename?"ERROR MESSAGE"}
# 等价于: dummy_variable=${ZZXy23AB?}
# dummy_variable=${ZZXy23AB?"ZXy23AB has not been set."}
#
# echo ${ZZXy23AB?} >/dev/null
# 使用命令"set -u"来比较这些检查变量是否被设置的方法.
#
echo "You will not see this message, because script already terminated."
HERE=0
exit $HERE # 不会在这里退出.
# 事实上, 这个脚本将会以返回值1作为退出状态(echo $?).
变量长度/子串删除
- ${#var 字符串长度 (变量 $var 得字符个数)}
- ${var#Pattern , ${var##Pattern 从变量 $var 的 开头 删除最短或最长匹配 $Pattern 的子串}}
- ${var%Pattern , ${var%%Pattern 从变量 $var 的 结尾 删除最短或最长匹配 $Pattern 的子串}}
#!/bin/bash
# length.sh
E_NO_ARGS=65
if [ $# -eq 0 ] # 这个演示脚本必须有命令行参数.
then
echo "Please invoke this script with one or more command-line
arguments."
exit $E_NO_ARGS
fi
var01=abcdEFGH28ij
echo "var01 = ${var01}"
echo "Length of var01 = ${#var01}"
# 现在, 让我们试试在变量中嵌入一个空格.
var02="abcd EFGH28ij"
echo "var02 = ${var02}"
echo "Length of var02 = ${#var02}"
echo "Number of command-line arguments passed to script = ${#@}"
echo "Number of command-line arguments passed to script = ${#*}"
exit 0
# 摘自例子"days-between.sh"的一个函数.
# 去掉传递进来参数开头的0.
strip_leading_zero () # 去掉从参数中传递进来的,
{ #+ 可能存在的开头的0(也可能有多个0).
return=${1#0} # "1"表示的是"$1" -- 传递进来的参数.
}
#!/bin/bash
# patt-matching.sh
# 使用# ## % %%来进行参数替换操作的模式匹配. parameter substitution operators.
var1=abcd12345abc6789
pattern1=a*c # *(通配符)匹配a - c之间的任意字符.
echo
echo "var1 = $var1" # abcd12345abc6789
echo "var1 = ${var1}" # abcd12345abc6789
# (另一种形式)
echo "Number of characters in ${var1} = ${#var1}"
echo
echo "pattern1 = $pattern1" # a*c (匹配'a'到'c'之间的任意字符)
echo "--------------"
echo '${var1#$pattern1} =' "${var1#$pattern1}" # d12345abc6789
# 最短的可能匹配, 去掉abcd12345abc6789的前3个字符.
# |-| ^^^^^
echo '${var1##$pattern1} =' "${var1##$pattern1}" # 6789
# 最长的可能匹配, 去掉abcd12345abc6789的前12个字符
# |----------| ^^^^^^
echo; echo; echo
pattern2=b*9 # 匹配'b'到'9'之间的任意字符
echo "var1 = $var1" # 还是abcd12345abc6789
echo
echo "pattern2 = $pattern2"
echo "--------------"
echo '${var1%pattern2} =' "${var1%$pattern2}" # abcd12345a
# 最短的可能匹配, 去掉abcd12345abc6789的最后6个字符
# |----| ^^^^^^^
echo '${var1%%pattern2} =' "${var1%%$pattern2}" # a
# 最长的可能匹配, 去掉abcd12345abc6789的最后12个字符
# |-------------| ^^^^^^^^
# 牢记, #和##是从字符串左边开始, 并且去掉左边的字符串,
# %和%%从字符串的右边开始, 并且去掉右边的字符串.
echo
exit 0
变量扩展/子串替换
- ${var:pos 变量 var 从位置 pos 开始扩展}
- ${var:pos:len 变量 var 从位置 pos 开始, 并扩展 len 个字符}
- ${var/Pattern/Replacement 用 Replacement 来替换变量 var 中第一个匹配 Pattern 的字符串}
- ${var//Pattern/Replacement 全局替换. 所有在变量 var 匹配 Pattern 的字符串, 都会被替换为 Replacement}
- ${var/#Pattern/Replacement 果变量 var 的 前缀 匹配 Pattern , 那么就使用 Replacement 来替换匹配到 Pattern 的字符串}
- ${var/%Pattern/Replacement 如果变量 var 的 后缀 匹配 Pattern , 那么就使用 Replacement 来替换匹配到 Pattern 的字符串}
- ${!varprefix* , ${!varprefix@ 匹配所有之前声明过的, 并且以 varprefix 开头的变量.}}
#!/bin/bash
# var-match.sh:
# 对字符串的前缀和后缀进行模式替换的一个演示.
v0=abc1234zip1234abc # 变量原始值.
echo "v0 = $v0" # abc1234zip1234abc
echo
# 匹配字符串的前缀(开头).
v1=${v0/#abc/ABCDEF} # abc1234zip1234abc
# |-|
echo "v1 = $v1" # ABCDEF1234zip1234abc
# |----|
# 匹配字符串的后缀(结尾).
v2=${v0/%abc/ABCDEF} # abc1234zip123abc
# |-|
echo "v2 = $v2" # abc1234zip1234ABCDEF
# |----|
echo
# ----------------------------------------------------
# 必须匹配字符串的开头或结尾,
#+ 否则是不会产生替换结果的.
# ----------------------------------------------------
v3=${v0/#123/000} # 匹配, 但不是在开头.
echo "v3 = $v3" # abc1234zip1234abc
# 不会发生替换.
v4=${v0/%123/000} # 匹配, 但不是在结尾.
echo "v4 = $v4" # abc1234zip1234abc
# 不会发生替换.
exit 0
xyz23=whatever
xyz24=
a=${!xyz*} # 展开所有以"xyz"开头的, 并且之前声明过的变量名.
echo "a = $a" # a = xyz23 xyz24
a=${!xyz@} # 同上.
echo "a = $a" # a = xyz23 xyz24
# Bash, 版本2.04, 添加了这个功能.
指定变量的类型: 使用declare或者typeset
declare 或者 typeset 内建命令(这两个命令是完全一样的)允许指定变量的具体类型. 在某些编程语言 中, 这是指定变量类型的一种很弱的形式. declare命令是从Bash 2.0之后才被引入的命令. typeset也 可以用在ksh的脚本中
declare/typeset选项
option | meaning | example |
---|---|---|
-r | 只读 | declare -r var1 #与 readonly var1 是完全一样的 |
-i | 整型 | declare -i numbe number=3 echo "Number = $number" # Number = 3number=three echo "Number = $number" # Number = 0 尝试把字符串"three"作为整数来求值 |
-a | 数组 | declare -a indices |
-f | 函数 | declare -f |
-x | export | declare -x var3 #声明一个变量, 并作为这个脚本的环境变量被导出 declare -x var3=373 #声明变量类型的同时给变量赋值 |
变量的间接引用
假设一个变量的值是第二个变量的名字. 那么我们如何从第一个变量中取得第二个变量的值呢? 比如, 如果 a=letter_of_alphabet 并且 letter_of_alphabet=z , 那么我们能够通过引用变量 a 来获得 z 么? 这确 实是可以做到的, 它被称为 间接引用 . 它使用 eval var1=\$$var2 这种不平常的形式.
#!/bin/bash
# ind-ref.sh: 间接变量引用.
# 访问一个以另一个变量内容作为名字的变量的值.(译者注: 怎么译都不顺)
a=letter_of_alphabet # 变量"a"的值是另一个变量的名字.
letter_of_alphabet=z
echo
# 直接引用.
echo "a = $a" # a = letter_of_alphabet
# 间接引用.
eval a=\$$a
echo "Now a = $a" # 现在 a = z
echo
# 现在, 让我们试试修改第二个引用的值.
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 # 解引用 "t" = 24
# 在这个简单的例子中, 下面的表达式也能正常工作么(为什么?).
# eval t=\$$t; echo "\"t\" = $t"
echo
t=table_cell_3
NEW_VAL=387
table_cell_3=$NEW_VAL
echo "Changing value of \"table_cell_3\" to $NEW_VAL."
echo "\"table_cell_3\" now $table_cell_3"
echo -n "dereferenced \"t\" now "; eval echo \$$t
# "eval" 带有两个参数 "echo" 和 "\$$t" (与$table_cell_3等价)
echo
# 另一个方法是使用${!t}符号, 见"Bash, 版本2"小节的讨论.
# 也请参考 ex78.sh.
exit 0
变量的间接引用到底有什么应用价值? 它
$RANDOM: 产生随机整数
$RANDOM 是Bash的内部函数 (并不是常量), 这个函数将返回一个 伪随机整数, 范围在0 - 32767之 间. 它不应该被用来产生密匙.
#!/bin/bash
# 每次调用$RANDOM都会返回不同的随机整数.
# 一般范围为: 0 - 32767 (有符号的16-bit整数).
MAXCOUNT=10
count=1
echo
echo "$MAXCOUNT random numbers:"
echo "-----------------"
while [ "$count" -le $MAXCOUNT ] # 产生10 ($MAXCOUNT)个随机整数.
do
number=$RANDOM
echo $number
let "count += 1" # 增加计数.
done
echo "-----------------"
# 如果你需要在特定范围内产生随机整数, 那么使用'modulo'(模)操作.(译者注: 事实上, 这不是一个非常
好的办法. 理由见man 3 rand)
# 取模操作会返回除法的余数.
RANGE=500
echo
number=$RANDOM
let "number %= $RANGE"
# ^^
echo "Random number less than $RANGE --- $number"
echo
# 如果你需要产生一个大于某个下限的随机整数.
#+ 那么建立一个test循环来丢弃所有小于此下限值的整数.
FLOOR=200
number=0 #初始化
while [ "$number" -le $FLOOR ]
do
number=$RANDOM
done
echo "Random number greater than $FLOOR --- $number"
echo
# 让我们对上边的循环尝试一个小改动, 如下:
# let "number = $RANDOM + $FLOOR"
# 这将不再需要那个while循环, 并且能够运行的更快.
# 但是, 这可能会产生一个问题, 思考一下是什么问题?
# 结合上边两个例子, 来在指定的上下限之间来产生随机数.
number=0 #initialize
while [ "$number" -le $FLOOR ]
do
number=$RANDOM
let "number %= $RANGE" # 让$number依比例落在$RANGE的范围内.
done
echo "Random number between $FLOOR and $RANGE --- $number"
echo
# 产生二元值, 就是, "true" 或 "false" 两个值.
BINARY=2
T=1
number=$RANDOM
let "number %= $BINARY"
# 注意 let "number >>= 14" 将会给出一个更好的随机分配. #(译者注: 正如man页中提到的, 更
高位的随机分布更加平均)
#+ (右移14位将把所有的位全部清空, 除了第15位, 因为有符号, 第16位是符号位). #取模操作使用低位来
产生随机数会相对不平均)
if [ "$number" -eq $T ]
then
echo "TRUE"
else
echo "FALSE"
fi
echo
# 抛骰子.
SPOTS=6 # 模6给出的范围是0 - 5.
# 加1会得到期望的范围1 - 6.
# 感谢, Paulo Marcel Coelho Aragao, 对此进行的简化.
die1=0
die2=0
# 是否让SPOTS=7会比加1更好呢? 解释行或者不行的原因?
# 每次抛骰子, 都会给出均等的机会.
let "die1 = $RANDOM % $SPOTS +1" # 抛第一次.
let "die2 = $RANDOM % $SPOTS +1" # 抛第二次.
# 上边的算术操作中, 哪个具有更高的优先级呢 --
#+ 模(%) 还是加法操作(+)?
let "throw = $die1 + $die2"
echo "Throw of the dice = $throw"
echo
exit 0
双圆括号结构
与let命令很相似, ((...))结构允许算术扩展和赋值. 举个简单的例子, a=$(( 5 + 3 )) , 将把变 量"a"设为"5 + 3", 或者8. 然而, 双圆括号结构也被认为是在Bash中使用C语言风格变量操作的一种处 理机制.
#!/bin/bash
# 使用((...))结构操作一个变量, C语言风格的变量操作.
echo
(( a = 23 )) # C语言风格的变量赋值, "="两边允许有空格.
echo "a (initial value) = $a"
(( a++ )) # C语言风格的后置自加.
echo "a (after a++) = $a"
(( a-- )) # C语言风格的后置自减.
echo "a (after a--) = $a"
(( ++a )) # C语言风格的前置自加.
echo "a (after ++a) = $a"
(( --a )) # 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" # True
echo
(( t = a<45?7:11 )) # C语言风格的三元操作.
echo "If a < 45, then t = 7, else t = 11."
echo "t = $t " # Yes!
echo
# ------------
# 复活节彩蛋!
# ------------
# Chet Ramey显然偷偷摸摸的将一些未公开的C语言风格的结构
#+ 引入到了Bash中 (事实上是从ksh中引入的, 这更接近些).
# 在Bash的文档中, Ramey将((...))称为shell算术运算,
#+ 但是它所能做的远远不止于此.
# 不好意思, Chet, 现在秘密被公开了.
# 你也可以参考一些 "for" 和 "while" 循环中使用((...))结构的例子.
# 这些只能够在Bash 2.04或更高版本的Bash上才能运行.
exit 0
循环与分支 P148
循环
for
for循环 for arg in [list] 这是一个基本的循环结构. 它与C语言中的for循环结构有很大的不同.
for arg in [ list ] do command(s) ... done
#!/bin/bash
# 列出所有的行星名称.
for planet in Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto
do
echo $planet # 每个行星都被单独打印在一行上.
done
echo
for planet in "Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto"
# 所有的行星名称都打印在同一行上.
# 整个'list'都被双引号封成了一个变量.
do
echo $planet
done
exit 0
#!/bin/bash
# 还是行星.
# 用行星距太阳的距离来分配行星的名字.
for planet in "Mercury 36" "Venus 67" "Earth 93" "Mars 142"
"Jupiter 483"
do
set -- $planet # 解析变量"planet"并且设置位置参数.
# "--" 将防止$planet为空, 或者是以一个破折号开头.
# 可能需要保存原始的位置参数, 因为它们被覆盖了.
# 一种方法就是使用数组.
# original_params=("$@")
echo "$1 $2,000,000 miles from the sun"
#-------two tabs---把后边的0和2连接起来
done
exit 0
#!/bin/bash
# list-glob.sh: 使用"globbing", 在for循环中产生[list]
echo
for file in *
# ^ 在表达式中识别文件名匹配时,
#+ Bash将执行文件名扩展.
do
ls -l "$file" # 列出在$PWD(当前目录)中的所有文件.
# 回想一下,通配符"*"能够匹配所有文件,
#+ 然而,在"globbing"中,是不能比配"."文件的.
# 如果没匹配到任何文件,那它将扩展成自己.
# 为了不让这种情况发生,那就设置nullglob选项
#+ (shopt -s nullglob).
done
echo; echo
for file in [jx]*
do
rm -f $file # 只删除当前目录下以"j"或"x"开头的文件.
echo "Removed file \"$file\"".
done
echo
exit 0
#!/bin/bash
# 两种循环到10的方法.
echo
# 标准语法.
for a in 1 2 3 4 5 6 7 8 9 10
do
echo -n "$a "
done
echo; echo
# +==========================================+
# 现在, 让我们用C风格语法来做相同的事情.
LIMIT=10
for ((a=1; a <= LIMIT ; a++)) # 双圆括号, 并且"LIMIT"变量前面没有"$".
do
echo -n "$a "
done # 这是一个借用'ksh93'的结构.
echo; echo
#
+=========================================================================+
# 让我们使用C语言的"逗号操作符", 来同时增加两个变量的值.
for ((a=1, b=1; a <= LIMIT ; a++, b++)) # 逗号将同时进行两条操作.
do
echo -n "$a-$b "
done
echo; echo
exit 0
while
这种结构在循环的开头判断条件是否满足, 如果条件一直满足, 那么就一直循环下去 (返回0作 为退出状态码). 与for循环的区别是, while循环 更适合在循环次数未知的情况下使用.
while [ condition ] do command ... done
#!/bin/bash
var0=0
LIMIT=10
while [ "$var0" -lt "$LIMIT" ]
do
echo -n "$var0 " # -n 将会阻止产生新行.
# ^ 空格, 数字之间的分隔.
var0=`expr $var0 + 1` # var0=$(($var0+1)) 也可以.
# var0=$((var0 + 1)) 也可以.
# let "var0 += 1" 也可以.
done # 使用其他的方法也行.
echo
exit 0
#!/bin/bash
echo # 等价于:
while [ "$var1" != "end" ] # while test "$var1" != "end"
do
echo "Input variable #1 (end to exit) "
read var1 # 为什么不使用'read $var1'?
echo "variable #1 = $var1" # 因为包含"#", 所以需要""
# 如果输入为'end', 那么就在这里echo.
# 不在这里判断结束, 在循环顶判断.
echo
done
exit 0
#!/bin/bash
# wh-loopc.sh: 循环10次的"while"循环.
LIMIT=10
a=1
while [ "$a" -le $LIMIT ]
do
echo -n "$a "
let "a+=1"
done # 到目前为止都没有什么令人惊奇的地方.
echo; echo
#
+=================================================================+
# 现在, 重复C风格的语法.
((a = 1)) # a=1
# 双圆括号允许赋值两边的空格, 就像C语言一样.
while (( a <= LIMIT )) # 双圆括号, 变量前边没有"$".
do
echo -n "$a "
((a += 1)) # let "a+=1"
# Yes, 看到了吧.
# 双圆括号允许像C风格的语法一样增加变量的值.
done
echo
# 现在, C程序员可以在Bash中找到回家的感觉了吧.
exit 0
until
在循环的顶部判断条件, 并且如果条件一直为false, 那么就一直循环下去. (与 while循 环 相反)
ntil [ condition-is-true ] do command ... done
#!/bin/bash
END_CONDITION=end
until [ "$var1" = "$END_CONDITION" ]
# 在循环的顶部进行条件判断.
do
echo "Input variable #1 "
echo "($END_CONDITION to exit)"
read var1
echo "variable #1 = $var1"
echo
done
exit 0
嵌套循环
嵌套循环 就是在一个循环中还有一个循环, 内部循环在外部循环体中. 在外部循环的每次执行过程中都 会触发内部循环, 直到内部循环执行结束. 外部循环执行了多少次, 内部循环就完成多少次. 当然, 无 论是内部循环还是外部循环的 break 语句都会打断处理过程.
#!/bin/bash
# nested-loop.sh: 嵌套的"for"循环.
outer=1 # 设置外部循环计数.
# 开始外部循环.
for a in 1 2 3 4 5
do
echo "Pass $outer in outer loop."
echo "---------------------"
inner=1 # 重置内部循环计数.
# ===============================================
# 开始内部循环.
for b in 1 2 3 4 5
do
echo "Pass $inner in inner loop."
let "inner+=1" # 增加内部循环计数.
done
# 内部循环结束.
# ===============================================
let "outer+=1" # 增加外部循环的计数.
echo # 每次外部循环之间的间隔.
done
# 外部循环结束.
exit 0
循环控制
break, continue break和continue这两个循环控制命令 与其他语言的类似命令的行为是相同的. break命令用 来跳出循环, 而continue命令只会跳过本次循环, 忽略本次循环剩余的代码, 进入循环的下一 次 迭代
break命令可以带一个参数. 一个不带参数的break命令只能退出最内层的循环, 而break N可以 退出 N 层循环.
#!/bin/bash
# "continue N" 命令, 将让N层的循环全部被continue.
for outer in I II III IV V # 外部循环
do
echo; echo -n "Group $outer: "
# ---------------------------------------------------------------
-----
for inner in 1 2 3 4 5 6 7 8 9 10 # 内部循环
do
if [ "$inner" -eq 7 ]
then
continue 2 # 在第2层循环上的continue, 也就是"外部循环".
# 使用"continue"来替代这句,
# 然后看一下一个正常循环的行为.
fi
echo -n "$inner " # 7 8 9 10 将不会被echo.
done
# ---------------------------------------------------------------
-----
# 译者注: 如果在此处添加echo的话, 当然也不会输出.
done
echo; echo
# 练习:
# 在脚本中放入一个有意义的"continue N".
exit 0
#!/bin/bash
LIMIT=19 # 上限
echo
echo "Printing Numbers 1 through 20 (but not 3 and 11)."
a=0
while [ $a -le "$LIMIT" ]
do
a=$(($a+1))
if [ "$a" -eq 3 ] || [ "$a" -eq 11 ] # 除了3和11.
then
continue # 跳过本次循环剩余的语句.
fi
echo -n "$a " # 在$a等于3和11的时候,这句将不会执行.
done
# 练习:
# 为什么循环会打印出20?
echo; echo
echo Printing Numbers 1 through 20, but something happens after 2.
##################################################################
# 同样的循环, 但是用'break'来代替'continue'.
a=0
while [ "$a" -le "$LIMIT" ]
do
a=$(($a+1))
if [ "$a" -gt 2 ]
then
break # 将会跳出整个循环.
fi
echo -n "$a "
done
echo; echo; echo
exit 0
测试与分支(case与select结构)
case和select结构在技术上说并不是循环, 因为它们并不对可执行代码块进行迭代. 但是和循环相似的 是, 它们也依靠在代码块顶部或底部的条件判断来决定程序的分支. 在代码块中控制程序分支
case "$variable " in $condition1 " ) command ...; $condition2 " ) command ...; esac
注意
- 对变量使用""并不是强制的, 因为不会发生单词分割.
- 每句测试行, 都以右小括号)来结尾.
- 每个条件判断语句块都以 一对 分号结尾 ;;.
- case块以esac ( case 的反向拼写)结尾.
#!/bin/bash
# 测试字符串范围.
echo; echo "Hit a key, then hit return."
read Keypress
case "$Keypress" in
[[:lower:]] ) echo "Lowercase letter";;
[[:upper:]] ) echo "Uppercase letter";;
[0-9] ) echo "Digit";;
* ) echo "Punctuation, whitespace, or other";;
esac # 允许字符串的范围出现在[中括号]中,
#+ 或者出现在POSIX风格的[[双中括号中.
# 在这个例子的第一个版本中,
#+ 测试大写和小写字符串的工作使用的是
#+ [a-z] 和 [A-Z].
# 这种用法在某些特定场合的或某些Linux发行版中不能够正常工作.
# POSIX 的风格更具可移植性.
exit 0
#!/bin/bash
# 未经处理的地址资料
clear # 清屏.
echo " Contact List"
echo " ------- ----"
echo "Choose one of the following persons:"
echo
echo "[E]vans, Roland"
echo "[J]ones, Mildred"
echo "[S]mith, Julie"
echo "[Z]ane, Morris"
echo
read person
case "$person" in
# 注意, 变量是被""引用的.
"E" | "e" )
# 接受大写或者小写输入.
echo
echo "Roland Evans"
echo "4321 Floppy Dr."
echo "Hardscrabble, CO 80753"
echo "(303) 734-9874"
echo "(303) 734-9892 fax"
echo "revans@zzy.net"
echo "Business partner & old friend"
;;
# 注意, 每个选项后边都要以双分号;;结尾.
"J" | "j" )
echo
echo "Mildred Jones"
echo "249 E. 7th St., Apt. 19"
echo "New York, NY 10009"
echo "(212) 533-2814"
echo "(212) 533-9972 fax"
echo "milliej@loisaida.com"
echo "Ex-girlfriend"
echo "Birthday: Feb. 11"
;;
# 后边的 Smith 和 Zane 的信息在这里就省略了.
* )
# 默认选项.
# 空输入(敲回车RETURN), 也适用于这里.
echo
echo "Not yet in database."
;;
esac
echo
# 练习:
# -----
# 修改这个脚本, 让它能够接受多个输入,
#+ 并且能够显示多个地址.
exit 0
#! /bin/bash
#用来测试命令行参数
case "$1" in
"") echo "Usage: ${0##*/} <filename>"; exit $E_PARAM;; # 没有命令行参
数,
# 或者第一个
参数为空.
# 注意: ${0##*/} 是 ${var##pattern} 的一种替换形式. 得到的结果为$0.
-*) FILENAME=./$1;; # 如果传递进来的文件名参数($1)以一个破折号开头,
#+ 那么用./$1来代替.
#+ 这样后边的命令将不会把它作为一个选项来解释.
* ) FILENAME=$1;; # 否则, $1.
esac
#! /bin/bash
#命令行参数处理
while [ $# -gt 0 ]; do # 直到你用完所有的参数 . . .
case "$1" in
-d|--debug)
# 是 "-d" 或 "--debug" 参数?
DEBUG=1
;;
-c|--conf)
CONFFILE="$2"
shift
if [ ! -f $CONFFILE ]; then
echo "Error: Supplied file doesn't exist!"
exit $E_CONFFILE # 错误: 文件未发现.
fi
;;
esac
shift # 检查剩余的参数.
done
#!/bin/bash
# match-string.sh: 简单的字符串匹配
match_string ()
{
MATCH=0
NOMATCH=90
PARAMS=2 # 此函数需要2个参数.
BAD_PARAMS=91
[ $# -eq $PARAMS ] || return $BAD_PARAMS
case "$1" in
"$2") return $MATCH;;
* ) return $NOMATCH;;
esac
}
a=one
b=two
c=three
d=two
match_string $a # 参数个数错误.
echo $? # 91
match_string $a $b # 不匹配
echo $? # 90
match_string $b $d # 匹配
echo $? # 0
exit 0
select
select结构是建立菜单的另一种工具, 这种结构是从ksh中引入的.
select variable [in list ] do command ... break done
#!/bin/bash
PS3='Choose your favorite vegetable: ' # 设置提示符字串.
echo
select vegetable in "beans" "carrots" "potatoes" "onions"
"rutabagas"
do
echo
echo "Your favorite veggie is $vegetable."
echo "Yuck!"
echo
break # 如果这里没有 'break' 会发生什么?
done
exit 0
#!/bin/bash
PS3='Choose your favorite vegetable: '
echo
choice_of()
{
select vegetable
# [in list]被忽略, 所以'select'使用传递给函数的参数.
do
echo
echo "Your favorite veggie is $vegetable."
echo "Yuck!"
echo
break
done
}
choice_of beans rice carrots radishes tomatoes spinach
# $1 $2 $3 $4 $5 $6
# 传递给choice_of()的参数
exit 0
内部命令与内建命令
内建命令 指的就是包含在Bash工具包中的命令, 从字面意思上看就是 built in . 这主要是考虑到执行效 率的问题 -- 内建命令将比外部命令执行的更快, 一部分原因是因为外部命令通常都需要fork出一个单 独的进程来执行 -- 另一部分原因是特定的内建命令需要直接访问shell的内核部分.
I/O
-
echo
打印(到 stdout )一个表达式或者变量
echo Hello echo $a if echo "$VAR" | grep -q txt # if [[ $VAR = *txt* ]] then echo "$VAR contains the substring sequence \"txt\"" fi a=`echo "HELLO" | tr A-Z a-z`
$IFS (内部域分隔符) 一搬都会将 \n (换行符) 包含在它的空白字符集合中. Bash因此会根据 参数中的换行来分离 command 的输出, 然后echo. 最后echo将以空格代替换行来输出这些参数.
# 嵌入一个换行? echo "Why doesn't this string \n split on two lines?" # 上边这句的\n将被打印出来. 达不到换行的目的. # 让我们再试试其他方法. echo echo $"A line of text containing a linefeed." # 打印出两个独立的行(嵌入换行成功了). # 但是, 是否必须有"$"作为变量前缀? echo echo "This string splits on two lines." # 不, 并不是非有"$"不可. echo echo "---------------" echo echo -n $"Another line of text containing a linefeed." # 打印出两个独立的行(嵌入换行成功了). # 即使使用了-n选项, 也没能阻止换行. (译者注: -n 阻止了第2个换行)
-
printf
printf命令, 格式化输出, 是echo命令的增强版. 它是C语言 printf() 库函数的一个有限的变形, 并且在语法上有些不同
printf format-string ... parameter ...
#!/bin/bash # printf 示例 PI=3.14159265358979 DecimalConstant=31373 Message1="Greetings," Message2="Earthling." echo printf "Pi to 2 decimal places = %1.2f" $PI echo printf "Pi to 9 decimal places = %1.9f" $PI # 都能够正确的结束. printf "\n" # 打印一个换行, # 等价于 'echo' . . . printf "Constant = \t%d\n" $DecimalConstant # 插入一个 tab (\t). printf "%s %s \n" $Message1 $Message2 echo # ==========================================# # 模拟C函数, sprintf(). # 使用一个格式化的字符串来加载一个变量. echo Pi12=$(printf "%1.12f" $PI) echo "Pi to 12 decimal places = $Pi12" Msg=`printf "%s %s \n" $Message1 $Message2` echo $Msg; echo $Msg # 像我们所看到的一样, 现在'sprintf'可以 #+ 作为一个可被加载的模块, #+ 但是不具可移植性. exit 0
-
read
从 stdin 中"读取"一个变量的值, 也就是, 和键盘进行交互, 来取得变量的值. 使用 -a 参数可 以read数组变量
默认接受变量 $REPLY
#!/bin/bash # "Reading" 变量. echo -n "Enter the value of variable 'var1': " # -n 选项, 阻止换行. read var1 # 注意: 在var1前面没有'$', 因为变量正在被设置. echo "var1 = $var1" echo # 一个单独的'read'语句可以设置多个变量. echo -n "Enter the values of variables 'var2' and 'var3' (separated by a space or tab): " read var2 var3 echo "var2 = $var2 var3 = $var3" # 如果你只输入了一个值, 那么其他的变量还是处于未设置状态(null). exit 0
#!/bin/bash # arrow-detect.sh: 检测方向键, 和一些非打印字符的按键. # 感谢, Sandro Magi, 告诉了我们怎么做到这点. # -------------------------------------------- # 按键所产生的字符编码. arrowup='\[A' arrowdown='\[B' arrowrt='\[C' arrowleft='\[D' insert='\[2' delete='\[3' # -------------------------------------------- SUCCESS=0 OTHER=65 echo -n "Press a key... " # 如果不是上边列表所列出的按键, 可能还是需要按回车. (译者注: 因为一般按键是一个 字符) read -n3 key # 读取3个字符. echo -n "$key" | grep "$arrowup" # 检查输入字符是否匹配. if [ "$?" -eq $SUCCESS ] then echo "Up-arrow key pressed." exit $SUCCESS fi echo -n "$key" | grep "$arrowdown" if [ "$?" -eq $SUCCESS ] then echo "Down-arrow key pressed." exit $SUCCESS fi echo -n "$key" | grep "$arrowrt" if [ "$?" -eq $SUCCESS ] then echo "Right-arrow key pressed." exit $SUCCESS fi echo -n "$key" | grep "$arrowleft" if [ "$?" -eq $SUCCESS ] then echo "Left-arrow key pressed." exit $SUCCESS fi echo -n "$key" | grep "$insert" if [ "$?" -eq $SUCCESS ] then echo "\"Insert\" key pressed." exit $SUCCESS fi echo -n "$key" | grep "$delete" if [ "$?" -eq $SUCCESS ] then echo "\"Delete\" key pressed." exit $SUCCESS fi echo " Some other key pressed." exit $OTHER
文件系统
-
cd
cd, 修改目录命令, 在脚本中用的最多的时候就是当命令需要在指定目录下运行时, 需要用它来 修改当前工作目录.
-P (physical)选项对于cd命令的意义是忽略符号链接. cd - 将会把工作目录修改至$OLDPWD, 也就是之前的工作目录.
-
pwd
打印出当前的工作目录
-
pushd, popd, dirs
-
pushd
把路径 dir-name 压入目录栈, 同时修改当前目录到 dir-name .
-
popd
将目录栈最上边的目录弹出, 同时将当前目录修改为刚弹出来的那个目录.
-
dirs
列出所有目录栈的内容
一个成功的pushd或者popd将会自动调用dirs命令
-
-
let
将执行变量的算术操作
在许多情况下, 它被看作是复杂的expr命令的一个简化版本.
#!/bin/bash
echo
let a=11 # 与 'a=11' 相同
let a=a+5 # 等价于 let "a = a + 5"
# (双引号和空格是这句话更具可读性.)
echo "11 + 5 = $a" # 16
let "a <<= 3" # 等价于 let "a = a << 3"
echo "\"\$a\" (=16) left-shifted 3 places = $a"
# 128
let "a /= 4" # 等价于 let "a = a / 4"
echo "128 / 4 = $a" # 32
let "a -= 5" # 等价于 let "a = a - 5"
echo "32 - 5 = $a" # 27
let "a *= 10" # 等价于 let "a = a * 10"
echo "27 * 10 = $a" # 270
let "a %= 8" # 等价于 let "a = a % 8"
echo "270 modulo 8 = $a (270 / 8 = 33, remainder $a)"
# 6
echo
exit 0
-
eval
eval arg1 [arg2] ... [argN] 将表达式中的参数, 或者表达式列表, 组合起来, 然后 评价 它们(译者注: 通常用来执行). 任何 被包含在表达示中的变量都将被扩展. 结果将会被转化到命令中. 如果你想从命令行中或者是从 脚本中产生代码, 那么这个命令就非常有用了.
#!/bin/bash y=`eval ls -l` # 与 y=`ls -l` 很相似 echo $y #+ 但是换行符将会被删除, 因为"echo"的变量未被""引用. echo echo "$y" # 用""将变量引用起来, 换行符就不会被空格替换了. echo; echo y=`eval df` # 与 y=`df` 很相似 echo $y #+ 换行符又被空格替换了. # 当没有LF(换行符)出现时, 如果使用"awk"这样的工具来分析输出的结果, #+ 应该能更容易一些. echo echo "===========================================================" echo # 现在,来看一下怎么用"eval"命令来"扩展"一个变量 . . . for i in 1 2 3 4 5; do eval value=$i # value=$i 具有相同的效果, 在这里并不是非要使用"eval"不可. # 一个缺乏特殊含义的变量将被评价为自身 -- 也就是说, #+ 这个变量除了能够被扩展成自身所表示的字符外, 不能被扩展成任何其他的含义. echo $value done echo echo "---" echo for i in ls df; do value=eval $i # value=$i 在这里就与上边这句有了本质上的区别. # "eval" 将会评价命令 "ls" 和 "df" . . . # 术语 "ls" 和 "df" 就具有特殊含义, #+ 因为它们被解释成命令, #+ 而不是字符串本身. echo $value done exit 0
-
set
set命令用来修改内部脚本变量的值. 它的一个作用就是触发选项标志位来帮助决定脚本的行为. 另一个作用是以一个命令的结果( set
command
)来重新设置脚本的位置参数. 脚本将会从命令的 输出中重新分析出位置参数.不使用任何选项或参数来调用set命令的话, 将会列出所有的环境变量和其他所有的已经初始化过 的变量.
#!/bin/bash # script "set-test" # 使用3个命令行参数来调用这个脚本, # 比如, "./set-test one two three". echo echo "Positional parameters before set \`uname -a\` :" echo "Command-line argument #1 = $1" echo "Command-line argument #2 = $2" echo "Command-line argument #3 = $3" set `uname -a` # 把`uname -a`的命令输出设置 # 为新的位置参数. echo $_ # unknown(译者注: 这要看你的uname -a输出了,这句打印出的就是 输出的最后一个单词.) # 在脚本中设置标志. echo "Positional parameters after set \`uname -a\` :" # $1, $2, $3, 等等. 这些位置参数将被重新初始化为`uname -a`的结果 echo "Field #1 of 'uname -a' = $1" echo "Field #2 of 'uname -a' = $2" echo "Field #3 of 'uname -a' = $3" echo --- echo $_ # --- echo exit 0
-
unset
unset命令用来删除一个shell变量, 这个命令的效果就是把这个变量设为 null . 注意: 这个命令 对位置参数无效.
#!/bin/bash # unset.sh: Unset 一个变量. variable=hello # 初始化. echo "variable = $variable" unset variable # Unset. # 与 variable= 效果相同. echo "(unset) variable = $variable" # $variable 设为 null. exit 0
-
export
export命令将会使得被export的变量在所运行脚本(或shell)的所有子进程中都可用. 不幸的是, 没有办法 将 变量 export 到父进程中, 这里所指的父进程就是调用这个脚本的脚本或shell. 关 于export命令的一个重要的用法就是使用在启动文件中, 启动文件用来初始化和设置环境变量, 这样, 用户进程才能够访问环境变量.
#!/bin/bash # 这是"求列的和"脚本的另外一个版本(col-totaler.sh) #+ 那个脚本可以把目标文件中的指定的列上的所有数字全部累加起来,求和. # 这个版本将把一个变量通过export的形式传递到'awk'中 . . . #+ 并且把awk脚本放到一个变量中. ARGS=2 E_WRONGARGS=65 if [ $# -ne "$ARGS" ] # 检查命令行参数的个数. then echo "Usage: `basename $0` filename column-number" exit $E_WRONGARGS fi filename=$1 column_number=$2 #===== 上边的这部分,与原始脚本完全一样 =====# export column_number # 将列号export出来, 这样后边的进程就可用了. # ----------------------------------------------- awkscript='{ total += $ENVIRON["column_number"] } END { print total }' # 是的, 变量可以保存awk脚本. # ----------------------------------------------- # 现在, 运行这个awk脚本. awk "$awkscript" "$filename" exit 0
-
declare, typeset
declare和typeset命令被用来指定或限制变量的属性.
-
readonly
与declare -r作用相同, 设置变量的只读属性, 或者可以认为这个变量就是一个常量.
-
getopts
这个命令是分析传递到脚本中命令行参数的最强力的工具. 这个命令与外部命令getopt, 还有 C 语言中的库函数getopt的作用是相同的. 它允许传递和连接多个选项 [2] 到脚本中, 并且 能够分配多个参数到脚本中
#!/bin/bash # 练习 getopts 和 OPTIND # 在Bill Gradwohl的建议下, 这个脚本于 10/09/03 被修改. # 在这里我们将学习如何使用 'getopts' 来处理脚本的命令行参数. # 参数被作为"选项"(标志)来解析, 并且对选项分配参数. # 试一下, 使用如下方法来调用这个脚本 # 'scriptname -mn' # 'scriptname -oq qOption' (qOption 可以是任意的哪怕有些诡异字符的字符串.) # 'scriptname -qXXX -r' # # 'scriptname -qr' - 意外的结果, "r" 将被看成是选项 "q" 的参数. # 'scriptname -q -r' - 意外的结果, 同上. # 'scriptname -mnop -mnop' - 意外的结果 # (OPTIND在选项刚传递进来的地方是不可靠的). # (译者注: 也就是说OPTIND只是一个参数指针, 指向下一个参数的位置. # 比如: -mnop 在mno处理的位置OPTION都为1, 而到p的处理就变成2, # -m -n -o 在m的时候OPTION为2, 而n为3, o为4, # 也就是说它总指向下一个位置). # # 如果选项需要一个参数的话("flag:"), 那么它将获取 #+ 命令行上紧挨在它后边的任何字符. NO_ARGS=0 E_OPTERROR=65 if [ $# -eq "$NO_ARGS" ] # 不带命令行参数就调用脚本? then echo "Usage: `basename $0` options (-mnopqrs)" exit $E_OPTERROR # 如果没有参数传递进来, 那么就退出脚本, 并且解释 此脚本的用法. fi # 用法: scriptname -options # 注意: 必须使用破折号 (-) while getopts ":mnopq:rs" Option do case $Option in m ) echo "Scenario #1: option -m- [OPTIND=${OPTIND}]";; n | o ) echo "Scenario #2: option -$Option- [OPTIND=${OPTIND}]";; p ) echo "Scenario #3: option -p- [OPTIND=${OPTIND}]";; q ) echo "Scenario #4: option -q-\ with argument \"$OPTARG\" [OPTIND=${OPTIND}]";; # 注意, 选项'q'必须分配一个参数, #+ 否则, 默认将失败. r | s ) echo "Scenario #5: option -$Option-";; * ) echo "Unimplemented option chosen.";; # 默认情况的处理 esac done shift $(($OPTIND - 1)) # (译者注: shift命令是可以带参数的, 参数就是移动的个数) # 将参数指针减1, 这样它将指向下一个参数. # $1 现在引用的是命令行上的第一个非选项参数, #+ 如果有一个这样的参数存在的话. exit 0 # 就像 Bill Gradwohl 所描述的, # "getopts机制允许指定一个参数, #+ 但是scriptname -mnop -mnop就是一种比较特殊的情况, #+ 因为在使用OPTIND的时候, 没有可靠的方法来区分到底传递进来了什么东西."
脚本行为
-
source, . (点 命令)
当在命令行中调用的时候, 这个命令将会执行一个脚本. 当在脚本中调用的时候, source file- name 将会加载 file-name 文件. sourc一个文件(或点命令)将会在脚本中 引入 代码, 并将这些代码 附加到脚本中(与 C 语言中的 #include 指令效果相同). 最终的结果就像是在使用"source"的行上插 入了相应文件的内容. 在多个脚本需要引用相同的数据, 或者需要使用函数库的情况下, 这个命 令非常有用.
#!/bin/bash . data-file # 加载一个数据文件. # 与"source data-file"效果相同, 但是更具可移植性. # 文件"data-file"必须存在于当前工作目录, #+ 因为这个文件是使用'basename'来引用的. # 现在, 引用这个文件中的一些数据. echo "variable1 (from data-file) = $variable1" echo "variable3 (from data-file) = $variable3" let "sum = $variable2 + $variable4" echo "Sum of variable2 + variable4 (from data-file) = $sum" echo "message1 (from data-file) is \"$message1\"" # 注意: 将双引号转义 print_message This is the message-print function in the data-file. exit 0
-
exit
无条件的停止一个脚本的运行. exit命令可以随意的取得一个整数参数, 然后把这个参数作为这 个脚本的退出状态码. 在退出一个简单脚本的时候, 使用 exit 0 的话, 是种好习惯, 因为这表明 成功运行.
如果不带参数调用exit命令退出的话, 那么退出状态码将会将会是脚本中最 后一个命令的退出状态码. 等价于exit $?.
-
exec
这个shell内建命令将使用一个特定的命令来取代当前进程. 一般的当shell遇到一个命令, 它 会forks off一个子进程来真正的运行命令. 使用exec内建命令, shell就不会fork了, 并且命令 的执行将会替换掉当前shell. 因此, 在脚本中使用时, 一旦exec所执行的命令执行完毕, 那么它 就会强制退出脚本.
#!/bin/bash exec echo "Exiting \"$0\"." # 脚本应该在这里退出. # ---------------------------------- # The following lines never execute. echo "This echo will never echo." exit 99 # 脚本是不会在这里退出的. # 脚本退出后会使用'echo $?' #+ 来检查一下退出码. # 一定 *不是* 99.
-
shopt
允许shell在空闲时修改shell选项
它经常出现在启动文件中, 但在一般脚本中也常出现
-
caller
将caller命令放到函数中, 将会在 stdout 上打印出函数的 调用者 信息.
#!/bin/bash function1 () { # 在 function1 () 内部. caller 0 # 显示调用者信息. } function1 # 脚本的第9行. # 9 main test.sh # ^ 函数调用者所在的行号. # ^^^^ 从脚本的"main"部分开始调用的. # ^^^^^^^ 调用脚本的名字. caller 0 # 没效果, 因为这个命令不在函数中
命令
-
true
这是一个返回(零)成功退出状态码的命令, 但是除此之外不做任何事.
# 死循环 while true # 这里的true可以用":"来替换 do operation-1 operation-2 ... operation-n # 需要一种手段从循环中跳出来, 或者是让这个脚本挂起. done
-
false
这是一个返回失败退出状态码的命令, 但是除此之外不做任何事.
# 测试 "false" if false then echo "false evaluates \"true\"" else echo "false evaluates \"false\"" fi # 失败会显示 "false" # while "false" 循环 (空循环) while false do # 这里面的代码不会被执行. operation-1 operation-2 ... operation-n # 什么事都没发生! done
-
type [cmd]
与外部命令which很相像, type cmd将会给出"cmd"的完整路径. 与which命令不同的是, type命 令是Bash内建命令. -a 是type命令的一个非常有用的选项, 它用来鉴别参数是 关键字 还是 内建命令 , 也可以用来定位同名的系统命令
bash$ type '[' [ is a shell builtin bash$ type -a '[' [ is a shell builtin [ is /usr/bin/[
-
hash [cmd]
在shell的hash表中, 记录指定命令的路径名, 所以在shell或脚本中调用这个命令的话, 就 不需要再在 $PATH 中重新搜索这个命令了. 如果不带参数的调用hash命令, 它将列出所有已经被 hash的命令. -r 选项会重新设置hash表.
-
bind
bind内建命令用来显示或修改 readline的键绑定
-
help
获得shell内建命令的一个小的使用总结. 与whatis命令比较象, 但help命令是内建命令.
bash$ help exit exit: exit [n] Exit the shell with a status of N. If N is omitted, the exit status is that of the last command executed.
作业控制命令
command | description |
---|---|
jobs | 在后台列出所有正在运行的作业, 给出作业号. 并不象ps命令那么有用. |
disown | 从shell的激活作业表中删除作业 |
fg, bg | fg命令可以把一个在后台运行的作业放到前台来运行. 而bg命令将会重新启动一个挂起的作业,并且在后台运行它. 如果使用fg或者bg命令的时候没有指定作业号, 那么默认将对当前正在运行的作业进行操作. |
wait | 停止脚本的运行, 直到后台运行的所有作业都结束为止, 或者如果传递了作业号或进程号为参数的话, 那么就直到指定作业结束为止. 返回等待命令的退出状态码. 你可以使用wait命令来防止在后台作业没完成(这会产生一个孤儿进程)之前退出脚 |
suspend | 这个命令的效果与Control-Z很相像, 但是它挂起的是这个shell(这个shell的父进程应该在合适的时候重新恢复它). |
logout | 退出一个已经登陆上的shell, 也可以指定一个退出状态码. |
times | 给出执行命令所占用的时间, 使用如下的形式进行输出 |
kill | 通过发送一个适当的 结束 信号, 来强制结束一个进程 kill -l 将会列出所有信号. kill -9 是"必杀"命令, |
killall | killall命令将会通过 名字 来杀掉一个正在运行的进程, 而不是通过进程ID. 如果某个特定的命令有多个实例正在运行, 那么执行一次 killall 命令就会把这些实例 全部 杀掉 |
command | 对于命令"COMMAND", command COMMAND会直接禁用别名和函数的查找. |
builtin | 当你使用builtin BUILTIN_COMMAND的时候, 只会调用shell内建命令"BUILTIN_COMMAND", 而暂时禁用同名的函数, 或者是同名的扩展命令. |
enable | 这个命令或者禁用内建命令或者恢复内建命令. 比如, enable -n kill将禁用内建命令kill, 所<以当我们调用kill命令时, 使用的将是 /bin/kill 外部命令. -a 选项会enable所有作为参数的shell内建命令, 不管它们之前是否被enable了. (译者注: 如果不带参数的调用enable -a, 那么会恢复所有内建命令.) -f filename 选项将会从适当的编译过的目标文件 [1] 中, 让enable命令以共享库的形式来加载内建命令. |
autoload | 一个带有"autoload"声明的函数, 在它第一次被调用 的时候才会被加载. 这样做是为了节省系统资源. |
外部过滤器, 程序和命令
标准的 UNIX 命令使得 shell 脚本更加灵活.通过简单的编程结构把shell指令和系统命令结 合起来, 这才是脚本能力的所在.
基本命令---必须要掌握的初级命令
command | description | usage/sample |
---|---|---|
ls | "列出"文件的基本命令 | ls -R 递归目录 ls -S 按照文件尺寸列出所有文件 ls -t 按照修改时间来列出文件 ls -i 显示文件的inode 参数可组合 |
cat ,tac | 把文件的内容输出到 stdout当与重定向 操作符(>或>>), 一般都是用来将多个文件连接起来. tac命令, 就是 cat 命令的反转, 这个命令将会从文件结尾部分列出文件的内容 |
cat filename # 打印出文件内容 cat file.1 file.2 file.3 > file.123 # 把三个文件连接到一个文件中 |
rev | 把每一行中的内容反转 | rev file1.txt |
cp | 文件拷贝 | cp src dst cp -u 更新 |
mv | 文件移动,等价 cp 与 rm的组合 | mv src dst |
rm | 删除(清除)一个或多个文件 | rm -f 强制 rm -r 递归 |
rmdir | 删除目录. | rmdir dirname |
mkdir | 生成目录, 创建一个空目录 | mkdir -p dir1/dir2/file 自动生成不存在的目录 |
chmod | 修改一个现存文件的属性 | chmod +x filename chmod u+s filenam chmod 644 filename chmod 1777 dir-name |
chattr | 修改文件属性 使用chattr命令设置过属性的文件将不会显示在文件列表中 |
chattr +i file1.txt |
ln | 创建文件链接 | ln -s oldfile newfile # -s 为符号链接 不见是硬链接文件系统必须存在 |
man, info | 查看系统命令或安装工具的手册和信息 | man/info cmd |
复杂命令--更高级的用户命令
find
-exec COMMAND ;
在每一个find匹配到的文件执行 COMMAND 命令. 命令序列以;结束
如果 COMMAND 中包含{}, 那么find命令将会用所有匹配文件的路径名来替换"{"}
find ~/ -name '*.txt'
#从用户的 home 目录中查找 所有txt结尾文件.
find ~/ -name 'core*' -exec rm {} \;
# 从用户的 home 目录中删除所有的 core dump文件.
find /home/bozo/projects -mtime 1
# 列出最后一天被修改的
#+ 在/home/bozo/projects目录树下的所有文件.
#
# mtime = 目标文件最后修改的时间
# ctime = 修改后的最后状态(通过'chmod'或其他方法)
# atime = 最后访问时间
DIR=/home/bozo/junk_files
find "$DIR" -type f -atime +5 -exec rm {} \;
# ^^
# 大括号就是"find"命令用来替换目录的地方.
#
# 删除至少5天内没被访问过的
#+ "/home/bozo/junk_files" 中的所有文件.
#
# "-type filetype", where
# f = regular file
# d = directory, etc.
find /etc -exec grep '[0-9][0-9]*[.][0-9][0-9]*[.][0-9][0-9]*[.][0-
][0-9]*' {} \;
# 在 /etc 目录中的文件找到所所有包含 IP 地址(xxx.xxx.xxx.xxx) 的文件.
# 可能会查找到一些多余的匹配. 我们如何去掉它们呢?
# 或许可以使用如下方法:
find /etc -type f -exec cat '{}' \; | tr -c '.[:digit:]' '\n' \
| grep '^[^.][^.]*\.[^.][^.]*\.[^.][^.]*\.[^.][^.]*$'
xargs
这是给命令传递参数的一个过滤器, 也是组合多个命令的一个工具. 它把一个数据流分割为一些足 够小的块, 以方便过滤器和命令进行处理. 由此这个命令也是后置引用的一个强有力的替换. 当在 一般情况下使用过多参数的命令替换都会产生失败的现象, 这时候使用xargs命令来替换, 一般都 能成功. 一般的, xargs从 stdin 或者管道中读取数据, 但是它也能够从文件的输出中读取数 据. xargs的默认命令是echo. 这意味着通过管道传递给xargs的输入将会包含换行和空白, 不过通 过xargs的处理, 换行和空白将被空格取代.
#!/bin/bash
# copydir.sh
# 将当前目录下($PWD)的所有文件都拷贝到
#+ 命令行所指定的另一个目录中去.
E_NOARGS=65
if [ -z "$1" ] # 如果没有参数传递进来那就退出.
then
echo "Usage: `basename $0` directory-to-copy-to"
exit $E_NOARGS
fi
ls . | xargs -i -t cp ./{} $1
# ^^ ^^ ^^
# -t 是 "verbose" (输出命令行到stderr) 选项.
# -i 是"替换字符串"选项.
# {} 是输出文本的替换点.
# 这与在"find"命令中使用{}的情况很相像.
#
# 列出当前目录下的所有文件(ls .),
#+ 将 "ls" 的输出作为参数传递到 "xargs"(-i -t 选项) 中,
#+ 然后拷贝(cp)这些参数({})到一个新目录中($1).
#
# 最终的结果和下边的命令等价,
#+ cp * $1
#+ 除非有文件名中嵌入了"空白"字符.
exit 0
#!/bin/bash
# wf2.sh: 分析一个文本文件中单词出现的频率.
# 使用 'xargs' 将文本行分解为单词.
# 与后边的 "wf.sh" 脚本相比较.
# 检查命令行上输入的文件.
ARGS=1
E_BADARGS=65
E_NOFILE=66
if [ $# -ne "$ARGS" ]
# 纠正传递到脚本中的参数个数?
then
echo "Usage: `basename $0` filename"
exit $E_BADARGS
fi
if [ ! -f "$1" ] # 检查文件是否存在.
then
echo "File \"$1\" does not exist."
exit $E_NOFILE
fi
#####################################################################
cat "$1" | xargs -n1 | \
# 列出文件, 每行一个单词.
tr A-Z a-z | \
# 将字符转换为小写.
sed -e 's/\.//g' -e 's/\,//g' -e 's/ /\
/g' | \
# 过滤掉句号和逗号,
#+ 并且将单词间的空格修改为换行,
sort | uniq -c | sort -nr
# 最后统计出现次数, 把数字显示在第一列, 然后显示单词, 并按数字排序.
#####################################################################
# 这个例子的作用与"wf.sh"的作用是一样的,
#+ 但是这个例子比较臃肿, 并且运行起来更慢一些(为什么?).
exit 0
#!/bin/bash
# kill-byname.sh: 通过名字kill进程.
# 与脚本kill-process.sh相比较.
# 例如,
#+ 试一下 "./kill-byname.sh xterm" --
#+ 并且查看你系统上的所有xterm都将消失.
# 警告:
# -----
# 这是一个非常危险的脚本.
# 运行它的时候一定要小心. (尤其是以root身份运行时)
#+ 因为运行这个脚本可能会引起数据丢失或产生其他一些不好的效果.
E_BADARGS=66
if test -z "$1" # 没有参数传递进来?
then
echo "Usage: `basename $0` Process(es)_to_kill"
exit $E_BADARGS
fi
PROCESS_NAME="$1"
ps ax | grep "$PROCESS_NAME" | awk '{print $1}' | xargs -i kill {}
&>/dev/null
# ^^ ^^
# -----------------------------------------------------------
# 注意:
# -i 参数是xargs命令的"替换字符串"选项.
# 大括号对的地方就是替换点.
# 2&>/dev/null 将会丢弃不需要的错误消息.
# -----------------------------------------------------------
exit $?
# 在这个脚本中, "killall"命令具有相同的效果,
#+ 但是这么做就没有教育意义了.
expr
通用求值表达式: 通过给定的操作(参数必须以空格分开)连接参数, 并对参数求值. 可以使算术操 作, 比较操作, 字符串操作或者是逻辑操作.
expr 3 + 5 返回 8 expr 5 % 3 返回2 expr 1 / 0 返回错误消息, expr: division by zero 不允许非法的算术操作. expr 5 \* 3 返回15 在算术表达式expr中使用乘法操作时, 乘法符号必须被转义. y=`expr $y + 1` 增加变量的值, 与 let y=y+1 和 y=$(($y+1)) 的效果相同. 这是使用算术表达式的一个例子. z=`expr substr $string $position $length` 在位置$position上提取$length长度的子串.
#!/bin/bash
# 展示一些使用'expr'的例子
# ========================
echo
# 算术 操作
# ---- ----
echo "Arithmetic Operators"
echo
a=`expr 5 + 3`
echo "5 + 3 = $a"
a=`expr $a + 1`
echo
echo "a + 1 = $a"
echo "(incrementing a variable)"
a=`expr 5 % 3`
# 取模操作
echo
echo "5 mod 3 = $a"
echo
echo
# 逻辑 操作
# ---- ----
# true返回1, false返回0,
#+ 而Bash的使用惯例则相反.
echo "Logical Operators"
echo
x=24
y=25
b=`expr $x = $y` # 测试相等.
echo "b = $b" # 0 ( $x -ne $y )
echo
a=3
b=`expr $a \> 10`
echo 'b=`expr $a \> 10`, therefore...'
echo "If a > 10, b = 0 (false)"
echo "b = $b" # 0 ( 3 ! -gt 10 )
echo
b=`expr $a \< 10`
echo "If a < 10, b = 1 (true)"
echo "b = $b" # 1 ( 3 -lt 10 )
echo
# 注意转义操作.
b=`expr $a \<= 3`
echo "If a <= 3, b = 1 (true)"
echo "b = $b" # 1 ( 3 -le 3 )
# 也有 "\>=" 操作 (大于等于).
echo
echo
# 字符串 操作
# ------ ----
echo "String Operators"
echo
a=1234zipper43231
echo "The string being operated upon is \"$a\"."
# 长度: 字符串长度
b=`expr length $a`
echo "Length of \"$a\" is $b."
# 索引: 从字符串的开头查找匹配的子串,
# 并取得第一个匹配子串的位置.
b=`expr index $a 23`
echo "Numerical position of first \"2\" in \"$a\" is \"$b\"."
# substr: 从指定位置提取指定长度的字串.
b=`expr substr $a 2 6`
echo "Substring of \"$a\", starting at position 2,\
and 6 chars long is \"$b\"."
# 'match' 操作的默认行为就是从字符串的开始进行搜索,
#+ 并匹配第一个匹配的字符串.
#
# 使用正则表达式
b=`expr match "$a" '[0-9]*'` # 数字的个数.
echo Number of digits at the beginning of \"$a\" is $b.
b=`expr match "$a" '\([0-9]*\)'` # 注意, 需要转义括号
# == == + 这样才能触发子串的匹配.
echo "The digits at the beginning of \"$a\" are \"$b\"."
echo
exit 0
时间日期
date
直接调用date命令就会把日期和时间输出到 stdout 上. 这个命令有趣的地方在于它的格式化和分
析选项上.
#!/bin/bash
# 练习'date'命令
echo "The number of days since the year's beginning is `date +%j`."
# 需要在调用格式的前边加上一个'+'号.
# %j用来给出今天是本年度的第几天.
echo "The number of seconds elapsed since 01/01/1970 is `date
+%s`."
# %s将产生从"UNIX 元年"到现在为止的秒数,
#+ 但是这东西现在还有用么?
prefix=temp
suffix=$(date +%s) # 'date'命令的"+%s"选项是GNU特性.
filename=$prefix.$suffix
echo $filename
# 这是一种非常好的产生"唯一"临时文件的办法,
#+ 甚至比使用$$都强.
# 如果想了解'date'命令的更多选项, 请查阅这个命令的man页.
exit 0
zdump
时区dump: 查看特定时区的当前时间.
zdump EST
time
输出统计出来的命令执行的时间. time ls -l
touch
更新文件被访问或修改的时间的工具, 时间可以是当前系统的时间,也可以是指 定的时间
也用来产生一个新文件. 命令 touch zzz 将产生一个 zzz 为名字的0字节长度文 件, 当然前提是 zzz 文件不存在. 为了存储时间信息, 就需要一个时间戳为空的文件, 比如当你想 跟踪一个工程的修改时间的时候, 这就非常有用了.
touch命令等价于 : >> newfile 或 >> newfile (对于一个普通文件).
at
at命令是一个作业控制命令, 用来在指定时间点上执行指定的命令集合. 它有点像cron命令, 然 而, at命令主要还是用来执行那种一次性执行的命令集合.
batch
batch作业控制命令与at令的行为很相像, 但是batch命令被用来在系统平均负载量降到 .8 以下时 执行一次性的任务. 与at命令相似的是, 它也可以使用 -f 选项来从文件中读取命令.
cal
从 stdout 中输出一个格式比较整齐的日历. 既可以指定当前年度, 也可以指定过去或将来的某个 年度.
sleep/usleep
这个命令与一个等待循环的效果一样. 你可以指定需要暂停的秒数, 这段时间将什么都不干. 当 一个后台运行的进程需要偶尔检测一个事件时, 这个功能很有用. 也可用于计时.
sleep 3 #暂停3秒
sleep 3 h
usleep 同sleep ,但时间单位为微妙
usleep 30 # 暂停30微秒.
hwclock, clock
hwclock命令可以访问或调整硬件时钟. 这个命令的一些选项需要具有root权限. 在系统启动的时 候, /etc/rc.d/rc.sysinit , 会使用hwclock来从硬件时钟中读取并设置系统时间. clock命令与hwclock命令完全相同.
文本处理 P209
command | description | sample/usage |
---|---|---|
sort | 文件排序, 通常用在管道中当过滤器来使用. 这个命令可以依据指定的关键字或指定的字符位置, 对文 件行进行排序. |
|
tsort | 拓扑排序, 读取以空格分隔的有序对, 并且依靠输入模式进行排序. | |
uniq | 这个过滤器将会删除一个已排序文件中的重复行. 这个命令经常出现在sort命令的管道后边 | cat list-1 list-2 list-3 -c 统计出现行数 |
expand, unexpand | expand命令将会把每个tab转化为一个空格. 这个命令经常用在管道中. unexpand命令将会把每个空格转化为一个tab. 效果与expand命令相反. |
|
cut | 一个从文件中提取特定域的工具. 这个命令与awk中使用的 print $N 命令很相似 | |
paste | 将多个文件, 以每个文件一列的形式合并到一个文件中, 合并后文件中的每一列就是原来的一个文件. 与cut结合使用, 经常用于创建系统log文件. |
|
join | 这个命令与paste命令属于同类命令. 但是它能够完成某些特殊的目地. 这个强力工具能够以一种特殊 的形式来合并两个文件, 这种特殊的形式本质上就是一个关联数据库的简单版本. |
|
head | 把文件的头部内容打印到 stdout 上(默认为 10 行, 可以自己修改) | |
tail | 将一个文件结尾部分的内容输出到 stdout 中(默认为 10 行). 通常用来跟踪一个系统logfile的修改情况, 如果使用 -f 选项的话, 这个命令将会继续显示添加到文件中的行. |
|
grep | 使用正则表达式的一个多用途文本搜索工具. | |
look | look命令与grep命令很相似, 但是这个命令只能做"字典查询", 也就是它所搜索的文件必须是已经排过序的单词列表. | |
sed | 交互式的"流编辑器", 在批处理模式下, 允许使用多个ex命令. 你会发现它在shell脚本中非常有用. | |
awk | 可编程的文件提取器和文件格式化工具, 在结构化的文本文件中, 处理或提取特定域(特定列)具有非常 好的表现. 它的语法与C语言很类似. |
|
wc | wc 可以统计文件或I/O流中的"单词数量": | |
tr | 字符转换过滤器. | |
fold | 将输入按照指定宽度进行折行. 这里有一个非常有用的选项 -s , 这个选项可以使用空格进行断行 | |
fmt | 一个简单的文件格式器, 通常用在管道中, 将一个比较长的文本行输出进行"折行". | |
col | 这个命令用来滤除标准输入的反向换行符号. 这个工具还可以将空白用等价的tab来替换 | |
column | 列格式化工具. 通过在合适的位置插入tab, 这个过滤工具会将列类型的文本转化为"易于打印"的表格 式进行输出. |
|
colrm | 列删除过滤器. 这个工具将会从文件中删除指定的列(列中的字符串)并且写到文件中, 如果指定的列不 存在, 那么就回到 stdout . |
|
nl | 计算行号过滤器. nl filename 将会把 filename 文件的所有内容都输出到 stdout 上, 但是会在每个非空行 的前面加上连续的行号. |
|
pr | 格式化打印过滤器. 这个命令会将文件(或 stdout )分页, 将它们分成合适的小块以便于硬拷贝打印或者 在屏幕上浏览. |
|
gettext | GNU gettext包是专门用来将程序的输出翻译或者本地化为不同国家语言的工具集 | |
msgfmt | 一个产生二进制消息目录的程序. 这个命令主要用来本地化. | |
iconv | 一个可以将文件转化为不同编码格式(字符集)的工具. 这个命令主要用来本地化. | |
recode | 可以认为这个命令是上边iconv命令的专业版本. | |
TeX, gs | eX和Postscript都是文本标记语言, 用来对打印和格式化的视频显示进行预拷贝. | |
enscript | 将纯文本文件转换为PostScript的工具 | |
groff, tbl, eqn | 另一种文本标记和显示格式化语言是groff. 这是一个对传统UNIX roff/troff显示和排版包的GNU增强 版本. Man页 使用的就是groff. |
|
lex, yacc | lex是用于模式匹配的词汇分析产生程序. 在Linux系统上这个命令已经被flex取代了. yacc工具基于一系列的语法规范, 产生一个语法分析器. 在Linux系统上这个命令已经被bison取代了. |
文件与归档
-
归档命令
-
tar
标准的UNIX归档工具.
-c 创建(一个新的归档文件) -x 解压文件(从存在的归档文件中) --delete 删除文件(从存在的归档文件中) 这个选项不能用于磁带类型设备. -r 将文件添加到现存的归档文件的尾部 -A 将 tar 文件添加到现存的归档文件的尾部 -t 列出现存的归档文件中包含的内容 -u 更新归档文件 -d 使用指定的文件系统, 比较归档文件 -z 用gzip压缩归档文件 (压缩还是解压, 依赖于是否组合了 -c 或 -x )选项 -j 用bzip2压缩归档文件
-
shar
Shell归档工具
-
ar
创建和操作归档文件的工具, 主要在对二进制目标文件打包成库时才会用到
-
rpm
Red Hat包管理器 , 或者说rpm工具提供了一种对源文件或二进制文件进行打包的方法. 除此之外, 它还包括安装命令, 并且还检查包的完整性
rpm -i package_name.rpm #对于安装一个包来说就足够 rpm -qa #列出给定系统上所有安装了的 rpm 包 rpm -qa package_name #会列出与给定名字 package_name 相匹配的包.
-
cpio
已经被tar/gzip所替代了
-
rpm2cpio
从rpm归档文件中解出一个cpio归档文件
-
-
压缩命令
-
gzip ,gunzip
标准的GNU/UNIX压缩工具, 取代了比较差的compress命令
-
bzip2, bunzip2
用来压缩的一个可选的工具, 通常比gzip命令压缩率更高(所以更慢), 适用于比较大的文件
-
compress ,uncompress
私有的压缩工具, 一般的商业UNIX发行版都会有这个工具.更有效率的gzip已替代
-
sq , unsq
只能工作于排过序的ASCII单词列表的过滤器.
-
zip ,unzip
跨平台的文件归档和压缩工具,
-
uarc, unarj, unrar
解档那些用DOS下的 arc.exe , arj.exe , 和 rar.exe 程序进行归档的文件.
-
-
文件信息
-
file
确定文件类型的工具.
-
which
which command-xxx将会给出"command-xxx"的完整路径.
-
whereis
与which很相似, whereis command-xxx不只会给出"command-xxx"的完整路径, 而且还会给出这个命令的 man页 的完整路径.
-
whatis
whatis filexxx将会在 whatis 数据库中查询"filexxx". 当你想确认系统命令和重要的配置文件的时候, 这个命令就非常重要了. 可以把这个命令认为是一个简单的man命令.
-
vdir
显示详细的目录列表. 与ls -l的效果相似.
-
locate,slocate
在预先建立好的档案数据库中查询文件
slocate命令是locate的安全版
-
readlink
显示符号链接所指向的文件.
-
strings
在二进制或数据文件中找出可打印字符
-
-
Comparision
-
diff ,path (二进制 bsdiff,bspatch)
diff 产生区别文件
patch: 灵活的版本工具. 给出一个用diff命令产生的区别文件, patch命令可以将一个老版本的包更新为一个新版本的包
-
diff3
diff命令的扩展版本, 可以同时比较三个文件
-
sdiff
比较和(或)编辑两个文件, 将它们合并到一个输出文件中
-
cmp
diff命令的一个简单版本.diff命令会报告两个文件的不同之处, 而cmp命令仅仅指出 哪些位置有所不同, 不会显示不同之处的具体细节
-
comm
多功能的文件比较工具. 使用这个命令之前必须先排序.
-
-
Utilities
-
basename
从文件名中去掉路径信息, 只打印出文件名.
-
dirname
从带路径的文件名字符串中去掉文件名(basename), 只打印出路径信息
-
split ,csplit
将一个文件分割为几个小段的工具.
csplit命令会根据 上下文 来切割文件, 切割的位置将会发生在模式匹配的地
-
sum,cksum ,md5sum ,sha1sum
产生checksum的工具,checksum 是对文件的内容进行数学计算而得到的, 它的目的是用来检验文件的完整性
-
shre
用随机字符填充文件, 使得文件无法恢复, 这样就可以保证文件安全的被删除.
-
-
编码和解码
-
uuencode
把二进制文件编码成ASCII字符串, 这个工具适用于编码e-mail消息体, 或者新闻组消 息.
-
uudecode
把uuencode后的ASCII字符串恢复为二进制文件.
-
mimencode, mmencode
处理多媒体编码的email附件
-
-
Miscellaneous
-
mktemp
使用一个"唯一"的文件名来创建一个 临时文件
-
make
build(建立)和compile(编译)二进制包的工具
make命令会检查 Makefile , makefile是文件的依赖和操作列表.
-
install
特殊目的的文件拷贝命令, 与cp命令相似, 但是具有设置拷贝文件的权限和属性的能力.
-
dos2unix
目的是将DOS格式的文本文件(以CR-LF为行结束符)转换为UNIX格式(以LF为行结束符)
-
ptx
ptx [targetfile]命令将输出目标文件的序列改变索引(交叉引用列表)
-
more, less
页显示文本文件或 stdout , 一次一屏. 可以用来过滤 stdout 的输出
-
通讯
信息与统计
-
host
通过名字或IP地址来搜索一个互联网主机的信息, 使用DNS.
-
ipcalc
显示一个主机IP信息. 使用 -h 选项, ipcalc将会做一个DNS的反向查询, 通过IP地址找到主机(服务器)名.
-
nslookup
通过IP地址在一个主机上做一个互联网的"名字服务查询". 事实上, 这与ipcalc -h或dig -x等 价. 这个命令既可以交互运行也可以非交互运行, 换句话说, 就是在脚本中运行.
-
dig
Domain Information Groper(域信息查询). 与nslookup很相似, dig也可以在一个主机上做互联网的"名字服务查询".
-
traceroute
跟踪包发送到远端主机过程中的路由信息
-
ping
广播一个"ICMP ECHO_REQUEST"包到其他主机上, 既可以是本地网络也可以是远端网络.
-
whois
执行DNS(域名系统)查询. -h 选项允许指定需要查询的特定 whois 服务器.
-
finger
取得网络上的用户信息
-
chfn
修改finger命令所显示出来的用户信息
-
vrfy
验证一个互联网的e-mail地址.
远程主机接入
-
ftp
向远端服务器上传或下载的工具, 也是一种协议. 一个ftp会话可以写到脚本中自动运行.
-
telnet
连接远端主机的工具和协议.
-
wget
wget工具使用 非交互 的形式从web或ftp站点上取得或下载文件. 在脚本中使用正好.
-
ssh 安全shell , 登陆远端主机并在其上运行命令. 这个工具具有身份认证和加密的功能, 可以安全的替换telnet, rlogin, rcp, 和rsh等工具. 请参考这个工具的 man页 来获取详细信
-
write
这是一个端到端通讯的工具. 这个工具可以从你的终端上(console或者 xterm )发送整行数据到另一个用户的终端上.
-
netconfig
用来配置网络适配器(使用DHCP)的命令行工具. 这个命令对于红帽发行版来说是内置的.
-
mail
发送或者读取e-mail消息.
-
mailto
与mail命令很相似, mailto可以使用命令行或在脚本中发送e-mail消息.
终端控制 P238
-
tput
初始化终端或者从 terminfo 数据中取得终端信息.
-
infocmp
打印出大量当前终端的信息
-
reset
复位终端参数并且清除屏幕,与clear命令一样
-
clear
简单的清除控制台或者 xterm 的屏幕. 光标和提示符将会重新出现在屏幕或者 xterm window的左上角
既可以用在命令行中也可以用在脚本中.
-
script
记录(保存到一个文件中)所有的用户按键信息(在控制台下的或在xterm window下的 按键信息)
数字计算
-
factor
将一个正数分解为多个素数.
-
bc
浮点运算
-
dc
dc(桌面计算器desk calculator)工具是面向栈的, 并且使用RPN(逆波兰表达式"Reverse Polish Notation"又叫"后缀表达式").
-
awk
在脚本中使用浮点运算的另一种方法是使用awk内建的数学运算函数, 可以用在shell包装中.
混杂命令
-
jot ,seq
这些工具用来生成一系列整数, 用户可以指定生成范围. 每个产生出来的整数一般都占一行, 但是可以使用 -s 选项来改变这种设置.
-
run-parts
run-parts命令将会执行目标目录中所有的脚本,
-
yes
yes命令的默认行为是向 stdout 连续不断的输出字符 y , 每个 y 单独占一行. 可以使用control-c来 结束输出.
-
banner
将会把传递进来的参数字符串用一个ASCII字符(默认是'#')给画出来(就是将多个'#'拼出一副字 符的图形), 然后输出到 stdout .
-
printenv
显示某个特定用户所有的环境变量.
-
lp
lp和lpr命令将会把文件发送到打印队列中, 并且作为硬拷贝来打印.
-
tee
重定向操作, 但是与之前所看到的有点不同. 就像管道中的"三通"一样, 这个命令可以 将命令或者管道命令的输出"抽出"到 一个文件 中, 而且不影响结果.
-
od
把输入(或文件)转换为8进制或者其他进制
-
hexdump
对二进制文件进行 16进制, 8进制, 10进制, 或者ASCII码的查阅动作.
-
objdump
显示编译后的二进制文件或二进制可执行文件的信息
-
mcookie
这个命令会产生一个"magic cookie", 这是一个128-bit(32-字符)的伪随机16进制数字, 这个数 字一般都用来作为X server的鉴权"签名".
-
units
不同的计量单位之间互相转换
-
m4
强大的宏处理过滤器,
-
doexec
将一个随便的参数列表传递到一个 二进制可执行文件 中.
-
dialog
提供了一种从脚本中调用交互对话框的方法
-
sox
ox命令, 也就是" so und e x change"命令, 可以进行声音文件的转换.
系统与管理命令 P257
在 /etc/rc.d 目录中的启动和关机脚本中包含了好多有用的(和没用的)系统管理命令. 这些命令通常总 是被root用户使用, 用于系统维护或者是紧急系统文件修复. 一定要小心使用这些工具, 因为如果滥用 的话, 它们会损坏你的系统.
User和Group类
- users 显示所有的登录用户. 这个命令与who -q基本一致.
- groups 列出当前用户和他所属的组.
- chown ,chgrp 修改一个或多个文件的所有权
- useradd, userdel 添加或删除一个用户帐号
- usermod 修改用户帐号
- groupmod 修改指定组.
- id 前进程真实有效的用户ID, 还有用户的组ID
- who 显示系统上所有已经登录的用户
- w 显示所有的登录用户和属于它们的进程
- logname 示当前用户的登录名(可以在 /var/run/utmp 中找到).
- su 使用替换的用户( s ubstitute u ser)身份来运行一个程序或脚本.
- sudo 以root(或其他用户)的身份来运行一个命令.
- passwd 设置, 修改, 或者管理用户的密码.
- ac 显示用户登录的连接时间, 就像从 /var/log/wtmp 中读取一样.
- last 户 最后 登录的信息, 就像从 /var/log/wtmp 中读出来一样.
- newgrp 不用登出就可以修改用户的组ID.
终端类命令
- tty 显示当前用户终端的名字.
- stty 显示并(或)修改终端设置
- setterm 设置特定的终端属性.
- tset 显示或初始化终端设置.
- setserial 设置或者显示串口参数. 这个脚本只能被root用户来运行, 并且通常都在系统安装脚本中使用.
- getty , agetty 一个终端的初始化过程通常都是使用getty或agetty来建立, 这样才能让用户登录.
- mesg 启用或禁用当前用户终端的访问权限.
- wall 这是一个缩写单词"write all", 也就是, 向登录到网络上的所有终端的所有用户都发送一个消息.
信息与统计类
-
uname 显示系统信息(OS, 内核版本, 等等.)
-
arch 显示系统的硬件体系结构
-
lastcomm 给出前一个命令的信息
-
lastlog 列出系统上所有用户最后登录的时间. 然后保存到 /var/log/lastlog 文件中.
-
lsof 列出打开的文件
-
strace 系统跟踪(System trace): 是跟踪 系统调用 和信号的诊断和调试工具.
-
ltrace 库跟踪工具(Library trace): 跟踪给定命令的 调用库 的相关信息.
-
nmap 网络映射(Network mapper)与端口扫描程序.
-
nc netcat 工具是一个完整的工具包, 可以用它连接和监听TCP和UDP端口.
-
free 使用表格形式来显示内存和缓存的使用情况
-
procinfo 从 /proc pseudo-filesystem中提取并显示所有信息和统计资料.
-
lsdev 列出系统设备, 也就是显示所有安装的硬件.
-
du 递归的显示(磁盘)文件的使用状况
-
df 使用列表的形式显示文件系统的使用状况.
-
dmesg 将所有的系统启动消息输出到 stdout 上
-
stat 显示一个或多个给定文件(也可以是目录文件或设备文件)的详细统计信息
-
vmstat 显示虚拟内存的统计信
-
netstat显示当前网络的统计状况和信息, 比如路由表和激活的连接, 这个工具将访问 /proc/net 中的信息.
-
uptime 显示系统运行的时间, 还有其他的一些统计信息.
-
hostname 显示系统的主机名字
-
hostid 用16进制表示法来显示主机的32位ID
-
sar sar(System Activity Reporter系统活动报告)命令将会给出系统统计的一个非常详细的概要.
-
readelf 示 elf 格式的二进制文件的统计信息
-
size
size [/path/to/binary]命令可以显示2进制可执行文件或归档文件每部分的尺寸.
系统日志类
- logger 附加一个用户产生的消息到系统日志中( /var/log/messages ).
- logrotatee 管理系统的log文件, 可以在合适的时候轮换, 压缩, 删除, 或(和)e-mail它们
作业控制类
-
ps
进程统计( P rocess S tatistics): 通过进程宿主或PID(进程ID)来列出当前正在执行的进程
-
pgrep , pkill
ps命令可以与grep或kill结合使用.
-
pstree 使用"树"形式列出当前执行的进程. -p 选项显示PID
-
top 连续不断的显示cpu占有率最高的进程
-
nice 使用经过修改的优先级来运行一个后台作业
-
nohup 保持一个命令进程处于运行状态, 即使这个命令进程所属的用户登出系统.
-
pidof 获取一个正在运行作业的 进程ID(PID)
-
fuser 或取一个正在访问某个或某些文件(或目录)的进程ID.
-
cron 管理程序调度器, 用来执行一些日常任务, 比如清除和删除系统log文件, 或者更新slocate数据 库.
启动与进程控制类
-
init init命令是所有进程的父进程.
-
telinit init命令的符号链接
-
runlevel
显示当前的和最后的运行级别, 也就是, 判断系统是处于终止状态(runlevel为 0 ), 单用户模式( 1 ), 多用户模式( 2 或 3 ), X Windows( 5 ), 还是正处于重起状态( 6 ). 这个命令将会访 问 /var/run/utmp 文件.
-
halt ,shudown, reboot 设置系统关机的命令, 通常比电源关机的优先级高.
-
service 开启或停止一个系统 服务 . 在 /etc/init.d 和 /etc/rc.d 中的启动脚本使用这个命令来启动服务
网络类
- ifconfig 网络的 接口配置 和调试工具.
- iwconfig 配置无线网络的命令集合. 可以说是上边的ifconfig的无线版本
- route 显示内核路由表信息
- chkconfig 检查网络配置.
- tcpdump 网络包的"嗅探器"
文件系统类
-
mount 加载一个文件系统, 通常都用来安装外部设备
mount -a将会mount所有出现在 /etc/fstab 中的文件系统和分区, 除了那些标记有 noauto (非自动)选项的. 启动的时候, 在 /etc/rc.d 中的一个启动脚本( rc.sysinit 或者一些相似的脚本)将会调用mount -a, 目的是mount所有可用的文件系统和分区.
-
umount 卸除一个当前已经mount的文件系统.
-
sync
当你需要更新硬盘buffer中的数据时, 这个命令可以强制将你buffer上的数据立即写入到硬盘上(同步带有buffer的驱动器).
-
losetup 建立和配置loopback设备.
-
mkswap 创建一个交换分区或文件. 交换区域随后必须马上使用swapon来启用
-
swapon ,swapoff 启用/禁用交换分区或文件. 这两个命令通常在启动和关机的时候才有效.
-
mke2fs 创建Linux ext2文件系统.
-
tune2fs 调整ext2文件系统.
-
dumpe2fs 打印(输出到 stdout )非常详细的文件系统信息.
-
hdparm 显示或修改硬盘参数
-
fdisk 在存储设备上(通常都是硬盘)创建和修改一个分区表.
-
fsck ,e2fsck, debugfs 文件系统的检查, 修复, 和除错命令集合
-
badblocks 检查存储设备的坏块(物理损坏).
-
lsusb ,usbmodules
lsusb命令会显示所有USB(Universal Serial Bus通用串行总线)总线和使用USB的设备. usbmodules命令会输出连接USB设备的驱动模块的信息.
-
lspci 显示 pci 总线及其设备
-
mkbootdisk 创建启动软盘, 启动盘可以唤醒系统
-
chroot 修改ROOT目录.
-
lockfile 创建一个 锁定文件 , 锁定文件 是一 种用来控制访问文件, 设备或资源的标记文件
-
flock
在一个文件上设置一个"咨询性"的锁,然后在锁持续的期间可以执行一 个命令
-
mknod 创建块或者字符设备文件(当在系统上安装新硬盘时, 必须这么做)
-
makedev 创建设备文件的工具. 必须在 /dev 目录下
-
tmpwatch 自动删除在指定时间内未被访问过的文件
备份类
-
dump , restore
dump命令是一个精巧的文件系统备份工具, 通常都用在比较大的安装版本和网络上
restore命令用来恢复 dump所产生的备份.
-
fdformat 对软盘进行低级格式化.
系统资源类
- ulimit 设置系统资源的使用 上限
- quota 显示用户或组的磁盘配额
- setquota 从命令行中设置用户或组的磁盘配额
- umask 设定用户创建文件时缺省的权限 mask (掩码).
- rdev 取得root device, swap space, 或video mode的相关信息, 或者对它们进行修
模块类
- lsmod 显示所有安装的内核模块
- insmod 强制安装一个内核模块
- rmmod 强制卸载一个内核模块
- modprobe 模块装载器, 一般情况下都是在启动脚本中自动调用.
- depmop 创建模块依赖文件, 一般都是在启动脚本中调用.
- modinfo 输出一个可装载模块的信息.
杂项类
-
env 使用设置过的或修改过(并不是修改整个系统环境)的环境变量来运行一个程序或脚本
-
ldd 显示一个可执行文件和它所需要共享库之间依赖关系.
-
watch 以指定的时间间隔来重复运行一个命令.
watch -n 5 tail /var/log/messages
-
strip 可执行文件中去掉调试符号的引用
-
nm 列出未strip过的, 经过编译的, 2进制文件的全部符号.
-
rdist 远程分布客户端: 在远端服务器上同步, 克隆, 或者备份一个文件系统.
#!/bin/sh
# 简单并且最短的系统脚本之一 "killall"
# --> 这是由Miquel van Smoorenburg所编写的
# --> 'rc'脚本包的一部分, <miquels@drinkel.nl.mugnet.org>.
# --> 这个特殊的脚本看起来是Red Hat/FC专用的,
# --> (在其它的发行版中可能不会出现).
# 停止所有正在运行的不必要的服务
#+ (不会有多少, 所以这是个合理性检查)
for i in /var/lock/subsys/*; do
# --> 标准的for/in循环, 但是由于"do"在同一行上,
# --> 所以必须添加";".
# 检查脚本是否在那里.
[ ! -f $i ] && continue
# --> 这是一种使用"与列表"的聪明方法, 等价于:
# --> if [ ! -f "$i" ]; then continue
# 取得子系统的名字.
subsys=${i#/var/lock/subsys/}
# --> 匹配变量名, 在这里就是文件名.
# --> 与subsys=`basename $i`完全等价.
# --> 从锁定文件名中获得
# -->+ (如果那里有锁定文件的话,
# -->+ 那就证明进程正在运行).
# --> 参考一下上边所讲的"锁定文件"的内容.
# 终止子系统.
if [ -f /etc/rc.d/init.d/$subsys.init ]; then
/etc/rc.d/init.d/$subsys.init stop
else
/etc/rc.d/init.d/$subsys stop
# --> 挂起运行的作业和幽灵进程.
# --> 注意"stop"只是一个位置参数,
# -->+ 并不是shell内建命令.
fi
done
命令替换 P284
命令替换能够重新分配一个[1] 甚至是多个命令的输出; 它会将命令的输出如实地添加到另一个上下文中. 命令替换的典型用法形式, 是使用 后置引用 (`...`). 使用后置引用的(反引号)命令会产生命令行文本.
script_name=`basename $0`
echo "The name of this script is $script_name."
rm `cat filename` # "filename"包含了需要被删除的文件列表.
#
# S. C. 指出, 这种使用方法可能会产生"参数列表太长"的错误.
# 更好的方法是 xargs rm -- < filename
# ( -- 同时涵盖了某些特殊情况, 这种特殊情况就是, 以"-"开头的文件名会产生不良结果.)
textfile_listing=`ls *.txt`
# 变量中包含了当前工作目录下所有的*.txt文件.
echo $textfile_listing
textfile_listing2=$(ls *.txt) # 这是命令替换的另一种形式.
echo $textfile_listing2
# 同样的结果.
# 如果将文件列表放入到一个字符串中的话,
# 可能会混入一个新行.
#
# 一种安全的将文件列表传递到参数中的方法就是使用数组.
# shopt -s nullglob # 如果不匹配, 那就不进行文件名扩展.
# textfile_listing=( *.txt )
命令替换甚至允许将整个文件的内容放到变量中, 可以使用重定向或者cat命令.
#!/bin/bash
# csubloop.sh: 将循环输出的内容设置到变量中.
variable1=`for i in 1 2 3 4 5
do
echo -n "$i" # 对于在这里的命令替换来说
done` #+ 这个'echo'命令是非常关键的.
echo "variable1 = $variable1" # variable1 = 12345
i=0
variable2=`while [ "$i" -lt 10 ]
do
echo -n "$i" # 再来一个, 'echo'是必需的.
let "i += 1" # 递增.
done`
echo "variable2 = $variable2" # variable2 = 0123456789
# 这就证明了在一个变量的声明中
#+ 嵌入一个循环是可行的.
exit 0
对于命令替换来说, $(COMMAND)形式已经取代了后置引用"`".
output=$(sed -n /"$1"/p $file) # 来自于例子"grp.sh".
# 将文本文件的内容保存到一个变量中.
File_contents1=$(cat $file1)
File_contents2=$(<$file2) # Bash也允许这么做.
$(...)形式的命令替换是允许嵌套的
word_count=$( wc -w $(ls -l | awk '{print $9}') )
算术扩展
算术扩展提供了一种强力工具, 可以在脚本中执行(整型)算法操作. 可以使用backticks, double parentheses, 或let来将字符串转换为数字表达式.
使用后置引用的算术扩展(通常都是和expr一起使用)
z=`expr $z + 3` # 'expr'命令将会执行这个扩展.
使用双括号形式的算术扩展, 也可以使用let命令
z=$(($z+3))
z=$((z+3)) # 也正确.
# 使用双括号的形式,
#+ 参数解引用
#+ 是可选的.
# $((EXPRESSION))是算数表达式. # 不要与命令替换
#+ 相混淆.
# 使用双括号的形式也可以不用给变量赋值.
n=0
echo "n = $n" # n = 0
(( n += 1 )) # 递增.
# (( $n += 1 )) is incorrect!
echo "n = $n" # n = 1
let z=z+3
let "z += 3" # 使用引用的形式, 允许在变量赋值的时候存在空格.
# 'let'命令事实上执行得的是算术赋值,
#+ 而不是算术扩展.
I/O重定向 P290
默认情况下始终有3个"文件"处于打开状态, stdin (键盘), stdout (屏幕), 和 stderr (错误消息输出到屏幕上). 这3个文件和其他打开的文件都可以被重定向. 对于重定向简单的解释就是捕捉一个文件, 命令, 程序, 脚本, 或者是脚本中的代码块的输出, 然后将这些输出作为输入发送到另一个文件, 命令, 程序, 或脚本中.
每个打开的文件都会被分配一个文件描述符. stdin , stdout , 和 stderr 的文件描述符分别是0, 1,和 2. 除了这3个文件, 对于其他那些需要打开的文件, 保留了文件描述符3到9. 在某些情况下, 将这些额外的文件描述符分配给 stdin , stdout , 或 stderr 作为临时的副本链接是非常有用的. 在经过复杂的重定向和刷新之后需要把它们恢复成正常状态\
COMMAND_OUTPUT >
# 将stdout重定向到一个文件.
# 如果这个文件不存在, 那就创建, 否则就覆盖.
ls -lR > dir-tree.list
# 创建一个包含目录树列表的文件.
: > filename
# >操作, 将会把文件"filename"变为一个空文件(就是size为0).
# 如果文件不存在, 那么就创建一个0长度的文件(与'touch'的效果相同).
# :是一个占位符, 不产生任何输出.
> filename
# >操作, 将会把文件"filename"变为一个空文件(就是size为0).
# 如果文件不存在, 那么就创建一个0长度的文件(与'touch'的效果相同).
# (与上边的": >"效果相同, 但是某些shell可能不支持这种形式.)
COMMAND_OUTPUT >>
# 将stdout重定向到一个文件.
# 如果文件不存在, 那么就创建它, 如果存在, 那么就追加到文件后边.
# 单行重定向命令(只会影响它们所在的行):
# --------------------------------------------------------------------
>filename
# 重定向stdout到文件"filename".
>>filename
# 重定向并追加stdout到文件"filename".
>filename
# 重定向stderr到文件"filename".
>>filename
# 重定向并追加stderr到文件"filename".
&>filename
# 将stdout和stderr都重定向到文件"filename".
M>N
# "M"是一个文件描述符, 如果没有明确指定的话默认为1.
# "N"是一个文件名.
# 文件描述符"M"被重定向到文件"N".
M>&N
# "M"是一个文件描述符, 如果没有明确指定的话默认为1.
# "N"是另一个文件描述符.
#==============================================================================
# 重定向stdout, 一次一行.
LOGFILE=script.log
echo "This statement is sent to the log file, \"$LOGFILE\"." 1>$LOGFILE
echo "This statement is appended to \"$LOGFILE\"." 1>>$LOGFILE
echo "This statement is also appended to \"$LOGFILE\"." 1>>$LOGFILE
echo "This statement is echoed to stdout, and will not appear in
\"$LOGFILE\"."
# 每行过后, 这些重定向命令会自动"reset".
# 重定向stderr, 一次一行.
ERRORFILE=script.errors
bad_command1 2>$ERRORFILE # Error message sent to $ERRORFILE.
bad_command2 2>>$ERRORFILE # Error message appended to $ERRORFILE.
bad_command3 # Error message echoed to stderr,
#+ and does not appear in $ERRORFILE.
# 每行过后, 这些重定向命令也会自动"reset".
#==============================================================================
>&1
# 重定向stderr到stdout.
# 将错误消息的输出, 发送到与标准输出所指向的地方.
i>&j
# 重定向文件描述符i到j.
# 指向i文件的所有输出都发送到j.
>&j
# 默认的, 重定向文件描述符1(stdout)到j.
# 所有传递到stdout的输出都送到j中去.
< FILENAME
< FILENAME
# 从文件中接受输入.
# 与">"是成对命令, 并且通常都是结合使用.
#
# grep search-word <filename
[j]<>filename
# 为了读写"filename", 把文件"filename"打开, 并且将文件描述符"j"分配给它.
# 如果文件"filename"不存在, 那么就创建它.
# 如果文件描述符"j"没指定, 那默认是fd 0, stdin.
#
# 这种应用通常是为了写到一个文件中指定的地方.
echo 1234567890 > File # 写字符串到"File".
exec 3<> File # 打开"File"并且将fd 3分配给它.
read -n 4 <&3 # 只读取4个字符.
echo -n . >&3 # 写一个小数点.
exec 3>&- # 关闭fd 3.
cat File # ==> 1234.67890
# 随机访问.
|
# 管道.
# 通用目的处理和命令链工具.
# 与">", 很相似, 但是实际上更通用.
# 对于想将命令, 脚本, 文件和程序串连起来的时候很有用.
cat *.txt | sort | uniq > result-file
# 对所有.txt文件的输出进行排序, 并且删除重复行.
# 最后将结果保存到"result-file"中.
可以将输入输出重定向和(或)管道的多个实例结合到一起写在同一行上
command < input-file > output-file
command1 | command2 | command3 > output-file
可以将多个输出流重定向到一个文件上
ls -yz >> command.log 2>&1
# 将错误选项"yz"的结果放到文件"command.log"中.
# 因为stderr被重定向到这个文件中,
#+ 所有的错误消息也就都指向那里了.
# 注意, 下边这个例子就不会给出相同的结果.
ls -yz 2>&1 >> command.log
# 输出一个错误消息, 但是并不写到文件中.
# 如果将stdout和stderr都重定向,
#+ 命令的顺序会有些不同.
关闭文件描述符
- n<&- 关闭输入文件描述符 n .
- 0<&-, <&- 关闭 stdin .
- n>&- 关闭输出文件描述符 n .
- 1>&-, >&- 关闭 stdout .
使用exec
exec <filename命令会将 stdin 重定向到文件中. 从这句开始, 所有的 stdin 就都来自于这个文件了, 而不是标准输入(通常都是键盘输入). 这样就提供了一种按行读取文件的方法, 并且可以使用sed和/或awk来对每一行进行分析
#!/bin/bash
# 使用'exec'重定向stdin.
exec 6<&0 # 将文件描述符#6与stdin链接起来.
# 保存stdin.
exec < data-file # stdin被文件"data-file"所代替.
read a1 # 读取文件"data-file"的第一行.
read a2 # 读取文件"data-file"的第二行.
echo
echo "Following lines read from file."
echo "-------------------------------"
echo $a1
echo $a2
echo; echo; echo
exec 0<&6 6<&-
# 现在将stdin从fd #6中恢复, 因为刚才我们把stdin重定向到#6了,
#+ 然后关闭fd #6 ( 6<&- ), 好让这个描述符继续被其他进程所使用.
#
# <&6 6<&- 这么做也可以.
echo -n "Enter data "
read b1 # 现在"read"已经恢复正常了, 就是能够正常的从stdin中读取.
echo "Input read from stdin."
echo "----------------------"
echo "b1 = $b1"
echo
exit 0
#!/bin/bash
# reassign-stdout.sh
LOGFILE=logfile.txt
exec 6>&1 # 将fd #6与stdout链接起来.
# 保存stdout.
exec > $LOGFILE # stdout就被文件"logfile.txt"所代替了.
# ----------------------------------------------------------- #
# 在这块中所有命令的输出都会发送到文件$LOGFILE中.
echo -n "Logfile: "
date
echo "-------------------------------------"
echo
echo "Output of \"ls -al\" command"
echo
ls -al
echo; echo
echo "Output of \"df\" command"
echo
df
# ----------------------------------------------------------- #
exec 1>&6 6>&- # 恢复stdout, 然后关闭文件描述符#6.
echo
echo "== stdout now restored to default == "
echo
ls -al
echo
exit 0
!/bin/bash
# upperconv.sh
# 将一个指定的输入文件转换为大写.
E_FILE_ACCESS=70
E_WRONG_ARGS=71
if [ ! -r "$1" ] # 判断指定的输入文件是否可读?
then
echo "Can't read from input file!"
echo "Usage: $0 input-file output-file"
exit $E_FILE_ACCESS
fi # 即使输入文件($1)没被指定
#+ 也还是会以相同的错误退出(为什么?).
if [ -z "$2" ]
then
echo "Need to specify output file."
echo "Usage: $0 input-file output-file"
exit $E_WRONG_ARGS
fi
exec 4<&0
exec < $1 # 将会从输入文件中读取.
exec 7>&1
exec > $2 # 将写到输出文件中.
# 假设输出文件是可写的(添加检查?).
# -----------------------------------------------
cat - | tr a-z A-Z # 转换为大写.
# ^^^^^ # 从stdin中读取.
# ^^^^^^^^^^ # 写到stdout上.
# 然而, stdin和stdout都被重定向了.
# -----------------------------------------------
exec 1>&7 7>&- # 恢复stout.
exec 0<&4 4<&- # 恢复stdin.
# 恢复之后, 下边这行代码将会如预期的一样打印到stdout上.
echo "File \"$1\" written to \"$2\" as uppercase conversion."
exit 0
#!/bin/bash
# avoid-subshell.sh
# 由Matthew Walker所提出的建议.
Lines=0
echo
cat myfile.txt | while read line; # (译者注: 管道会产生子shell)
do {
echo $line
(( Lines++ )); # 增加这个变量的值
#+ 但是外部循环却不能访问.
# 子shell问题.
}
done
echo "Number of lines read = $Lines" # 0
# 错误!
echo "------------------------"
exec 3<> myfile.txt
while read line <&3
do {
echo "$line"
(( Lines++ )); # 增加这个变量的值
#+ 现在外部循环就可以访问了.
# 没有子shell, 现在就没问题了.
}
done
exec 3>&-
echo "Number of lines read = $Lines" # 8
echo
exit 0
# 下边这些行是这个脚本的结果, 脚本是不会走到这里的.
$ cat myfile.txt
Line 1.
Line 2.
Line 3.
Line 4.
Line 5.
Line 6.
Line 7.
Line 8.
代码块重定向
像while, until, 和for循环代码块, 甚至if/then测试结构的代码块, 都可以对 stdin 进行重定向. 即使函数也可以使用这种重定向方式(请参考例子 23-11). 要想做到这些, 都要依靠代码块结尾的<操作符.
#!/bin/bash
# redir2.sh hile 循环的重定向
if [ -z "$1" ]
then
Filename=names.data # 如果没有指定文件名, 则使用这个默认值.
else
Filename=$1
fi
#+ Filename=${1:-names.data}
# 这句可代替上面的测试(参数替换).
count=0
echo
while [ "$name" != Smith ] # 为什么变量$name要用引号?
do
read name # 从$Filename文件中读取输入, 而不是在stdin中读取输入.
echo $name
let "count += 1"
done <"$Filename" # 重定向stdin到文件$Filename.
# ^^^^^^^^^^^^
echo; echo "$count names read"; echo
exit 0
# 注意在一些比较老的shell脚本编程语言中,
#+ 重定向的循环是放在子shell里运行的.
# 因此, $count 值返回后会是 0, 此值是在循环开始前的初始值.
# *如果可能的话*, 尽量避免在Bash或ksh中使用子shell,
#+ 所以这个脚本能够正确的运行.
# (多谢Heiner Steven指出这个问题.)
# 然而 . . .
# Bash有时还是*会*在一个使用管道的"while-read"循环中启动一个子shell,
#+ 与重定向的"while"循环还是有区别的.
abc=hi
echo -e "1\n2\n3" | while read l
do abc="$l"
echo $abc
done
echo $abc
#!/bin/bash
if [ -z "$1" ]
then
Filename=names.data # 如果没有指定文件名, 则使用默认值.
else
Filename=$1
fi
Savefile=$Filename.new # 保存最终结果的文件名.
FinalName=Jonah # 终止"read"时的名称.
line_count=`wc $Filename | awk '{ print $1 }'` # 目标文件的行数.
for name in `seq $line_count`
do
read name
echo "$name"
if [ "$name" = "$FinalName" ]
then
break
fi
done < "$Filename" > "$Savefile" # 重定向stdin到文件$Filename,
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 并且将它保存到备份文件中.
exit 0
重定向的应用
巧妙地运用I/O重定向, 能够解析和粘合命令输出的各个片断. 这样就可以产生报告与日志文件.
#!/bin/bash
# logevents.sh, 由Stephane Chazelas所编写.
# 把事件记录在一个文件中.
# 必须以root身份运行 (这样才有权限访问/var/log).
ROOT_UID=0 # 只有$UID值为0的用户才具有root权限.
E_NOTROOT=67 # 非root用户的退出错误.
if [ "$UID" -ne "$ROOT_UID" ]
then
echo "Must be root to run this script."
exit $E_NOTROOT
fi
FD_DEBUG1=3
FD_DEBUG2=4
FD_DEBUG3=5
# 去掉下边两行注释中的一行, 来激活脚本.
# LOG_EVENTS=1
# LOG_VARS=1
log() # 把时间和日期写入日志文件.
{
echo "$(date) $*" >&7 # 这会把日期*附加*到文件中.
# 参考下边的代码.
}
case $LOG_LEVEL in
) exec 3>&2 4> /dev/null 5> /dev/null;;
) exec 3>&2 4>&2 5> /dev/null;;
) exec 3>&2 4>&2 5>&2;;
*) exec 3> /dev/null 4> /dev/null 5> /dev/null;;
esac
FD_LOGVARS=6
if [[ $LOG_VARS ]]
then exec 6>> /var/log/vars.log
else exec 6> /dev/null # 丢弃输出.
fi
FD_LOGEVENTS=7
if [[ $LOG_EVENTS ]]
then
# then exec 7 >(exec gawk '{print strftime(), $0}' >> /var/log/event.log)
# 上面这行不能在2.04版本的Bash上运行.
exec 7>> /var/log/event.log # 附加到"event.log".
log # 记录日期与时间.
else exec 7> /dev/null # 丢弃输出.
fi
echo "DEBUG3: beginning" >&${FD_DEBUG3}
ls -l >&5 2>&4 # command1 >&5 2>&4
echo "Done" # command2
echo "sending mail" >&${FD_LOGEVENTS} # 将字符串"sending mail"写到文件描述符#7.
exit 0
Here Document
一个 here document 就是一段带有特殊目的的代码段. 它使用I/O重定向的形式将一个命令序列传递到一个交互程序或者命令中, 比如ftp, cat, 或者 ex 文本编辑器.
#!/bin/bash interactive-program <<LimitString command #1 command #2 ... LimitString
#!/bin/bash
# 用非交互的方式来使用'vi'编辑一个文件.
# 模仿'sed'.
E_BADARGS=65
if [ -z "$1" ]
then
echo "Usage: `basename $0` filename"
exit $E_BADARGS
fi
TARGETFILE=$1
# 在文件中插入两行, 然后保存.
#--------Begin here document-----------#
vi $TARGETFILE <<x23LimitStringx23
i
This is line 1 of the example file.
This is line 2 of the example file.
^[
ZZ
x23LimitStringx23
#----------End here document-----------#
# 注意上边^[是一个转义符, 键入Ctrl+v <Esc>就行,
#+ 事实上它是<Esc>键;.
# Bram Moolenaar指出这种方法不能使用在'vim'上, (译者注: Bram Moolenaar是vim作者)
#+ 因为可能会存在终端相互影响的问题.
exit 0
$ variable=$(cat <<SETVAR
this variable
run over multiple lines.
SETVAR)
echo "$variable"
子Shell
运行一个shell脚本的时候, 会启动命令解释器的另一个实例. 就好像你的命令是在命令行提示下被解释的一样, 类似于批处理文件中的一系列命令. 每个shell脚本都有效地运行在父shell的一个子进程中. 这个父shell指的是在一个控制终端或在一个 xterm 窗口中给出命令提示符的那个进程.
shell脚本也能启动它自已的子进程. 这些 子shell 能够使脚本并行的, 有效的, 同时运行多个子任务.
( command1; command2; command3; ... )
#!/bin/bash
# subshell.sh
echo
echo "Subshell level OUTSIDE subshell = $BASH_SUBSHELL"
# Bash, 版本3, 添加了这个新的 $BASH_SUBSHELL 变量.
echo
outer_variable=Outer
(
echo "Subshell level INSIDE subshell = $BASH_SUBSHELL"
inner_variable=Inner
echo "From subshell, \"inner_variable\" = $inner_variable"
echo "From subshell, \"outer\" = $outer_variable"
)
echo
echo "Subshell level OUTSIDE subshell = $BASH_SUBSHELL"
echo
if [ -z "$inner_variable" ]
then
echo "inner_variable undefined in main body of shell"
else
echo "inner_variable defined in main body of shell"
fi
echo "From main body of shell, \"inner_variable\" = $inner_variable"
# $inner_variable将被作为未初始化的变量, 被显示出来,
#+ 这是因为变量是在子shell里定义的"局部变量".
# 还有补救的办法么?
echo
exit 0
#!/bin/bash
# allprofs.sh: 打印所有用户的配置文件
# 由Heiner Steven编写, 并由本书作者进行了修改.
FILE=.bashrc # 在原始脚本中, File containing user profile,
#+ 包含用户profile的是文件".profile".
for home in `awk -F: '{print $6}' /etc/passwd`
do
[ -d "$home" ] || continue # 如果没有home目录, 跳出本次循环.
[ -r "$home" ] || continue # 如果home目录没有读权限, 跳出本次循环.
(cd $home; [ -e $FILE ] && less $FILE)
done
# 当脚本结束时,不必使用'cd'命令返回原来的目录.
#+ 因为'cd $home'是在子shell中发生的, 不影响父shell.
exit 0
受限Shell
在受限shell中禁用的命令 在 受限 模式下运行一个脚本或脚本片断, 将会禁用某些命令, 这些命令在正常模式下都可以运行.这是一种安全策略, 目的是为了限制脚本用户的权限, 并且能够让运行脚本所导致的危害降低到最小.
- 使用 cd 命令更改工作目录.
- 更改环境变量 $PATH , $SHELL , $BASH_ENV , 或 $ENV 的值.
- 读取或修改环境变量 $SHELLOPTS 的值.
- 输出重定向.
- 调用的命令路径中包括有一个或多个斜杠(/).
- 调用 exec , 把当前的受限shell替换成另外一个进程.
- 能够在无意中破坏脚本的命令.
- 在脚本中企图脱离受限模式的操作.
#!/bin/bash
# 脚本开头以"#!/bin/bash -r"来调用,
#+ 会使整个脚本在受限模式下运行.
echo
echo "Changing directory."
cd /usr/local
echo "Now in `pwd`"
echo "Coming back home."
cd
echo "Now in `pwd`"
echo
# 非受限的模式下,所有操作都正常.
set -r
# set --restricted 也具有相同的效果.
echo "==> Now in restricted mode. <=="
echo
echo
echo "Attempting directory change in restricted mode."
cd ..
echo "Still in `pwd`"
echo
echo
echo "\$SHELL = $SHELL"
echo "Attempting to change shell in restricted mode."
SHELL="/bin/ash"
echo
echo "\$SHELL= $SHELL"
echo
echo
echo "Attempting to redirect output in restricted mode."
ls -l /usr/bin > bin.files
ls -l bin.files # 尝试列出刚才创建的文件.
echo
exit 0
进程替换
进程替换 与命令替换很相似. 命令替换把一个命令的结果赋值给一个变量, 比如dir_contents=`ls -al`或xref=$( grep word datafile). 进程替换把一个进程的输出提供给另一个进程(换句话说, 它把一个命令的结果发给了另一个命令).
命令替换的模版 用圆括号扩起来的命令
>(command) <(command)
comm <(ls -l) <(ls -al)
diff <(ls $first_directory) <(ls $second_directory)
函数
一个函数就是一个子程序, 用于实现一系列操作的代码块, 它是完成特定任务的"黑盒子". 当存在重复代码的时候, 或者当一个任 务只需要轻微修改就被重复使用的时候, 你就需要考虑使用函数了.
function function_name { command ... } 或 function_name () { command ... }
只需要简单的调用函数名, 函数就会被调用或 触发
#!/bin/bash
JUST_A_SECOND=1
funky ()
{ # 这是一个最简单的函数.
echo "This is a funky function."
echo "Now exiting funky function."
} # 函数必须在调用前声明.
fun ()
{ # 一个稍微复杂一些的函数.
i=0
REPEATS=30
echo
echo "And now the fun really begins."
echo
sleep $JUST_A_SECOND # 嘿, 暂停一秒!
while [ $i -lt $REPEATS ]
do
echo "----------FUNCTIONS---------->"
echo "<------------ARE-------------"
echo "<------------FUN------------>"
echo
let "i+=1"
done
}
# 现在, 调用这两个函数.
funky
fun
exit 0
复杂函数与函数复杂性
函数可以处理传递给它的参数, 并且能返回它的退出状态码给脚本, 以便后续处理. function_name $arg1 $arg2 函数以位置来引用传递过来的参数(就好像它们是位置参数), 例如, $1 , $2 , 等等.
#!/bin/bash
# 函数和参数
DEFAULT=default # 默认参数值.
func2 () {
if [ -z "$1" ] # 第一个参数是否长度为零?
then
echo "-Parameter #1 is zero length.-" # 或者没有参数被传递进来.
else
echo "-Param #1 is \"$1\".-"
fi
variable=${1-$DEFAULT} # 这里的参数替换
echo "variable = $variable" #+ 表示什么?
# ---------------------------
# 为了区分没有参数的情况,
#+ 和只有一个null参数的情况.
if [ "$2" ]
then
echo "-Parameter #2 is \"$2\".-"
fi
return 0
}
echo
echo "Nothing passed."
func2 # 不带参数调用
echo
echo "Zero-length parameter passed."
func2 "" # 使用0长度的参数进行调用
echo
echo "Null parameter passed."
func2 "$uninitialized_param" # 使用未初始化的参数进行调用
echo
echo "One parameter passed."
func2 first # 带一个参数调用
echo
echo "Two parameters passed."
func2 first second # 带两个参数调用
echo
echo "\"\" \"second\" passed."
func2 "" second # 带两个参数调用,
echo # 第一个参数长度为0, 第二个参数是由ASCII码组成的字符串.
exit 0
#!/bin/bash
# ind-func.sh: 将一个间接引用传递给函数.
echo_var ()
{
echo "$1"
}
message=Hello
Hello=Goodbye
echo_var "$message" # Hello
# 现在,让我们传递一个间接引用给函数.
echo_var "${!message}" # Goodbye
echo "-------------"
# 如果我们改变"hello"变量的值会发生什么?
Hello="Hello, again!"
echo_var "$message" # Hello
echo_var "${!message}" # Hello, again!
exit 0
#!/bin/bash
# max.sh: 取两个整数中的最大值.
E_PARAM_ERR=-198 # 如果传给函数的参数少于2个时, 就返回这个值.
EQUAL=-199 # 如果两个整数相等时, 返回这个值.
# 任意超出范围的
#+ 参数值都可能传递到函数中.
max2 () # 返回两个整数中的最大值.
{ # 注意: 参与比较的数必须小于257.
if [ -z "$2" ]
then
return $E_PARAM_ERR
fi
if [ "$1" -eq "$2" ]
then
return $EQUAL
else
if [ "$1" -gt "$2" ]
then
return $1
else
return $2
fi
fi
}
max2 33 34
return_val=$?
if [ "$return_val" -eq $E_PARAM_ERR ]
then
echo "Need to pass two parameters to the function."
elif [ "$return_val" -eq $EQUAL ]
then
echo "The two numbers are equal."
else
echo "The larger of the two numbers is $return_val."
fi
exit 0
#!/bin/bash
# realname.sh
#
# 依靠username, 从/etc/passwd中获得"真名".
ARGCOUNT=1 # 需要一个参数.
E_WRONGARGS=65
file=/etc/passwd
pattern=$1
if [ $# -ne "$ARGCOUNT" ]
then
echo "Usage: `basename $0` USERNAME"
exit $E_WRONGARGS
fi
file_excerpt () # 按照要求的模式来扫描文件, 然后打印文件相关的部分.
{
while read line # "while"并不一定非得有"[ condition ]"不可.
do
echo "$line" | grep $1 | awk -F":" '{ print $5 }' # awk用":"作为
界定符.
done
} <$file # 重定向到函数的stdin.
file_excerpt $pattern
# 是的, 整个脚本其实可以被缩减为
# grep PATTERN /etc/passwd | awk -F":" '{ print $5 }'
# 或
# awk -F: '/PATTERN/ {print $5}'
# 或
# awk -F: '($1 == "username") { print $5 }' # 从username中获得
真名.
# 但是, 这些起不到示例的作用.
exit 0
局部变量
如果变量用 local 来声明, 那么它就只能够在该变量被声明的代码块中可见. 这个代码块就是局部"范围". 在一个函数中, 一个 局部变量 只有在函数代码块中才有意义.
#!/bin/bash
# 函数内部的局部变量与全局变量.
func ()
{
local loc_var=23 # 声明为局部变量.
echo # 使用'local'内建命令.
echo "\"loc_var\" in function = $loc_var"
global_var=999 # 没有声明为局部变量.
# 默认为全局变量.
echo "\"global_var\" in function = $global_var"
}
func
# 现在, 来看看局部变量"loc_var"在函数外部是否可见.
echo
echo "\"loc_var\" outside function = $loc_var"
# $loc_var outside function =
# 不行, $loc_var不是全局可见的.
echo "\"global_var\" outside function = $global_var"
# 在函数外部$global_var = 999
# $global_var是全局可见的.
echo
exit 0
# 与C语言相比, 在函数内声明的Bash变量
#+ 除非它被明确声明为local时, 它才是局部的.
别名
种避免输入长命令序列的手段. 举个例子, 如果我们 添加alias lm="ls -l | more"到文件 ~/.bashrc 中, 那么每次在命令行中键入 lm 就可以自动转换为 ls-l | more. 这可以让你在命令行上少敲好多次, 而且也可以避免记忆复杂的命令和繁多的选项. 设置alias rm="rm -i"(删除的时候提示), 可以让你在犯了错误之后也不用悲伤, 因为它可以让你避免意外删除重要文件. 在脚本中, 别名就没那么重要了. 如果把别名机制想象成C预处理器的某些功能的话, 就很形象, 比如说宏扩展, 但不幸的是, Bash不能在别名中扩展参数. 而且在脚本中, 别名不能够用在"混合型结构"中, 比如if/then结构, 循环, 和函数. 还有一个限制, 别名不能递归扩展. 绝大多数情况下, 我们期望别名能够完成的工作, 都能够用函数更高效的完成.
#!/bin/bash
# alias.sh
shopt -s expand_aliases
# 必须设置这个选项, 否则脚本不会打开别名功能.
# 首先, 来点有趣的.
alias Jesse_James='echo "\"Alias Jesse James\" was a 1959 comedy starring Bob
Hope."'
Jesse_James
echo; echo; echo;
alias ll="ls -l"
# 可以使用单引号(')或双引号(")来定义一个别名.
echo "Trying aliased \"ll\":"
ll /usr/X11R6/bin/mk* #* 别名工作了.
echo
directory=/usr/X11R6/bin/
prefix=mk* # 看一下通配符会不会引起麻烦.
echo "Variables \"directory\" + \"prefix\" = $directory$prefix"
echo
alias lll="ls -l $directory$prefix"
echo "Trying aliased \"lll\":"
lll # 详细列出/usr/X11R6/bin目录下所有以mk开头的文件.
# 别名能处理连接变量 -- 包括通配符 -- o.k.
TRUE=1
echo
if [ TRUE ]
then
alias rr="ls -l"
echo "Trying aliased \"rr\" within if/then statement:"
rr /usr/X11R6/bin/mk* #* 产生错误信息!
# 别名不能在混合结构中使用.
echo "However, previously expanded alias still recognized:"
ll /usr/X11R6/bin/mk*
fi
echo
count=0
while [ $count -lt 3 ]
do
alias rrr="ls -l"
echo "Trying aliased \"rrr\" within \"while\" loop:"
rrr /usr/X11R6/bin/mk* #* 这里, 别名也不会扩展.
# alias.sh: line 57: rrr: command not found
let count+=1
done
echo; echo
alias xyz='cat $0' # 脚本打印自身内容.
# 注意是单引号(强引用).
xyz
# 虽然Bash文档建议, 它不能正常运行,
#+ 不过它看起来是可以运行的.
#
# 然而, 就像Steve Jacobson所指出的那样,
#+ 参数"$0"立即扩展成了这个别名的声明.
exit 0
unalias命令用来删除之前设置的 别名
#!/bin/bash
# unalias.sh
shopt -s expand_aliases # 启用别名扩展.
alias llm='ls -al | more'
llm
echo
unalias llm # 删除别名.
llm
# 产生错误信息, 因为'llm'已经不再有效了.
exit 0
列表结构 p346
"与列表"和"或列表"结构能够提供一种手段, 这种手段能够用来处理一串连续的命令. 这样就可以有效 的替换掉嵌套的if/then结构, 甚至能够替换掉case语句.
与列表 如果每个命令执行后都返回true(0)的话, 那么命令将会依次执行下去.
command-1 && command-2 && command-3 && ... command-n
或列表 如果每个命令都返回false, 那么命令链就会执行下去.
command-1 || command-2 || command-3 || ... command-n
数组 p349
新版本的Bash支持一维数组. 数组元素可以使用符号 variable[xx] 来初始化. 另外, 脚本可以使 用 declare -a variable 语句来指定一个数组. 如果想解引用一个数组元素(也就是取值), 可以使用 大括 号 , 访问形式为 ${variable[xx] .}
#!/bin/bash
area[11]=23
area[13]=37
area[51]=UFOs
# 数组成员不一定非得是相邻或连续的.
# 数组的部分成员可以不被初始化.
# 数组中允许空缺元素.
# 实际上, 保存着稀疏数据的数组("稀疏数组")
#+ 在电子表格处理软件中是非常有用的.
echo -n "area[11] = "
echo ${area[11]} # 需要{大括号}.
echo -n "area[13] = "
echo ${area[13]}
echo "Contents of area[51] are ${area[51]}."
# 没被初始化的数组成员打印为空值(null变量).
echo -n "area[43] = "
echo ${area[43]}
echo "(area[43] unassigned)"
echo
# 两个数组元素的和被赋值给另一个数组元素
area[5]=`expr ${area[11]} + ${area[13]}`
echo "area[5] = area[11] + area[13]"
echo -n "area[5] = "
echo ${area[5]}
area[6]=`expr ${area[11]} + ${area[51]}`
echo "area[6] = area[11] + area[51]"
echo -n "area[6] = "
echo ${area[6]}
# 这里会失败, 是因为不允许整数与字符串相加.
echo; echo; echo
# -----------------------------------------------------------------
# 另一个数组, "area2".
# 另一种给数组变量赋值的方法...
# array_name=( XXX YYY ZZZ ... )
area2=( zero one two three four )
echo -n "area2[0] = "
echo ${area2[0]}
# 阿哈, 从0开始计算数组下标(也就是数组的第一个元素为[0], 而不是[1]).
echo -n "area2[1] = "
echo ${area2[1]} # [1]是数组的第2个元素.
# -----------------------------------------------------------------
echo; echo; echo
# -----------------------------------------------
# 第3个数组, "area3".
# 第 3 种给数组元素赋值的方法
# array_name=([xx]=XXX [yy]=YYY ...)
area3=([17]=seventeen [24]=twenty-four)
echo -n "area3[17] = "
echo ${area3[17]}
echo -n "area3[24] = "
echo ${area3[24]}
# -----------------------------------------------
exit 0
/dev /proc
Linux或者UNIX机器典型地都带有 /dev 和 /proc 目录, 用于特殊目的.
/dev
/dev 目录包含物理 设备 的条目, 这些设备可能会以硬件的形式出现, 也可能不会. 包含有挂载文件 系统的硬驱动器分区, 在 /dev 目录中都有对应的条目, 就像df命令所展示的那样.
/dev 中还有少量的伪设备用于其它特殊目的, 比如 /dev/null , /dev/zero , /dev/urandom , /dev/sda1 , /dev/udp , 和 /dev/tcp
/proc
/proc 目录实际上是一个伪文件系统. /proc 目录中的文件用来映射当前运行的系统, 内核 进程 以及与它 们相关的状态与统计信息.
cat /proc/devices
zero 与 Null
/dev/null
它非常接近于一个只写文件. 所有写入它的内容都会永远丢失. 而如果想从它那读取内容, 则什么也读不到. 但是, 对于命令行和脚本来说, /dev/null 却非常的有用.
cat $filename >/dev/null
# 文件的内容不会输出到stdout.
rm $badname 2>/dev/null
# 错误消息[stderr]就这么被丢到太平洋去了.
cat $filename 2>/dev/null >/dev/null
# 如果"$filename"不存在, 将不会有错误消息输出.
# 如果"$filename"存在, 文件内容不会输出到stdout.
# 因此, 上边的代码根本不会产生任何输出.
cat /dev/null > /var/log/messages
# : > /var/log/messages 具有同样的效果, 但是不会产生新进程.(译者注: 因为是内建的)
if [ -f ~/.netscape/cookies ] # 如果存在, 就删除.
then
rm -f ~/.netscape/cookies
fi
ln -s /dev/null ~/.netscape/cookies
/dev/zero
类似于 /dev/null , /dev/zero 也是一个伪文件, 但事实上它会产生一个null流(二进制的0流, 而 不是ASCII类型). 如果你想把其他命令的输出写入它的话, 那么写入的内容会消失, 而且如果你 想从 /dev/zero 中读取一连串null的话, 也非常的困难, 虽然可以使用od或者一个16进制编辑器来 达到这个目的. /dev/zero 的主要用途就是用来创建一个指定长度, 并且初始化为空的文件, 这种 文件一般都用作临时交换文件.
#!/bin/bash
# ramdisk.sh
# 一个"ramdisk"就是系统RAM内存中的一部分,
#+ 只不过它被当作文件系统来操作.
# 它的优点是访问速度非常快(读/写时间快).
# 缺点: 易失性, 当机器重启或关机时, 会丢失数组.
#+ 而且会减少系统可用的RAM.
#
# 那么ramdisk有什么用呢?
# 保存一个大数据集, 比如保存表格或字典.
#+ 这样的话, 可以增加查询速度, 因为访问内存比访问硬盘快得多.
E_NON_ROOT_USER=70 # 必须以root身份来运行.
ROOTUSER_NAME=root
MOUNTPT=/mnt/ramdisk
SIZE=2000 # 2K个块(可以进行适当的修改)
BLOCKSIZE=1024 # 每块的大小为1K(1024字节)
DEVICE=/dev/ram0 # 第一个ram设备
username=`id -nu`
if [ "$username" != "$ROOTUSER_NAME" ]
then
echo "Must be root to run \"`basename $0`\"."
exit $E_NON_ROOT_USER
fi
if [ ! -d "$MOUNTPT" ] # 测试挂载点是否已经存在,
then #+ 如果做了这个判断的话, 当脚本运行多次的时
候,
mkdir $MOUNTPT #+ 就不会报错了. (译者注: 主要是为了避免多
次创建目录.)
fi
dd if=/dev/zero of=$DEVICE count=$SIZE bs=$BLOCKSIZE # 把RAM设备的内
容用0填充.
# 为什么必须这
么做?
mke2fs $DEVICE # 在RAM上创建一个ext2文件系统.
mount $DEVICE $MOUNTPT # 挂载上.
chmod 777 $MOUNTPT # 使一般用户也可以访问这个ramdisk.
# 然而, 只能使用root身份来卸载它.
echo "\"$MOUNTPT\" now available for use."
# 现在ramdisk就可以访问了, 即使是普通用户也可以访问.
# 小心, ramdisk存在易失性,
#+ 如果重启或关机的话, 保存的内容就会消失.
# 所以, 还是要将你想保存的文件, 保存到常规磁盘目录下.
# 重启之后, 运行这个脚本, 将会再次建立一个ramdisk.
# 如果你仅仅重新加载/mnt/ramdisk, 而没有运行其他步骤的话, 那就不会正常工作.
# 如果对这个脚本进行适当的改进, 就可以将其放入/etc/rc.d/rc.local中,
#+ 这样, 在系统启动的时候就会自动建立一个ramdisk.
# 这么做非常适合于那些对速度要求很高的数据库服务器.
exit 0
调试
Bash并不包含调试器, 甚至都没有包含任何用于调试目的的命令和结构. 脚本中的语法错误, 或者拼写错误只会产生模糊的错误信息, 当你调试一些非功能性脚本的时候, 这些错误信息通常都不会提供有意义的帮助.
问题
- 由于"syntax error"(语法错误)使得脚本停止运行,
- 或者脚本能够运行, 但是并不是按照我们所期望的那样运行(逻辑错误).
- 脚本能够按照我们所期望的那样运行, 但是有烦人的副作用(逻辑炸弹).
调试方式
-
echo语句可以放在脚本中存在疑问的位置上, 来观察变量的值, 也可以了解脚本后续的动作.
### 只有在DEBUG变量被赋值的情况下, 才会打印传递进来的参数. ### debecho () { if [ ! -z "$DEBUG" ]; then echo "$1" >&2 # ^^^ 打印到stderr fi } DEBUG=on Whatever=whatnot debecho $Whatever # whatnot DEBUG= Whatever=notwhat debecho $Whatever # (这里就不会打印.)
-
使用过滤器tee来检查临界点上的进程或数据流.
-
置选项 -n -v -x
sh -n scriptname 不会运行脚本, 只会检查脚本的语法错误.
sh -v scriptname 将会在运行脚本之前, 打印出每一个命令.
sh -x scriptname 会打印出每个命令执行的结果, 但只使用缩写形式.
-
使用"assert"(断言)函数在脚本的临界点上测试变量或条件.
-
使用变量$LINENO和内建命令caller.
-
捕获exit.
选项
选项用来更改shell和脚本的行为. set命令用来打开脚本中的选项. 你可以在脚本中任何你想让选项生效的地方插入set -o option-name,或者使用更简单的形式, set -option-abbrev. 这两种形式是等价的.
#!/bin/bash
set -o verbose
#打印出所有执行前的命令.
set -v
# 与上边的例子具有相同的效果.
set +v
#不打印
#!/bin/bash -x
# sharp-bang 指定参数
# 下边是脚本的主要内容
陷阱 P389
- 将保留字或特殊字符声明为变量名
- 使用连字符或其他保留字符来做变量名(或函数名).
- 让变量名与函数名相同
- 不合时宜的使用空白字符
- 定未初始化的变量(赋值前的变量)被"清0".
- 混淆测试符号 = 和 -eq .
- 误用了字符串比较操作符.
- 在非Linux机器上的Bourne shell脚本( #!/bin/sh )中使用Bash特有的功能, 可能会引起不可预料的行为.
- 使用Bash未文档化的特征, 将是一种危险的举动
- 一个带有DOS风格换行符( \r\n )的脚本将会运行失败
- 以 #!/bin/sh 开头的Bash脚本, 不能在完整的Bash兼容模式下运行. 某些Bash特定的功能可能会被禁用.
- ...
脚本编程风格 P396
杂项 P399
注意:本文归作者所有,未经作者允许,不得转载