Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1637823
  • 博文数量: 584
  • 博客积分: 13857
  • 博客等级: 上将
  • 技术积分: 11883
  • 用 户 组: 普通用户
  • 注册时间: 2009-12-16 09:34

分类: LINUX

2010-01-06 15:14:06

摘自《高级bash脚本编程指南》

索引

I/O文件系统变量脚本行为命令作业控制命令

内建命令指的就是包含在Bash工具包中的命令, 从字面意思上看就是 built in. 这主要是考虑到执行效率的问题 — 内建命令将比外部命令执行的更快, 一部分原因是因为外部命令通常都需要fork出一个单独的进程来执行 — 另一部分原因是特定的内建命令需要直接访问shell的内核部分.

当一个命令或者是shell本身需要初始化(或者创建)一个新的子进程来执行一个任务的时候, 这种行为被称为fork. 这个新产生的进程被叫做子进程, 并且这个进程是从父进程中fork出来的. 当子进程执行它的任务时, 父进程也在运行.

注意: 当父进程获得了子进程的进程ID时, 父进程可以给子进程传递参数, 然而反过来却不行. 这将会产生不可思议的并且很难追踪的问题.

通常情况下, 脚本中的Bash内建命令在运行的时候是不会fork出一个子进程的. 但是脚本中的外部或者过滤命令通常会fork出一个子进程.

一个内建命令通常会与一个系统命令同名, 但是Bash在内部重新实现了这些命令. 比如, Bash的echo命令与/bin/echo就不尽相同, 虽然它们的行为在绝大多数情况下都是一样的.

关键字的 意思就是保留字, 对于shell来说关键字具有特殊的含义, 并且用来构建shell语法结构. 比如, “for”, “while”, “do”, 和 “!” 都是关键字. 与内建命令相似的是, 关键字也是Bash的骨干部分, 但是与内建命令不同的是, 关键字本身并不是一个命令, 而是一个比较大的命令结构的一部分.

echo

打印(到 stdout)一个表达式或者变量

echo命令需要 -e 参数来打印转义字符.

例子 1:转义符

  1 #!/bin/bash
2 # escaped.sh: 转义符
3
4 echo; echo
5
6 echo "\v\v\v\v" # 逐字的打印\v\v\v\v.
7 # 使用-e选项的'echo'命令来打印转义符.
8 echo "============="
9 echo "VERTICAL TABS"
10 echo -e "\v\v\v\v" # 打印4个垂直制表符.
11 echo "=============="
12
13 echo "QUOTATION MARK"
14 echo -e "\042" # 打印" (引号, 8进制的ASCII 码就是42).
15 echo "=============="
16
17 # 如果使用$'\X'结构,那-e选项就不必要了.($' '字符串扩展结构
18 echo; echo "NEWLINE AND BEEP"
19 echo $'\n' # 新行.
20 echo $'\a' # 警告(蜂鸣).
21
22 echo "==============="
23 echo "QUOTATION MARKS"
24 # 版本2以后Bash允许使用$'\nnn'结构.
25 # 注意在这里, '\nnn\'是8进制的值.
26 echo $'\t \042 \t' # 被水平制表符括起来的引号(").
27
28 # 当然,也可以使用16进制的值,使用$'\xhhh' 结构.
29 echo $'\t \x22 \t' # 被水平制表符括起来的引号(").
30 # 感谢, Greg Keraunen, 指出了这点.
31 # 早一点的Bash版本允许'\x022'这种形式.
32 echo "==============="
33 echo
34
35
36 # 分配ASCII字符到变量中.
37 # ----------------------------------------
38 quote=$'\042' # " 被赋值到变量中.
39 echo "$quote This is a quoted string, $quote and this lies outside the quotes."
40
41 echo
42
43 # 变量中的连续的ASCII字符.
44 triple_underline=$'\137\137\137' # 137是八进制的'_'.
45 echo "$triple_underline UNDERLINE $triple_underline"
46
47 echo
48
49 ABC=$'\101\102\103\010' # 101, 102, 103是八进制码的A, B, C.
50 echo $ABC
51
52 echo; echo
53
54 escape=$'\033' # 033 是八进制码的esc.
55 echo "\"escape\" echoes as $escape"
56 # 没有变量被输出.
57
58 echo; echo
59
60 exit 0

转义是一种引用单个字符的方法. 一个前面放上转义符 (\)的字符就是告诉shell这个字符按照字面的意思进行解释, 换句话说, 就是这个字符失去了它的特殊含义.

在某些特定的命令和工具中, 比如echo和sed, 转义符往往会起到相反效果 – 它反倒可能会引发出这个字符的特殊含义.例如:

\n  表示新的一行
\r 表示回车
\t 表示水平制表符
\v 表示垂直制表符
\b 表示后退符
\a 表示"alert"(蜂鸣或者闪烁)
\0xx转换为八进制的ASCII码, 等价于0xx

等等。

通常情况下, 每个echo命令都会在终端上新起一行, 但是-n参数会阻止新起一行.

