TLCL之第四章(4)


第四章

11. 数组

1. 创建一个数组

数组变量就像其它bash变量一样命名,当被访问的时候,它们会被自动地创建。

[me@linuxbox ~]$ a[1]=foo
[me@linuxbox ~]$ echo ${a[1]}
foo

也可以用declare命令创建一个数组:

[me@linuxbox ~]$ declare -a a

使用-a选项,declare命令创建了数组a。

2. 数组赋值

有两种方式可以给数组赋值,单个值赋值使用以下语法:

name[subscript]=value

这里的name是数组的名字,subscript是一个大于或等于零的整数(或算术表达式)。注意数组的第一个元素的下标是0,而不是1,数组元素的值可以是一个字符串或整数。
多个值赋值使用下面的语法:


name=(value1 value2 ...)
[me@linuxbox ~]$ days=(Sun Mon Tue Wed Thu Fri Sat)

还可以通过指定下标,把赋值给数组中的特定元素:

[me@linuxbox ~]$ days=([0]=Sun [1]=Mon [2]=Tue [3]=Wed [4]=Thu [5]=Fri [6]=Sat)

3. 访问数组元素

输出整个数组的内容

下标*和@可以被用来访问数组中的每一个元素,与位置参数一样,@表示法在两者之间更有用处。

[me@linuxbox ~]$ animals=("a dog" "a cat" "a fish")
[me@linuxbox ~]$ for i in ${animals[*]}; do echo $i; done
a
dog
a
cat
a
fish
[me@linuxbox ~]$ for i in ${animals[@]}; do echo $i; done
a
dog
a
cat
a
fish
[me@linuxbox ~]$ for i in "${animals[*]}"; do echo $i; done
a dog a cat a fish
[me@linuxbox ~]$ for i in "${animals[@]}"; do echo $i; done
a dog
a cat
a fish

确定数组元素个数

使用参数展开,我们能够确定数组元素的个数,与计算字符串长度的方式几乎相同。

