<!-- @page { margin: 2cm } P { margin-bottom: 0.21cm } TD P { margin-bottom: 0cm } -->
Here 文档:允许用户把脚本的内容重定向为脚本的输入。
例子:
1 grep -i "$1" <<+
2 Alex June 22
3 Barbara February 3
4 Darlene May 8
5 Helen March 13
6 Jenny January 23
7 Nancy June 26
8 +
9
两个小于号(<<) 说明后面跟的就是一个Here 文档,后面的加号(+) 用于界定Here 文档的内容,当然可以被其他符号代替。开始的界定符必须单独占用一行,结束的界定符必须独占一行。shell 将界定符之间的所有内容当作标准输入发送给进程。就好像是把标准输入重定向到一个文件上,只不过这个文件的内容已经在脚本中嵌入了。
文件描述符:一个进程无论是从文件中读内容还是向文件中写内容之前都必须先打开这个文件。linux 给该文件分配一数字作为文件描述符。一个典型的linux 进程在启动的时候包含3 个文件描述符号,标准输入(0) 、标准输出(1) 、标准错误输出(2) 。一个用户打开的文件,要么作为输入,要么作为输出。
例子:
1 #!/bin/bash
2 exec 4> filed.out
3 echo "test text!" >&4
4 exec 4<&-
5 exec 5<filed.out
6 cat -n <&5
7 exec 5<&-
打开文件描述符:
exec n> outfile --- 打开文件作为输出文件,描述符 n
exec m< infile ---- 打开文件作为输入文件,描述符 m
复制文件描述符:
exec m<&n --------- 复制输入文件描述符, n 到 m
exec n>&m --------- 复制输出文件描述符, m 到 n
关闭文件描述符:
exec n<&-
因为exec 是内置命令,所以在运行包含exec 打开文件描述符的shell 脚本的进程中,该文件描述符将一直有效。也就是说同一个脚本里利用exec 打开的文件描述符将在其后的命令执行过程中继续有效。
参数和变量:
数组变量,数组的下标是整数并以数字0 作为起始。
name=(element1 element2 …)
>NAMES=(max helen sam zach)
>echo ${NAMES[2]}
>echo ${NAMES[*]}
>echo ${NAMES[@]}
>echo ${#NAMES[*]}
>echo ${#NAMES[2]}
>NAMES[0]=leon
>echo ${NAMES[*]}
>A=(“${NAMES[*]}”)
>B=(“${NAMES[@]}”)
>declare -a
>echo ${A[2]}
>echo ${A[0]}
>echo ${B[2]}
>echo ${B[0]}
>unset A B NAMES
’@‘-- 把数组中的内容复制到一个新数组中,生成的新数组和原来是一样的。
‘*’--- 把原来的数组中的所有元素当成一个元素复制到新的数组中,生成的新数组只有一个元素。
${#name [*]}— 返回数组变量name 中元素的个数,如果将其中的'*' 替换为数组下标则返回数组中对应元素内容的长度。
在bash 中使用export 命令来使父进程的变量对子进程可用。利用export 声明一个参数作为参数,shell 就会把变量的值传递到子shell 的调用环境中,值传递方式 使得每个子进程在使用变量时都得到一个副本。
>cat -n extest1
1 cheese=american
2 echo "extest1 1:$cheese"
3 ./subtest
4 echo "extest1 2:$cheese"
>cat -n subtest
1 echo "subtest 1:$cheese"
2 cheese=swiss
3 echo "subtest 2:$cheese"
4
>cat -n extest2
1 export cheese=american
2 echo "extest1 1:$cheese"
3 ./subtest
4 echo "extest1 2:$cheese"
>./extest1
>./extest2
subtest 脚本不能从extest1 脚本接收变量cheese 的内容,同时extest1 脚本中的变量也不会失去其内容,subtest 与extest1 中的变量没有关系。subtest 从extest2 脚本继承变量cheese 的值,并在其脚本中将其副本重新赋值,而当控制权回到父进程时,显示的是原有变量的值,而不是副本的值,subtest 与extest2 中变量是值传递关系。值传递关系类似C 语言中形参和实参的关系,只是形参和实参名字相同而已。
函数 由于函数运行的环境通常与其被调用的环境相同,所以其中变量是显示的被shell 和调用它的函数一起共享的。注意export 对变量的声明的区别,export 用于父子进程之间的变量;而函数与调用它的shell 在同一环境中运行。
>function nam(){ ENTER
echo $myname ENTER
myname=zach ENTER
} ENTER
>myname=sam
>nam
>echo $myname
上面的例子可以看出,函数和shell 本身共享同一个变量。
函数中的局部变量:局部变量可以避免众多程序调用同一个函数,函数中的变量名与调用该函数的程序中的变量名相冲突。使用内置命令typeset 声明变量为局部变量。
$ function count_down(){
typeset count
count=$1
while [ $count -gt 0 ]
do
echo "$count..."
((count=count-1))
sleep 1
done
echo "Blast off."
}
>count=10
>echo $count
>count_down 4
>echo $count
((count=count-1)) 赋值语句包含在双括号中,保证shell 把他们看着是一个算数表达式。双括号中可以不加$ 来引用变量。
特殊参数:shell 把执行shell 的进程的PID 号保存到参数$$ 中。
>echo $$
>echo $0
>cat >pid.test
echo “$0 PID=$$”
CTRL+D
>chmod u+x pid_test
>./pid.test
后台运行的进程的 PID 号存储在符号 $! 中,注意参数 !$ 保存的是上一个命令的最后一个参数。
>xclock &
>echo $!
一个进程无论由于何种原因停止运行,都要向父进程返回一个exit 状态。$? 保存上一个命令的返回状态码。约定返回非零码代表一个false 状态,同时还意味着命令的执行是失败的。返回状态码为0 意味着命令被成功执行。
>cat >es
echo This program returns an exit status of 7.
exit 7
CTRL+D
>chmod u+x es
>./es
>echo $?
>echo $?
位置参数 位置参数包含命令行上的命令和后面所跟的参数。
$# 命令行参数的个数,保存了除命令自身之所的参数的个数。
$0 调用程序的名称。
$1-$n 命令行参数。$8 ${12} ---${name}
>cat -n abc
1 echo "The command used to run this script is $(basename $0)"
2 echo "The script was called with $# arguments"
3 echo "The arguments are:$1 $2 $3 $4"
4 echo "The all arguments are:$*"
>echo $xx
>unset xx
>./abc $xx haha hehe jiji
>./abc “$xx” haha hehe jiji
注意最后两种调用方式的区别:一个是3 个参数,一个是4 个参数。
$* 和 $@ :前者产生一个参数,把位置参数看作一个参数,包括位置参数之间的空格。后者生成一串参数,其中每个参数依然是单独的参数,与数组里引用数组变量情况一致。
1 set "$*"
2 echo $# parameters with '"$*"'
3 echo 1:$1
4 echo 2:$2
5 echo 3:$3
-----------------------
1 set "$@"
2 echo $# parameters with '"$@"'
3 echo 1:$1
4 echo 2:$2
5 echo 3:$3
注意上面两个脚本运行结果的不同,对于同样的调用情况,第一个脚本得到的$# 其值始终是1 ,因为其中的$* 只产生一个参数。而第二个脚本得到的$# 将是实际调用时的参数个数。
shift 左移命令行参数,第1 个参数($1) 丢弃,第2 个参数($2) 变成第1 个参数($1) ,其余以此类推。
由于没有unshift 命令,所以被丢弃的参数不能找回。
1 echo "arg1=$1 arg2=$2 arg3=$3"
2 shift
3 echo "arg1=$1 arg2=$2 arg3=$3"
4 shift
5 echo "arg1=$1 arg2=$2 arg3=$3"
6 shift
set 初始化命令行参数,把set 后面跟的一个或者几个参数赋值给位置参数,这些位置参数以$1 打头。
1 set haha hehe hoho
2 echo $3 $2 $1
set 命令可以与其他内置命令结合起来一起使用,这样可以在shell 脚本中很方便地把某些内置命令与标准输出格式化成一种很容易识别和使用的形式。
如果不带任何参数使用 set 命令,那么 set 命令就会显示出一列已经设置好的 shell 变量,包括用户创建的变量以及关键字变量。在 bash 下它显示的内容与不带参数使用 declare 和 typeset 命令时显示内容相同。
set 命令可以接受选项,该选项可以让用户定制shell 的行为。'set +o'
${name } 扩展为变量name 的值,如果name 变量为空或还没有设置,bash 就将${name} 扩展成为一个空串。
${name:-default} 使用一个默认的值来替代那些空的或者没有赋值 的变量而作为整个表达式的值,如果有值就使用name 的值。
${name:default} 与${name:-default} 的区别在与它将使用default 对name 赋值。
${name:-default} 只是使用default 来作为整个表达式的值,对name 没有影响-- 使用默认值。
${name:default} 不仅使用default 来作为整个表达式的值,同时将改变name 的值-- 赋默认值。
${name:?message} 如果name 为空或未赋值,(:?) 修饰符就会显示相应错误信息。
内置命令:
type 显示命令相关信息。
read 接受用户输入。
1 echo -n "Go ahead:"
2 read firstline
3 echo " You entered: $firstline"
--------
1 echo -n "Go ahead:"
2 read firstline
3 echo You entered: $firstline
---------
1 read -p "Go ahead: " word1 word2 word3
2 echo "You entered: $word1 "
3 echo "You entered: $word2 "
4 echo "You entered: $word3 "
注意以上3 种使用read 命令的方式的区别,1 和2 的区别在于是否使用了双引号将echo 的输出进行引用,如果没有使用双引号,则如果输入'*' ,则1 和2 得到的结果不同,在2 中会对其进行路径名扩展。
而第3 个脚本使用了'-p' 选项来显示用户的提示来替代echo 命令。
$ while read first rest
> do
> echo $rest $first
> done <names
>exec 3< name
>read -u3 line1;echo $line1;read -u3 line2;echo $line2
>exec 3<&-
exec 执行命令,使用 exec 可以不用创建新进程来执行一个命令;使用 exec 可以重定向 shell 脚本内部的文件描述符 。假如shell 执行的命令不是来自shell 内部,那么执行这个命令就会创建一个新进程。这个新进程继承来自父进程的环境变量( 全局变量或则导出变量) 而不会继承父进程中没有使用export 导出的变量。相反,exec 执行的命令会覆盖当前进程。
exec 与‘.’ 的比较,当exec 在原有进程环境状态下执行命令时,它的功能与'.' 是很相似的。不同在于:
'.' 只能执行shell 脚本命令,exec 可以执行脚本也可以执行编译程序;其次,'.' 在结束其任务时会把控制返回到原来的脚本中,而exec 命令不会返回其控制状态;'.' 可以把授予新进程以本地变量的访问权限而exec 不行。
1 who
2 exec date
3 echo "This line is never displayed."
-----
1 #/bin/bash
2 set -x
3 if [ $# -eq 0 ]
4 then
5 echo "Usage:out [-v] filename ...." 1>&2
6 exit 1
7 fi
8 if [ "$1" = "-v" ]
9 then
10 shift
11 less -- "$@"
12 else
13 cat -- "$@"
14 fi
15
-----
1 #/bin/bash
2 set -x
3 if [ $# -eq 0 ]
4 then
5 echo "Usage:out2 [-v] filename ...." 1>&2
6 exit 1
7 fi
8 if [ "$1" = "-v" ]
9 then
10 shift
11 exec less "$@"
12 else
13 exec cat -- "$@"
14 fi
15
-----
1 echo "message to standard output."
2 echo "message to standard error." 1>&2
3 echo "message to the user" > /dev/tty
4 echo "message to the user" > /dev/pts/0
/dev/tty 表示用户的工作屏幕;可以使用这个变量来引用用户的屏幕而不用关心具体是哪个设备。 tty 显示用户正在使用的具体设备名 (/dev/pts/0) 。
trap 捕获信号,使用trap 内置命令来捕获一个或多个信号,以便于用户在收到一个特殊的信号时采取相应的动作。信号向进程报告外部状况。
非真实信号 |
EXIT |
0 |
由exit 命令导致的退出或程序执行完成,不是以真正的信号 |
挂起 |
SIGHUP 或HUP |
1 |
断开执行 |
中断 |
SIGINT 或INT |
2 |
按下中断键(CTRL+C) |
退出 |
SIGQUIT 或QUIT |
3 |
按下退出键(CTRL+SHIFT+|) |
结束 |
SIGKILL 或KILL |
9 |
使用kill 命令带选项'-9' 会产生这个信号( 不能被捕获) |
软件中断 |
SIGTERM 或TERM |
15 |
kill 命令的默认执行 |
停止 |
SIGTSTP 或TSTP |
20 |
按下抑制键(CTRL+Z) |
调试 |
DEBUG |
|
在每个命令执行后执行trap 语句指定的命令 |
错误 |
ERR |
|
在那些没有正常终止的命令结束之后执行trap 语句指定的内容 |
如果用户没有在脚本中使用trap 命令,那么在shell 运行过程中一旦出现上面6 个信号中的任何一个都会中止脚本的运行。
trap ['commands'] [signal]
当执行过commands 的内容后,shell 会恢复执行commands 命令离开处的脚本。在收到一个信号时,如果用户想使用trap 阻止脚本退出但又不想运行任何一个显式命令,那么可以将commands 指定为一个空串。
1 #! /bin/bash
2 trap 'echo You Enter control+c;' INT
3 while true
4 do
5 echo "program running."
6 sleep 1
7 done
-----
1 #! /bin/bash
2 script=$(basename $0)
3
4 if [ ! -r "$HOME/banner" ]
5 then
6 echo "$script:need readable $HOME/banner file" 1>&2
7 exit 1
8 fi
9
10 trap 'exit 1' 1 2 3 15
11 trap 'rm /tmp/$$.$script 2> /dev/null' 0
12
13 for file
14 do
15 if [ -r "$file" -a -w "$file" ]
16 then
17 cat $HOME/banner $file > /tmp/$$.$script
18 cp /tmp/$$.$script $file
19 echo "$script:banner added to $file"
20 else
21 echo "$script:need read and write permission for $file" 1>&2
22 fi
23 done
上面脚本当调用时带一个或两个文件名参数时,该脚本将循环扫描文件并且会在每个文件的顶部加一个标题,标题使用~/banner 中的内容。trap 命令用来删除该脚本的临时文件,上面脚本在正常结束或者由于挂起,软件中断,退出或者软件调试信号而终止时,利用两个trap 删除临时文件。
kill 终止进程,kill 内置命令用来给一个进程或者作业发送信号。
kill [-signal] PID
signal 是信号的名字或者信号的编号,PID 是要接收信号的进程ID ,可以使用%n 的形式来指定一个作业编号来替代PID 。如果省略signal ,kill 就会发送一个TERM 信号。被打断的程序可能会出现一些预想不到的事情,所以好的程序都会捕获INT QUIT 和 TERM 信号。
getopts: 解析选项,用来解析命令行参数。
>getopts optstring varname [arg... ]
其中,optstring 是合法的字母选项列表,varname 用来保存每次接收的选项值,arg 是即将被处理的可选参数。若存在arg 参数,getopts 就去处理命令行参数。若optstring 以冒号: 作为开始,则由脚本负责产生错误信息,否则由getopts 产生错误信息。getopts 使用变量OPTIND( 选项索引) 和OPTARG( 选项参数) 来保存和选项相关的值。当shell 脚本启动时,OPTIND 的值被设为1 ,以后每次当getopts 命令发现一个参数,它就增加OPTIND 的值,该值与下一个将要处理的选项的索引相等。如果选项中含有参数,bash 将参数的值赋给OPTARG 。为了指名某个选项含有参数,在optstring 中相应的字母后面加上一个冒号':' 。
'dxo:lt:r'---- 告诉getopts 应当搜索'-d' '-x' '-o' '-l' '-t' 和'-r' 。并且选项'-o' 和'-t' 后面带有参数。
--------
1 #/bin/bash
2 SKIPBLANKS=
3 TMPDIR=/tmp
4 CASE=lower
5 while [[ "$1" = -* ]]
6 do
7 case $1 in
8 -b) SKIPBLANKS=TRUE ;;
9 -t) if [ -d "$2" ]
10 then
11 TMPDIR=$2
12 shift
13 else
14 echo "$0:-t takes a directory argument." >&2
15 exit 1
16 fi ;;
17 -u) CASE=upper ;;
18 --) break ;;
19 *) echo "$0:Invalid option $1 ignored." >&2 ;;
20 esac
21 shift
22 done
--------
1 #/bin/bash
2 SKIPBLANKS=
3 TMPDIR=/tmp
4 CASE=lower
5 while getopts :bt:u arg
6 do
7 case $arg in
8 b) SKIPBLANKS=TRUE ;;
9 t) if [ -d "$OPTARG " ]
10 then
11 TMPDIR=$OPTARG
12 else
13 echo "$0:$OPTARG takes a directory argument." >&2
14 exit 1
15 fi ;;
16 u) CASE=upper ;;
17 :) echo "$0:must supply an argument to -$OPTARG" >&2
18 exit 1 ;;
19 /?) echo "$0:Invalid option -$OPTARG ignored." >&2 ;;
20 esac
21 done
区别上面两个shell 脚本,第一个程序循环的条件是参数不是两个连字符,当满足循环条件时,程序就利用case 语句来检查可能的选项。'--'case 用来退出while 循环。'*'case 标记可以识别的任意一个选项,它是作为最后一个case 语句用来捕获所有不能识别的选项。每次循环一个选项就移动到下一个参数,如果选项带有参数,程序就再次左移以跳过选项的参数。第二个程序,使用getopts 来解释选项,getopts 利用OPTIND 变量来获知下次调用时即将处理的参数的索引。所以不用shift 命令。case 结构不再需要一个连字符为开头,因为arg 参数的值就是选项字母(getopts 去掉了连字符) 。此外getopts 把'--' 着为选项的结束,所以不用显式的指名'--'case 路径。因为用户告诉了getopts 那些选项是合法的,同时那些选项后面带有参数。所以getopts 能够发现那些选项是合法的,那些是需要参数的。当 getopts 发现一个非法选项时,将 varname 的值设为 '?' ,并把OPTARG 设置为相应的选项字母。当它 发现一个选项缺少参数时, getopts 把 varname 设置为 ':' ,并把OPTARG 设为缺少参数的选项。
表达式:
算术表达式,let 语句中不需要给变量前面加$ 符号,但是必须将单个的变量或者带有空格的表达式用双引号括起来。bash 也接受((expression)) 作为let “expression” 的同义表示。
>((VALUE=VALUE * 10+NEW))
>((VALUE=VALUE * 10 + NEW))
>let VALUE=VALUE*10+NEW
>let “VLUAE=VALUE * 10 + NEW”
>((COUNT = COUNT + 1, VALUE = VALUE*10 + NEW))
注意,算术赋值和算术扩展是不同的,算术扩展语法是$((expression)) ,用它给表达式赋值并用得到的结果来取代$((expression)) 。可以使用算术扩展来显示表达式的值或者把值赋给一个变量。
算术赋值的语法形式是let expression 或者((expression)) 。用来给表达式赋值并返回一个状态码。利用算术赋值可以进行逻辑比较或赋值运算。
逻辑表达式,可以利用((expression)) 语法结构来表示逻辑表达式。
1 #!/bin/bash
2 echo -n "How old are you?"
3 read age
4 if ((30 < age && age < 60)) ; then
5 echo "Wow,in $((60-age)) years,you'll be 60!"
6 else
7 echo "You are too young or too old to play."
8 fi
-------
1 #!/bin/bash
2 echo -n "How old are you?"
3 read age
4 if [ 30 -lt $age -a $age -lt 60 ] ; then
5 echo "Wow,in $((60-age)) years,you'll be 60!"
6 else
7 echo "You are too young or too old to play."
8 fi
------
1 #!/bin/bash
2 echo -n "How old are you?"
3 read age
4 if [[ 30 < $age && $age < 60 ]] ; then
5 echo "Wow,in $((60-age)) years,you'll be 60!"
6 else
7 echo "You are too young or too old to play."
8 fi
if 语句中条件测试语句给用布尔and 链接起来的两个逻辑比较式赋值,当 两个式子都为真时返回 0(true) 。 其他返回 1(false) 。
逻辑表达式:条件表达式,在expression 中必须在变量的名字前面加上$ 符号。执行结果就像test 一样,返回的是一个状态。
[[ expression ]]
条件表达式也可以用于字符串的比较,操作符 ‘ =’ 进行类型匹配比较 而不是仅仅进行相等比较:
[[ artist = a* ]] 返回真 (0), 而 [[ a* = artist ]] 为假 (1) 。注意格式中的空白符不可省略。
>[[ -d bin && -f src/myscript.bash ]] && cp src/myscript.bash /
bin/myscript && chmod +x bin/myscript || echo “cannot make /
executable version of myscript”
字符串模式匹配,可以从字符串的前缀或后缀中删去字符串,4 个字符串匹配操作符。
操作符 |
功能 |
# |
去除最小匹配前缀 |
## |
去除最大前缀匹配 |
% |
去除最小匹配后缀 |
%% |
去除最大匹配后缀 |
>SOURCEFILE=/usr/local/src/prog.c
>echo ${SOURCEFILE##/*/}
>echo ${SOURCEFILE%/*}
>echo ${SOURCEFILE%%/*}
>echo ${SOURCEFILE%.c}
>echo ${#SOURCEFILE}
最后一个命令是显示SOURCEFILE 的字符个数。
操作符:算术扩展和算术赋值使用了和C 语言相同的语法、操作符的运算优先级以及表达式的关联关系。
操作符类型 |
功能 |
后置 |
var++ |
var-- |
|
前置 |
++var |
--var |
|
一元 |
- 一元减 |
+ 一元加 |
|
取反 |
! 布尔取反 |
~ 二进制取反 |
|
取幂 |
** 幂指数 |
乘法、除法和取模 |
* |
/ |
|
% 取模 |
|
加减 |
- |
+ |
|
二进制移位 |
<< |
>> |
|
比较运算符 |
<= |
>= |
|
< |
|
> |
|
相等或不等 |
= 相等 |
!= |
|
二进制位运算符 |
& |
^ |
|
| |
|
布尔( 逻辑) 运算 |
&& |
|| |
|
条件赋值 |
?: 三元操作符 |
赋值 |
= 、*= 、/= 、%= 、+= 、-= 、<<= 、>>= &= 、^= 、|= 赋值操作 |
逗号 |
, |
管道 管道操作符的优先级比所有的操作优先级都要高。
增加和减少操作符 前置加,前置减,后置加和后置减。
> N=10
> echo $N
> echo $((--N+3))
> echo $N
> echo $((N++ - 3))
> echo $N
> unset N
取模操作符 取出第一操作数被第二个操作数除之后的余数.
> echo $((17%5))
布尔操作符 布尔操作符号'&&' 和'||' 被称为短路操作符。如果仅仅使用左边的操作数就可以得出最终结果,那么右边的操作数就可以不用执行。
>mkdir bkup && cp -r src bkup
>mkdir bkup || echo “mkdir of bkup failed” >> /trap/log
>false;echo $?
因为短路操作符的右边表达式很可能不会被执行,所以在给操作符右边的表达式赋值时一定要特别小心。
>((N=10,Z=0))
>echo $((N || ((Z+=1)) ))
三重条件符 三重比较符?: 语法同c 语言。
基于其他基数的赋值 使用语法base #n 可以给变量赋值基数为base 的n( 基于base 的表达形式) 。
>((v1=2#0101))
>((v2=2#0110))
>echo $v1 $v2
shell 程序
>cat -n makepath.sh
1 function makepath()
2 {
3 if [[ ${#1} -eq 0 || -d "$1" ]]
4 then
5 return 0
6 fi
7 if [[ "${1%/*} " = "$1" ]]
8 then
9 mkdir $1
10 return $?
11 fi
12 makepath ${1%/*} || return 1
13 mkdir $1
14 return $?
15 }
>chmod u+x makepath.sh
>./makepath.sh
>set | grep makepath
>bash makepath.sh
>set | grep makepath
>. makepath.sh
>set | grep makepath
>makepath a/b/c
>rm -r a
其中'${#1}' 是'${#$1}' 的简写形式,得到位置参数$1 的长度。
'${1%/*}' 是'${$1%/*}' 的简写,去除最小匹配'/*' 。
同时注意三种运行makepath.sh 脚本方式的区别,只有第三种方式才能在当前shell 命令行中调用makepath 函数,而前两种随着创建的进程的结束,makepath 函数的生存周期也就结束了,要对其进行调用可以在脚本文件内部进行调用。
注意在shell 函数中定义的变量有全局性,也就是在同一进程中有全局性。所以需要使用typeset 命令将其中的所有变量设置为局部变量。
通过在用户工作的shell 中输入
set -o xtrace
打开调试命令,注意与shell 脚本中'set -x' 使用形式的区别。