echo命令可以作为输入, 通过管道传递到一系列命令中去.

  1 if echo "$VAR" | grep -q txt   # if [[ $VAR = *txt* ]]
2 then
3 echo "$VAR contains the substring sequence \"txt\""
4 fi

echo命令可以与命令替换组合起来, 这样可以用来设置一个变量.

a=`echo "HELLO" | tr A-Z a-z`

注:echo `command`将会删除任何由command所产生的换行符.

$IFS (内部域分隔符) 一搬都会将 \n (换行符) 包含在它的空白字符集合中. Bash因此会根据参数中的换行来分离command的输出, 然后echo. 最后echo将以空格代替换行来输出这些参数.

fhc2007@fhc2007-desktop:~/program/source$ ls -l
总用量 0
-rw-r--r-- 1 fhc2007 fhc2007 0 2009-10-02 12:51 test1
-rw-r--r-- 1 fhc2007 fhc2007 0 2009-10-02 12:51 test2
fhc2007@fhc2007-desktop:~/program/source$ echo `ls -l`
总用量 0 -rw-r--r-- 1 fhc2007 fhc2007 0 2009-10-02 12:51 test1 -rw-r--r-- 1 fhc2007 fhc2007 0 2009-10-02 12:51 test2

printf

printf命令, 格式化输出, 是echo命令的增强版. 它是C语言printf()库函数的一个有限的变形, 并且在语法上有些不同.(基本相同,只是不需要“()”来括住其后的格式字符串以及变量参数)

    printf format-string... parameter...

这是Bash的内建版本, 与/bin/printf或者/usr/bin/printf命令不同. 如果想更深入的了解, 请察看printf(系统命令)的man页.

例子 2. 使用printf的例子

  1 #!/bin/bash
2 # printf 示例
3
4 PI=3.14159265358979
5 DecimalConstant=31373
6 Message1="Greetings,"
7 Message2="Earthling."
8
9 echo
10
11 printf "Pi to 2 decimal places = %1.2f" $PI
12 echo
13 printf "Pi to 9 decimal places = %1.9f" $PI # 都能够正确的结束.
14
15 printf "\n" # 打印一个换行,
16 # 等价于 'echo' . . .
17
18 printf "Constant = \t%d\n" $DecimalConstant # 插入一个 tab (\t).
19
20 printf "%s %s \n" $Message1 $Message2
21
22 echo
23
24 # ==========================================#
25 # 模拟C函数, sprintf().
26 # 使用一个格式化的字符串来加载一个变量.
27
28 echo
29
30 Pi12=$(printf "%1.12f" $PI)
31 echo "Pi to 12 decimal places = $Pi12"
32
33 Msg=`printf "%s %s \n" $Message1 $Message2`
34 echo $Msg; echo $Msg
35
36 # 像我们所看到的一样, 现在'sprintf'可以
37 #+ 作为一个可被加载的模块,
38 #+ 但是不具可移植性.
39
40 exit 0

read

从stdin中”读取”一个变量的值, 也就是, 和键盘进行交互, 来取得变量的值. 使用-a参数可以read数组变量。

例子 3. 使用read来进行变量分配

  1 #!/bin/bash
2 # "Reading" 变量.
3
4 echo -n "Enter the value of variable 'var1': "
5 # -n 选项, 阻止换行.
6
7 read var1
8 # 注意: 在var1前面没有'$', 因为变量正在被设置.
9
10 echo "var1 = $var1"
11
12
13 echo
14
15 # 一个单独的'read'语句可以设置多个变量.
16 echo -n "Enter the values of variables 'var2' and 'var3' (separated by a space or tab): "
17 read var2 var3
18 echo "var2 = $var2 var3 = $var3"
19 # 如果你只输入了一个值, 那么其他的变量还是处于未设置状态(null).
20
21 exit 0

一个不带变量参数的read命令, 将会把来自键盘的输入存入到专用变量$REPLY中.

一般的, 当输入给read时, 输入一个 \ , 然后回车, 将会阻止产生一个新行. -r 选项将会让 \ 转义.

例子 4. read命令的多行输入

  1 #!/bin/bash
2
3 echo
4
5 echo "Enter a string terminated by a \\, then press ."
6 echo "Then, enter a second string, and again press ."
7 read var1 # 当 read $var1 时, "\" 将会阻止产生新行.
8 # first line \
9 # second line
10
11 echo "var1 = $var1"
12 # var1 = first line second line
13
14 # 对于每个以 "\" 结尾的行,
15 #+ 你都会看到一个下一行的提示符, 让你继续向var1输入内容.
16
17 echo; echo
18
19 echo "Enter another string terminated by a \\ , then press ."
20 read -r var2 # -r 选项会让 "\" 转义.
21 # first line \
22
23 echo "var2 = $var2"
24 # var2 = first line \
25
26 # 第一个 就会结束var2变量的录入.
27
28 echo
29
30 exit 0

read命令有些有趣的选项, 这些选项允许打印出一个提示符, 然后在不输入ENTER的情况下, 可以读入你所按下的字符的内容.

  1 # 不敲回车, 读取一个按键字符.