[me@linuxbox ~]$ a[100]=foo
[me@linuxbox ~]$ echo ${#a[@]} # number of array elements
1
[me@linuxbox ~]$ echo ${#a[100]} # length of element 100
3

我们创建了数组 a,并把字符串 “foo” 赋值给数组元素100。下一步,我们使用参数展开来检查数组的长度,使用 @ 表示法。 最后,我们查看了包含字符串 “foo” 的数组元素 100 的长度。有趣的是,尽管我们把字符串赋值给数组元素100, bash 仅仅报告数组中有一个元素。这不同于一些其它语言的行为,这种行为是数组中未使用的元素(元素0-99)会初始化为空值, 并把它们计入数组长度。

找到数组使用的下标

  • ${!array[*]}
  • ${!array[@]}

这里的array是一个数组变量的名字,和其它使用符号*和@的展开一样,用引号引起来的@格式是最有用的,因为它能展开成分离的词。

[me@linuxbox ~]$ foo=([2]=a [4]=b [6]=c)
[me@linuxbox ~]$ for i in "${foo[@]}"; do echo $i; done
a
b
c
[me@linuxbox ~]$ for i in "${!foo[@]}"; do echo $i; done
2
4
6

在数组末尾添加元素

如果我们需要在数组末尾附加数据,那么知道数组中元素的个数是没用的,因为通过 * 和 @ 表示法返回的数值不能 告诉我们使用的最大数组索引。幸运地是,shell 为我们提供了一种解决方案。通过使用 += 赋值运算符, 我们能够自动地把值附加到数组末尾。这里,我们把三个值赋给数组 foo,然后附加另外三个。

[me@linuxbox~]$ foo=(a b c)
[me@linuxbox~]$ echo ${foo[@]}
a b c
[me@linuxbox~]$ foo+=(d e f)
[me@linuxbox~]$ echo ${foo[@]}
a b c d e f

数组排序

shell没有这样做的直接方法,但是通过一点儿代码,可以实现:

#!/bin/bash
# array-sort : Sort an array
a=(f e d c b a)
echo "Original array: ${a[@]}"
a_sorted=($(for i in "${a[@]}"; do echo $i; done | sort))
echo "Sorted array: ${a_sorted[@]}"

删除数组

删除一个数组,使用unset命令:

[me@linuxbox ~]$ foo=(a b c d e f)
[me@linuxbox ~]$ echo ${foo[@]}
a b c d e f
[me@linuxbox ~]$ unset foo
[me@linuxbox ~]$ echo ${foo[@]}
[me@linuxbox ~]$

也可以使用unset命令删除单个的数组元素:

[me@linuxbox~]$ foo=(a b c d e f)
[me@linuxbox~]$ echo ${foo[@]}
a b c d e f
[me@linuxbox~]$ unset 'foo[2]'
[me@linuxbox~]$ echo ${foo[@]}
a b d e f

给一个数组赋空值不会清空数组内容:

[me@linuxbox ~]$ foo=(a b c d e f)
[me@linuxbox ~]$ foo=
[me@linuxbox ~]$ echo ${foo[@]}
b c d e f

任何没有下标的对数组变量的引用都指向数组元素0:

[me@linuxbox~]$ foo=(a b c d e f)
[me@linuxbox~]$ echo ${foo[@]}
a b c d e f
[me@linuxbox~]$ foo=A
[me@linuxbox~]$ echo ${foo[@]}
A b c d e f

4. 关联数组

关联数组使用字符串而不是整数作为数组索引。这种功能给出了一种有趣的新方法来管理数据。例如,我们可以创建一个叫做"colors"的数组,并用颜色名字作为索引。

declare -A colors
colors["red"]="#ff0000"
colors["green"]="#00ff00"
colors["blue"]="#0000ff"

访问关联数组元素的方式几乎与整数索引数组相同:

echo ${colors["blue"]}

12. 奇珍异宝

这里将介绍一些零星的知识点。

1. 组命令和子shell

bash允许把命令组合在一起。可以通过两种方式完成。
要么用一个group命令,要么用一个子shell。
groupmingl:

{ command1; command2; [command3; ...] }

子shell:

(command1; command2; [command3;...])

这两种形式的不同之处在于,组命令用花括号把它的命令包裹起来,而子 shell 用括号。值得注意的是,鉴于 bash 实现组命令的方式, 花括号与命令之间必须有一个空格,并且最后一个命令必须用一个分号或者一个换行符终止。

2. 进程替换

虽然组命令和子 shell 看起来相似,并且它们都能用来在重定向中合并流,但是两者之间有一个很重要的不同之处。 然而,一个组命令在当前 shell 中执行它的所有命令,而一个子 shell(顾名思义)在当前 shell 的一个 子副本中执行它的命令。这意味着运行环境被复制给了一个新的 shell 实例。当这个子 shell 退出时,环境副本会消失, 所以在子 shell 环境(包括变量赋值)中的任何更改也会消失。因此,在大多数情况下,除非脚本要求一个子 shell, 组命令比子 shell 更受欢迎。组命令运行很快并且占用的内存也少。
在前面看到过一个子 shell 运行环境问题的例子,当我们发现管道线中的一个 read 命令 不按我们所期望的那样工作的时候。为了重现问题,我们构建一个像这样的管道线:

echo "foo" | read
echo $REPLY

该 REPLY 变量的内容总是为空,是因为这个 read 命令在一个子 shell 中执行,所以当该子 shell 终止的时候, 它的 REPLY 副本会被毁掉。因为管道线中的命令总是在子 shell 中执行,任何给变量赋值的命令都会遭遇这样的问题。 幸运地是,shell 提供了一种奇异的展开方式,叫做进程替换,它可以用来解决这种麻烦。进程替换有两种表达方式:
一种适用于产生标准输出的进程:

<(list)

另一种使适用于接受标准输入的进程:

>(list)

这里的list是一串命令列表:
为了解决我们的read命令问题,我们可以使用进程替换:

read < <(echo "foo")
echo $REPLY

进程替换允许我们把一个子 shell 的输出结果当作一个用于重定向的普通文件。事实上,因为它是一种展开形式,我们可以检验它的真实值:

[me@linuxbox ~]$ echo <(echo "foo")
/dev/fd/63

3. 陷阱

在一个好的设计流程,我们应该让脚本删除创建的 临时文件,当脚本完成它的任务之后。若脚本接收到一个信号,表明该程序即将提前终止的信号, 此时让脚本删除创建的临时文件,也会是很精巧的设计。
为满足这样需求,bash 提供了一种机制,众所周知的 trap。陷阱正好由内部命令 trap 实现。 trap 使用如下语法:

trap argument signal [signal...]

这里的 argument 是一个字符串,它被读取并被当作一个命令,signal 是一个信号的说明,它会触发执行所要解释的命令。

#!/bin/bash
# trap-demo : simple signal handling demo
trap "echo 'I am ignoring you.'" SIGINT SIGTERM
for i in {1..5}; do
    echo "Iteration $i of 5"
    sleep 5
done

这个脚本定义一个陷阱,当脚本运行的时候,这个陷阱每当接受到一个 SIGINT 或 SIGTERM 信号时,就会执行一个 echo 命令。 当用户试图通过按下 Ctrl-c 组合键终止脚本运行的时候,该程序的执行结果看起来像这样:

[me@linuxbox ~]$ trap-demo
Iteration 1 of 5
Iteration 2 of 5
I am ignoring you.
Iteration 3 of 5
I am ignoring you.
Iteration 4 of 5
Iteration 5 of 5

4. 异步执行

有时候需要同时执行多个任务。我们已经知道现在所有的操作系统若不是多用户的但至少是多任务的。 脚本也可以构建成多任务处理的模式。
通常这涉及到启动一个脚本,依次,启动一个或多个子脚本来执行额外的任务,而父脚本继续运行。然而,当一系列脚本 以这种方式运行时,要保持父子脚本之间协调工作,会有一些问题。
bash有一个内置命令,能帮助管理异步执行的任务。wait命令导致一个父脚本暂停运行,指导一个特定的进程运行结束。

wait

首先演示一下wait命令的用法:
父进程:

#!/bin/bash
# async-parent : Asynchronous execution demo (parent)
echo "Parent: starting..."
echo "Parent: launching child script..."
async-child &
pid=$!
echo "Parent: child (PID= $pid) launched."
echo "Parent: continuing..."
sleep 2
echo "Parent: pausing to wait for child to finish..."
wait $pid
echo "Parent: child is finished. Continuing..."
echo "Parent: parent is done. Exiting."

子脚本:

#!/bin/bash
# async-child : Asynchronous execution demo (child)
echo "Child: child is running..."
sleep 5
echo "Child: child is done. Exiting."

在这个例子中,我们看到该子脚本是非常简单的。真正的操作通过父脚本完成。在父脚本中,子脚本被启动, 并被放置到后台运行。子脚本的进程 ID 记录在 pid 变量中,这个变量的值是 $! shell 参数的值,它总是 包含放到后台执行的最后一个任务的进程 ID 号。
父脚本继续,然后执行一个以子进程 PID 为参数的 wait 命令。这就导致父脚本暂停运行,直到子脚本退出,父脚本随之结束。

5. 命名管道

在大多数类似 Unix 的操作系统中,有可能创建一种特殊类型的文件,叫做命名管道。命名管道用来在 两个进程之间建立连接,也可以像其它类型的文件一样使用。虽然它们不是那么流行,但是它们值得我们去了解。
有一种常见的编程架构,叫做客户端-服务器,它可以利用像命名管道这样的通信方式, 也可以使用其它类型的进程间通信方式,比如网络连接。
命令管道的行为类似于文件,但实际上形成了先入先出(FIFO)的缓冲。和普通(未命令的)管道一样, 数据从一端进入,然后从另一端出现。通过命令管道,有可能像这样设置一些东西:
process1 > named_pipe

process2 < named_pipe
表现出来像这样:
process1 | process2

设置一个命名管道

首先,我们必须创建一个命名管道,使用mkfifo命令能够创建命名管道:

[me@linuxbox ~]$ mkfifo pipe1
[me@linuxbox ~]$ ls -l pipe1
prw-r--r-- 1 me
me
0 2009-07-17 06:41 pipe1

这里使用mkfifo创建了一个名为pipe1的命名管道。使用ls命令,我们查看这个文件,看到位于属性字段的第一个字母是"p",表明它是一个命名管道。

使用命名管道

为了演示命名管道是如何工作的,我们将需要两个终端窗口(或用两个虚拟控制台代替)。 在第一个终端中,我们输入一个简单命令,并把命令的输出重定向到命名管道:
[me@linuxbox ~]$ ls -l > pipe1
我们按下 Enter 按键之后,命令将会挂起。这是因为在管道的另一端没有任何对象来接收数据。这种现象被称为管道阻塞。一旦我们绑定一个进程到管道的另一端,该进程开始从管道中读取输入的时候,管道阻塞现象就不存在了。 使用第二个终端窗口,我们输入这个命令:
[me@linuxbox ~]$ cat < pipe1
然后产自第一个终端窗口的目录列表出现在第二个终端中,并作为来自 cat 命令的输出。在第一个终端 窗口中的 ls 命令一旦它不再阻塞,会成功地结束。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值