2
3 read -s -n1 -p "Hit a key " keypress
4 echo; echo "Keypress was "\"$keypress\""."
5
6 # -s 选项意味着不打印输入.
7 # -n N 选项意味着只接受N个字符的输入.
8 # -p 选项意味着在读取输入之前打印出后边的提示符.
9 # 使用这些选项是有技巧的, 因为你需要用正确的顺序来使用它们.

read 命令也可以从重定向的文件中”读取”变量的值. 如果文件中的内容超过一行, 那么只有第一行被分配到这个变量中. 如果read命令的参数个数超过一个, 那么每个变量都会从文件中取得一个分配的字符串作为变量的值, 这些字符串都是以定义的空白字符来进行分隔的. 小心使用!

例子 5. 通过文件重定向来使用read命令

  1 #!/bin/bash
2
3 read var1
4 echo "var1 = $var1"
5 # var1将会把"data-file"的第一行的全部内容都为它的值.
6
7 read var2 var3
8 echo "var2 = $var2 var3 = $var3"
9 # 注意, 这里的"read"命令将会产生一种不直观的行为.
10 # 1) 重新从文件的开头开始读入变量.
11 # 2) 每个变量都设置成了以空白分割的字符串.
12 # 而不是之前的以整行的内容作为变量的值.
13 # 3) 而最后一个变量将会取得第一行剩余的全部部分(译者注: 不管是否以空白分割).
14 # 4) 如果需要赋值的变量个数比文件中第一行以空白分割的字符串个数还多的话,
15 # 那么这些变量将会被赋空值.
16
17 echo "------------------------------------------------"
18
19 # 如何用循环来解决上边所提到的问题:
20 while read line
21 do
22 echo "$line"
23 done

24 # 感谢, Heiner Steven 指出了这点.
25
26 echo "------------------------------------------------"
27
28 # 使用$IFS(内部域分隔变量)来将每行的输入单独的放到"read"中,
29 # 前提是如果你不想使用默认空白的话.
30
31 echo "List of all users:"
32 OIFS=$IFS; IFS=: # /etc/passwd 使用 ":" 作为域分隔符.
33 while read name passwd uid gid fullname ignore
34 do
35 echo "$name ($fullname)"
36 done # I/O 重定向.
37 IFS=$OIFS # 恢复原始的$IFS.
38 # 这段代码也是Heiner Steven编写的.
39
40
41
42 # 在循环内部设置$IFS变量,
43 #+ 而不用把原始的$IFS
44 #+ 保存到临时变量中.
45 # 感谢, Dim Segebart, 指出了这点.
46 echo "------------------------------------------------"
47 echo "List of all users:"
48
49 while IFS=: read name passwd uid gid fullname ignore # 多条件的 while 循环
50 do
51 echo "$name ($fullname)"
52 done # I/O 重定向.
53
54 echo
55 echo "\$IFS still $IFS"
56
57 exit 0

管道输出到read命令中, 使用管道echo输出来设置变量将会失败.

然而, 使用管道cat输出看起来能够正常运行.

  1 cat file1 file2 |
2 while read line
3 do
4 echo $line
5 done

例子 6. 管道输出到read中的问题

  1 #!/bin/sh
2 # readpipe.sh
3 # 这个例子是由Bjon Eriksson所编写的.
4
5 last="(null)"
6 cat $0 |
7 while read line
8 do
9 echo "{$line}"
10 last=$line
11 done
12 printf "\nAll done, last:$last\n"
13
14 exit 0 # 代码结束.
15 # 下边是脚本的(部分)输出.
16 # 'echo'出了多余的大括号.
17
18 #############################################
19
20 ./readpipe.sh
21
22 {#!/bin/sh}
23 {last="(null)"}
24 {cat $0 |}
25 {while read line}
26 {do}
27 {echo "{$line}"}
28 {last=$line}
29 {done}
30 {printf "nAll done, last:$lastn"}
31
32
33 All done, last:(null)
34
35 变量(last)被设置在子shell中, 并没有被设置在外边.

子 shell 陷阱
例子 31-2. 子shell缺陷

  1 #!/bin/bash
2 # 子shell中的变量缺陷.
3
4 outer_variable=outer
5 echo
6 echo "outer_variable = $outer_variable"
7 echo
8
9 (
10 # 开始子shell
11
12 echo "outer_variable inside subshell = $outer_variable"
13 inner_variable=inner # Set
14 echo "inner_variable inside subshell = $inner_variable"
15 outer_variable=inner # 会修改全局变量么?
16 echo "outer_variable inside subshell = $outer_variable"
17
18 # 如果将变量'导出'会产生不同的结果么?
19 # export inner_variable
20 # export outer_variable
21 # 试试看.
22
23 # 结束子shell
24 )
25
26 echo
27 echo "inner_variable outside subshell = $inner_variable" # 未设置.
28 echo "outer_variable outside subshell = $outer_variable" # 未修改.
29 echo
30
31 exit 0
32
33 # 如果你打开第19和第20行的注释会怎样?
34 # 会产生不同的结果么? (译者注: 小提示, 第18行的'导出'都加上引号了.)

将 echo 的输出通过管道传递给read命令可能会产生不可预料的结果. 在这种情况下, read命令的行为就好像它在子shell中运行一样.

例子 31-3. 将echo的输出通过管道传递给read命令

  1 #!/bin/bash
2 # badread.sh:
3 # 尝试使用'echo'和'read'命令
4 #+ 非交互的给变量赋值.
5
6 a=aaa
7 b=bbb
8 c=ccc
9
10 echo "one two three" | read a b c
11 # 尝试重新给变量a, b, 和c赋值.
12
13 echo
14 echo "a = $a" # a = aaa
15 echo "b = $b" # b = bbb
16 echo "c = $c" # c = ccc
17 # 重新赋值失败.
18
19 # ------------------------------
20
21 # 试试下边这种方法.
22
23 var=`echo "one two three"`
24 set -- $var
25 a=$1; b=$2; c=$3
26
27 echo "-------"
28 echo "a = $a" # a = one
29 echo "b = $b" # b = two
30 echo "c = $c" # c = three
31 # 重新赋值成功.
32
33 # ------------------------------
34
35 # 也请注意, echo到'read'的值只会在子shell中起作用.
36 # 所以, 变量的值*只*会在子shell中被修改.
37
38 a=aaa # 重新开始.
39 b=bbb
40 c=ccc
41
42 echo; echo
43 echo "one two three" | ( read a b c;
44 echo "Inside subshell: "; echo "a = $a"; echo "b = $b"; echo "c = $c" )
45 # a = one
46 # b = two
47 # c = three
48 echo "-----------------"
49 echo "Outside subshell: "
50 echo "a = $a" # a = aaa
51 echo "b = $b" # b = bbb
52 echo "c = $c" # c = ccc
53 echo
54
55 exit 0

cd

cd, 修改目录命令, 在脚本中用的最多的时候就是当命令需要在指定目录下运行时, 需要用它来修改当前工作目录.

      (cd /source/directory && tar cf - . ) | (cd /dest/directory && tar xpvf -)

-P (physical)选项对于cd命令的意义是忽略符号链接.

cd – 将会把工作目录修改至$OLDPWD, 也就是之前的工作目录.

注:当我们使用两个”/”来作为cd命令的参数时, 结果却出乎我们的意料. .

    bash$ cd //
bash$ pwd
//

输出应该是, 并且当然应该是 /. 无论在命令下还是在脚本中, 这都是个问题.

pwd

打印出当前的工作目录. 这将给出用户(或脚本)的当前工作目录。使用这个命令的结果和从内建变量$PWD中所读取的值是相同的.

pushd, popd, dirs

这几个命令可以使得工作目录书签化, 就是可以按顺序向前或向后移动工作目录. 压栈的动作可以保存工作目录列表. 选项可以允许对目录栈做不同的操作.

pushd dir-name 把路径dir-name压入目录栈, 同时修改当前目录到dir-name.

popd 将目录栈最上边的目录弹出, 同时将当前目录修改为刚弹出来的那个目录.

dirs 列出所有目录栈的内容 (与$DIRSTACK变量相比较). 一个成功的pushd或者popd将会自动调用dirs命令.

对于那些并没有对当前目录做硬编码, 并且需要对当前工作目录做灵活修改的脚本来说, 使用这些命令是再好不过了. 注意内建$DIRSTACK数组变量, 这个变量可以在脚本中进行访问, 并且它们保存了目录栈的内容.

例子 7. 修改当前工作目录

      1 #!/bin/bash
2
3 dir1=/usr/local
4 dir2=/var/spool
5
6 pushd $dir1
7 # 将自动运行一个 'dirs' (把目录栈的内容列到stdout上).
8 echo "Now in directory `pwd`." # 使用后置引用的 'pwd'.
9
10 # 现在对'dir1'做一些操作.
11 pushd $dir2
12 echo "Now in directory `pwd`."
13
14 # 现在对'dir2'做一些操作.
15 echo "The top entry in the DIRSTACK array is $DIRSTACK."
16 popd
17 echo "Now back in directory `pwd`."
18
19 # 现在, 对'dir1'做更多的操作.
20 popd
21 echo "Now back in original working directory `pwd`."
22
23 exit 0
24
25 # 如果你不使用 'popd' 将会发生什么 -- 然后退出这个脚本?
26 # 你最后将落在哪个目录中? 为什么?

let

let命令将执行变量的算术操作. 在许多情况下, 它被看作是复杂的expr命令的一个简化版本.

例子 8. 使用”let”命令来做算术运算.

      1 #!/bin/bash
2
3 echo
4
5 let a=11 # 与 'a=11' 相同
6 let a=a+5 # 等价于 let "a = a + 5"
7 # (双引号和空格使这句话更具可读性.)
8 echo "11 + 5 = $a" # 16
9
10 let "a <<= 3" # 等价于 let "a = a << 3"
11 echo "\"\$a\" (=16) left-shifted 3 places = $a"
12 # 128
13
14 let "a /= 4" # 等价于 let "a = a / 4"
15 echo "128 / 4 = $a" # 32
16
17 let "a -= 5" # 等价于 let "a = a - 5"
18 echo "32 - 5 = $a" # 27
19
20 let "a *= 10" # 等价于 let "a = a * 10"
21 echo "27 * 10 = $a" # 270
22
23 let "a %= 8" # 等价于 let "a = a % 8"
24 echo "270 modulo 8 = $a (270 / 8 = 33, remainder $a)"
25 # 6
26
27 echo
28
29 exit 0

eval

    eval arg1 [arg2] ... [argN]

将表达式中的参数, 或者表达式列表, 组合起来, 然后评价它们(译者注: 通常用来执行). 任何被包含在表达示中的变量都将被扩展. 结果将会被转化到命令中. 如果你想从命令行中或者是从脚本中产生代码, 那么这个命令就非常有用了.

    bash$ process=xterm
bash$ show_process="eval ps ax | grep $process"
bash$ $show_process (注:这个变量现在成为了一个命令.)
1867 tty1 S 0:02 xterm
2779 tty1 S 0:00 xterm
2886 pts/1 S 0:00 grep xterm

例子 9. 展示eval命令的效果

  1 #!/bin/bash
2
3 y=`eval ls -l` # 与 y=`ls -l` 很相似
4 echo $y #+ 但是换行符将会被删除, 因为"echo"的变量未被""引用.
5 echo
6 echo "$y" # 用""将变量引用起来, 换行符就不会被空格替换了.
7
8 echo; echo
9
10 y=`eval df` # 与 y=`df` 很相似
11 echo $y #+ 换行符又被空格替换了.
12
13 # 当没有LF(换行符)出现时, 如果使用"awk"这样的工具来分析输出的结果,
14 #+ 应该能更容易一些.
15
16 echo
17 echo "==========================================================="
18 echo
19
20 # 现在,来看一下怎么用"eval"命令来"扩展"一个变量 . . .
21
22 for i in 1 2 3 4 5; do
23 eval value=$i
24 # value=$i 具有相同的效果, 在这里并不是非要使用"eval"不可.
25 # 一个缺乏特殊含义的变量将被评价为自身 -- 也就是说,
26 #+ 这个变量除了能够被扩展成自身所表示的字符外, 不能被扩展成任何其他的含义.
27 echo $value
28 done
29
30 echo
31 echo "---"
32 echo
33
34 for i in ls df; do
35 value=eval $i # 注:这与“eval value=$i”也是不同的。“展开再赋值”与“赋值再展开”不同。
#+ value=eval $i 与 value="eval $i" 也是不同的,后者在引用($value)时变成了一个命令。
36 # value=$i 在这里就与上边这句有了本质上的区别.
37 # "eval" 将会评价命令 "ls" 和 "df" . . .
38 # 术语 "ls" 和 "df" 就具有特殊含义,
39 #+ 因为它们被解释成命令,
40 #+ 而不是字符串本身.
41 echo $value
42 done
43
44
45 exit 0

eval命令是有风险的, 如果你有更合适的方法来实现功能的话, 尽量避免使用它.

set

set命令用来修改内部脚本变量的值. 它的一个作用就是触发选项标志位来帮助决定脚本的行为. 另一个作用是以一个命令的结果(set `command`)来重新设置脚本的位置参数. 脚本将会从命令的输出中重新分析出位置参数.

例子 10. 使用set命令来改变脚本的位置参数

  1 #!/bin/bash
2
3 # script "set-test"
4
5 # 使用3个命令行参数来调用这个脚本,
6 # 比如, "./set-test one two three".
7
8 echo
9 echo "Positional parameters before set \`uname -a\` :"
10 echo "Command-line argument #1 = $1"
11 echo "Command-line argument #2 = $2"
12 echo "Command-line argument #3 = $3"
13
14
15 set `uname -a` # 把`uname -a`的命令输出设置
16 # 为新的位置参数.
17
18 echo $_ # unknown(译者注: 这要看你的uname -a输出了,这句打印出的就是输出的最后一个单词.) "$_" 这个变量保存之前执行的命令的最后一个参数的值.
19 # 在脚本中设置标志.
20
21 echo "Positional parameters after set \`uname -a\` :"
22 # $1, $2, $3, 等等. 这些位置参数将被重新初始化为`uname -a`的结果
23 echo "Field #1 of 'uname -a' = $1"
24 echo "Field #2 of 'uname -a' = $2"
25 echo "Field #3 of 'uname -a' = $3"
26 echo ---
27 echo $_ # ---
28 echo
29
30 exit 0

不使用任何选项或参数来调用set命令的话, 将会列出所有的环境变量和其他所有的已经初始化过的变量.

$ set
BASH=/bin/bash
BASH_ARGC=()
BASH_ARGV=()
BASH_COMPLETION=/etc/bash_completion
BASH_COMPLETION_DIR=/etc/bash_completion.d
BASH_LINENO=()
BASH_SOURCE=()
BASH_VERSINFO=([0]="3" [1]="2" [2]="39" [3]="1" [4]="release" [5]="i486-pc-linux-gnu")
BASH_VERSION='3.2.39(1)-release'
BROWSER='/usr/bin/most -s'
...
HOME=/home/fhc2007
HOSTNAME=fhc2007-desktop
HOSTTYPE=i486
IFS=$' \t\n'
...

如果使用参数 -- 来调用 set 命令的话, 将会明确的分配位置参数. 如果 -- 选项后边没有跟变量名的话, 那么结果就使得所有位置参数都被unsets了.

例子 11. 重新分配位置参数

  1 #!/bin/bash
2
3 variable="one two three four five"
4
5 set -- $variable
6 # 将位置参数的内容设为变量"$variable"的内容.
7
8 first_param=$1
9 second_param=$2
10 shift; shift # 将最前面的两个位置参数移除.
11 remaining_params="$*"
12
13 echo
14 echo "first parameter = $first_param" # one
15 echo "second parameter = $second_param" # two
16 echo "remaining parameters = $remaining_params" # three four five
17
18 echo; echo
19
20 # 再来一次.
21 set -- $variable
22 first_param=$1
23 second_param=$2
24 echo "first parameter = $first_param" # one
25 echo "second parameter = $second_param" # two
26
27 # ======================================================
28
29 set --
30 # 如果没指定变量,那么将会unset所有的位置参数.
31
32 first_param=$1
33 second_param=$2
34 echo "first parameter = $first_param" # (null value)
35 echo "second parameter = $second_param" # (null value)
36
37 exit 0

unset

unset命令用来删除一个shell变量, 这个命令的效果就是把这个变量设为null. 注意: 这个命令对位置参数无效.

    bash$ unset PATH
bash$ echo $PATH

bash$

例子 12. "Unsett"一个变量

      1 #!/bin/bash
2 # unset.sh: Unset 一个变量.
3
4 variable=hello # 初始化.
5 echo "variable = $variable"
6
7 unset variable # Unset.
8 # 与 variable= 效果相同.
9 echo "(unset) variable = $variable" # $variable 设为 null.
10
11 exit 0

export

export命令将会使得被export的变量在所运行脚本(或shell)的所有子进程中都可用. 不幸的是, 没有办法将变量export到父进程中, 这里所指的父进程就是调用这个脚本的脚本或shell. 关于export命令的一个重要的用法就是使用在启动文件中, 启动文件用来初始化和设置环境变量, 这样, 用户进程才能够访问环境变量.

source. (点 命令)

当在命令行中调用的时候, 这个命令将会执行一个脚本. 当在脚本中调用的时候, source file-name 将会加载file-name文件. sourc一个文件(或点命令)将会在脚本中引入代码, 并将这些代码附加到脚本中(与C语言中的#include指令效果相同). 最终的结果就像是在使用"source"的行上插入了相应文件的内容. 在多个脚本需要引用相同的数据, 或者需要使用函数库的情况下, 这个命令非常有用.

例子 13. "includ"一个数据文件

  1 #!/bin/bash
2
3 . data-file # 加载一个数据文件.
4 # 与"source data-file"效果相同, 但是更具可移植性.
5
6 # 文件"data-file"必须存在于当前工作目录,
7 #+ 因为这个文件是使用'basename'来引用的.
8
9 # 现在, 引用这个文件中的一些数据.
10
11 echo "variable1 (from data-file) = $variable1"
12 echo "variable3 (from data-file) = $variable3"
13
14 let "sum = $variable2 + $variable4"
15 echo "Sum of variable2 + variable4 (from data-file) = $sum"
16 echo "message1 (from data-file) is \"$message1\""
17 # 注意: 将双引号转义
18
19 print_message This is the message-print function in the data-file.
20
21
22 exit 0

上边例子所使用的数据文件data-file, 必须和上边的脚本放在同一目录下.

  1 # 这是需要被脚本加载的数据文件.
2 # 这种文件可以包含变量, 函数, 等等.
3 # 在脚本中可以通过'source'或者'.'命令来加载.
4
5 # 让我们初始化一些变量.
6
7 variable1=22
8 variable2=474
9 variable3=5
10 variable4=97
11
12 message1="Hello, how are you?"
13 message2="Enough for now. Goodbye."
14
15 print_message ()
16 {
17 # echo出所有传递进来的消息.
18
19 if [ -z "$1" ]
20 then
21 return 1
22 # 如果没有参数的话, 会出错.
23 fi
24
25 echo
26
27 until [ -z "$1" ]
28 do
29 # 循环处理传递到函数中的参数.
30 echo -n "$1"
31 # 每次 echo 一个参数, -n禁止换行.
32 echo -n " "
33 # 在参数之间插入空格.
34 shift
35 # 切换到下一个.
36 done
37
38 echo
39
40 return 0
41 }

如果source进来的文件本身就一个可执行脚本的话, 那么它将运行起来, 然后将控制权交还给调用它的脚本. 一个source进来的可执行脚本可以使用return命令来达到这个目的.(可选的)也可以向source文件中传递参数, 这些参数将被看作位置参数.

   source $filename $arg1 $arg2

你甚至可以在脚本文件中source它自身, 虽然这么做看不出有什么实际的应用价值.

exit

无条件的停止一个脚本的运行. exit命令可以随意的取得一个整数参数, 然后把这个参数作为这个脚本的退出状态码. 在退出一个简单脚本的时候, 使用exit 0的话, 是种好习惯, 因为这表明成功运行.

如果不带参数调用exit命令退出的话, 那么退出状态码将会将会是脚本中最后一个命令的退出状态码. 等价于exit $?.
exec

这 个shell内建命令将使用一个特定的命令来取代当前进程. 一般的当shell遇到一个命令, 它会forks off一个子进程来真正的运行命令. 使用exec内建命令, shell就不会fork了, 并且命令的执行将会替换掉当前shell. 因此, 在脚本中使用时, 一旦exec所执行的命令执行完毕, 那么它就会强制退出脚本. (除非exec命令被用来重新分配文件描述符)

例子 14. exec命令的效果

      1 #!/bin/bash
2
3 exec echo "Exiting \"$0\"." # 脚本应该在这里退出.
4
5 # ----------------------------------
6 # The following lines never execute.
7
8 echo "This echo will never echo."
9
10 exit 99 # 脚本是不会在这里退出的.
11 # 脚本退出后会使用'echo $?'
12 #+ 来检查一下退出码.
13 # 一定 *不是* 99.

例子 15. 一个exec自身的脚本

  1 #!/bin/bash
2 # self-exec.sh
3
4 echo
5
6 echo "This line appears ONCE in the script, yet it keeps echoing."
7 echo "The PID of this instance of the script is still $$."
8 # 上边这行展示了并没有fork出子shell.
9
10 echo "==================== Hit Ctl-C to exit ===================="
11
12 sleep 1
13
14 exec $0 # 产生了本脚本的另一个实例,
15 #+ 但是这个新产生的实例却代替了原来的实例.
16
17 echo "This line will never echo!" # 为什么不是这样?
18
19 exit 0

exec命令还能够用来重新分配文件描述符. 比如, exec stdin.

find命令的-exec选项与shell内建的exec命令是不同的.

true

这是一个返回(零)成功退出状态码的命令, 但是除此之外不做任何事.

      1 # 死循环
2 while true # 这里的true可以用":"来替换
3 do
4 operation-1
5 operation-2
6 ...
7 operation-n
8 # 需要一种手段从循环中跳出来, 或者是让这个脚本挂起.
9 done

false

这是一个返回失败退出状态码的命令, 但是除此之外不做任何事.

      1 # 测试 "false"
2 if false
3 then
4 echo "false evaluates \"true\""
5 else
6 echo "false evaluates \"false\""
7 fi
8 # 失败会显示 "false"
9
10
11 # while "false" 循环 (空循环)
12 while false
13 do
14 # 这里面的代码不会被执行.
15 operation-1
16 operation-2
17 ...
18 operation-n
19 # 什么事都没发生!
20 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/[

help

获得 shell 内建命令简要的使用总结. help命令是 bash 内建命令.

$ help help
help: help [-s] [pattern ...]
Display helpful information about builtin commands. If PATTERN is
specified, gives detailed help on all commands matching PATTERN,
otherwise a list of the builtins is printed. The -s option
restricts the output for each builtin command matching PATTERN to
a short usage synopsis.
$ type -a help
help 是一个 shell 内部命令

下边的作业控制命令需要一个"作业标识符"作为参数. 请参考本章结尾部分的表格.

jobs

在后台列出所有正在运行的作业, 给出作业号. 并不象 ps 命令那么有用.

作业和进程的概念太容易混淆了. 特定的内建命令, 比如kill, disown, 和wait命令即可以接受作业号为参数, 也可以接受进程号为参数. 但是fg, bg和jobs命令就只能接受作业号为参数.

    bash$ sleep 100 &
[1] 1384

bash $ jobs
[1]+ Running sleep 100 &

"1"是作业号(作业是被当前shell所维护的), 而"1384"是进程号(进程是被系统维护的). 为了kill掉作业/进程, 或者使用kill %1或者使用kill 1384. 这两个命令都行.

disown

从shell的激活作业表中删除作业.
fg, bg

fg 命令可以把一个在后台运行的作业放到前台来运行. 而 bg 命令将会重新启动一个挂起的作业, 并且在后台运行它. 如果使用 fg 或者 bg 命令的时候没有指定作业号, 那么默认将对当前正在运行的作业进行操作.
wait

停止脚本的运行, 直到后台运行的所有作业都结束为止, 或者如果传递了作业号或进程号为参数的话, 那么就直到指定作业结束为止. 返回等待命令的退出状态码.

你可以使用wait命令来防止在后台作业没完成(这会产生一个孤儿进程)之前退出脚本.

例子 16. 在继续处理之前, 等待一个进程的结束

  1 #!/bin/bash
2
3 ROOT_UID=0 # 只有$UID为0的用户才拥有root权限.
4 E_NOTROOT=65
5 E_NOPARAMS=66
6
7 if [ "$UID" -ne "$ROOT_UID" ]
8 then
9 echo "Must be root to run this script."
10 # "Run along kid, it's past your bedtime."
11 exit $E_NOTROOT
12 fi
13
14 if [ -z "$1" ]
15 then
16 echo "Usage: `basename $0` find-string"
17 exit $E_NOPARAMS
18 fi
19
20
21 echo "Updating 'locate' database..."
22 echo "This may take a while."
23 updatedb /usr & # 必须使用root身份来运行.
24
25 wait
26 # 将不会继续向下运行, 除非'updatedb'命令执行完成.
27 # 你希望在查找文件名之前更新database.
28
29 locate $1
30
31 # 如果没有'wait'命令的话, 而且在比较糟的情况下,
32 #+ 脚本可能在'updatedb'命令还在运行的时候退出,
33 #+ 这将会导致'updatedb'成为一个孤儿进程.
34
35 exit 0

可选的, wait也可以接受一个作业标识符作为参数, 比如, wait%1或者wait $PPID. 请参考作业标识符表.

在一个脚本中, 使用后台运行命令(&)可能会使这个脚本挂起, 直到敲ENTER, 挂起的脚本才会被恢复. 看起来只有在这个命令的结果需要输出到stdout的时候, 这种现象才会出现. 这是个很烦人的现象.例如:

  1 #!/bin/bash
2 # test.sh
3
4 ls -l &
5 echo "Done."

看起来只要在后台运行命令的后边加上一个wait命令就会解决这个问题.

  1 #!/bin/bash
2 # test.sh
3
4 ls -l &
5 echo "Done."
6 wait

如果将后台运行命令的输出重定向到文件中或 /dev/null 中, 也能解决这个问题.

suspend

这个命令的效果与Control-Z很相像, 但是它挂起的是这个shell(这个shell的父进程应该在合适的时候重新恢复它).
logout

退出一个已经登陆上的shell, 也可以指定一个退出状态码.
times

给出执行命令所占用的时间, 使用如下的形式进行输出:

0m0.020s 0m0.020s

这只能给出一个很有限的值, 因为它很少在shell脚本中出现.
kill

通过发送一个适当的结束信号, 来强制结束一个进程
例子 17. 一个结束自身的脚本程序

      1 #!/bin/bash
2 # self-destruct.sh
3
4 kill $$ # 脚本将在此处结束自己的进程.
5 # 回忆一下,"$$"就是脚本的PID.
6
7 echo "This line will not echo."
8 # 而且shell将会发送一个"Terminated"消息到stdout.
9
10 exit 0
11
12 # 在脚本结束自身进程之后,
13 #+ 它返回的退出码是什么?
14 #
15 # sh self-destruct.sh
16 # echo $?
17 # 143
18 #
19 # 143 = 128 + 15
20 # 结束信号

kill -l 将会列出所有信号. kill -9 是"必杀" 命令, 这个命令将会结束顽固的不想被kill掉的进程. 有时候 kill -15 也能干这个活. 一个"僵尸进程", 僵尸进程就是子进程已经结束了, 但是父进程还没kill掉这个子进程, 不能被登陆的用户kill掉 -- 因为你不能杀掉一些已经死了的东西 -- 但是init进程迟早会把它清除干净.
killall

killall 命令将会通过名字来杀掉一个正在运行的进程, 而不是通过进程ID. 如果某个特定的命令有多个实例正在运行, 那么执行一次killall命令就会把这些实例全部杀掉.

注:这里所指的killall命令是在/usr/bin中, 而不是/etc/rc.d/init.d中的killall脚本.
command

对于命令"COMMAND", command COMMAND会直接禁用别名和函数的查找.

译者注, 注意一下Bash执行命令的优先级:

      1	别名
2 关键字
3 函数
4 内建命令
5 脚本或可执行程序($PATH)

这是 shell 用来影响脚本命令处理效果的三个命令之一. 另外两个分别是 builtin 和 enable. (译者注: 当你想运行的命令或函数与内建命令同名时, 由于内建命令比外部命令的优先级高, 而函数比内建命令的优先级高, 所以Bash将总会执行优先级比较高的命令. 这样当你想执行优先级低的命令的时候, 就没有选择的余地了. 这三个命令就是用来为你提供这样的机会. )
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

这是从ksh中的autoloader命令移植过来的. 一个带有"autoload"声明的函数, 在它第一次被调用的时候才会被加载. 这样做是为了节省系统资源.

注意, autoload命令并不是Bash核心安装时候的一部分. 这个命令需要使用命令 enable -f 来加载(参考上边的enable命令).

记法	含义
%N 作业号[N]
%S 以字符串S开头的被(命令行)调用的作业
%?S 包含字符串S的被(命令行)调用的作业
%% "当前"作业(前台最后结束的作业, 或后台最后启动的作业)
%+ "当前"作业(前台最后结束的作业, 或后台最后启动的作业)
%- 最后的作业
$! 最后的后台进程

阅读(831) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~