00 0 12 11
01 1 13 7
02 0 14 1
03 0 15 7
04 1 16 6
04 1 17 5
06 6 18 4
07 3 19 4
08 1 20 1
09 14 21 0
10 2 22 0
11 5 23 0
Total files = 80

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31



当执行该 hours 程序时,指定当前目录作为目标目录。它打印出一张表显示一天(0-23小时)每小时内, 有多少文件做了最后修改。

```bash
#!/bin/bash
usage () {
echo "usage: $(basename $0) directory" >&2
}
# Check that argument is a directory
if [[ ! -d $1 ]]; then
usage
exit 1
fi
# Initialize array
for i in {0..23}; do hours[i]=0; done
# Collect data
for i in $(stat -c %y "$1"/* | cut -c 12-13); do
j=${i/#0}
((++hours[j]))
((++count))
done
# Display data
echo -e "Hour\tFiles\tHour\tFiles"
echo -e "----\t-----\t----\t-----"
for i in {0..11}; do
j=$((i + 12))
printf "%02d\t%d\t%02d\t%d\n" $i ${hours[i]} $j ${hours[j]}
done
printf "\nTotal files = %d\n" $count

这个脚本由一个函数(名为 usage),和一个分为四个区块的主体组成。

第一部分,我们检查是否有一个命令行参数, 且该参数为目录。如果不是目录,会显示脚本使用信息并退出。

第二部分初始化一个名为 hours 的数组。给每一个数组元素赋值一个0。虽然没有特殊需要在使用之前准备数组,但是我们的脚本需要确保没有元素是空值。注意这个循环构建方式很有趣。通过使用花括号展开({0..23}),我们能很容易为 for 命令产生一系列的数据(words)。

接下来的一部分收集数据,对目录中的每一个文件运行 stat 程序。我们使用 cut 命令从结果中抽取两位数字的小时字段。 在循环里面,我们需要把小时字段开头的零清除掉,因为 shell 将试图(最终会失败)把从 “00” 到 “09” 的数值解释为八进制。 下一步,我们以小时为数组索引,来增加其对应的数组元素的值。最后,我们增加一个计数器的值(count),记录目录中总共的文件数目。

脚本的最后一部分显示数组中的内容。我们首先输出两行标题,然后进入一个循环产生两栏输出。最后,输出总共的文件数目。

数组操作

输出整个数组的内容

下标 *@ 可以被用来访问数组中的每一个元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ animals=("a dog" "a cat" "a fish")
$ for i in ${animals[*]}; do echo $i; done
a
dog
a
cat
a
fish
$ for i in ${animals[@]}; do echo $i; done
a
dog
a
cat
a
fish
$ for i in "${animals[*]}"; do echo $i; done
a dog a cat a fish
$ for i in "${animals[@]}"; do echo $i; done
a dog
a cat
a fish

${animals[*]}${animals[@]}的行为是一致的直到它们被用引号引起来。

确定数组元素个数

1
2
3
4
5
$ a[100]=foo
$ echo ${#a[@]} # number of array elements
1
$ echo ${#a[100]} # length of element 100
3

把字符串赋值给数组元素100, bash 仅仅报告数组中有一个元素。

这不同于一些其它语言的行为,这种行为是数组中未使用的元素(元素0-99)会初始化为空值, 并把它们计入数组长度。

找到数组使用的下标

${!array[*]}

${!array[@]}

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

1
2
3
4
5
6
7
8
9
$ foo=([2]=a [4]=b [6]=c)
$ for i in "${foo[@]}"; do echo $i; done
a
b
c
$ for i in "${!foo[@]}"; do echo $i; done
2
4
6

在数组末尾添加元素

如果我们需要在数组末尾附加数据,那么知道数组中元素的个数是没用的,因为通过 * 和 @ 表示法返回的数值不能 告诉我们使用的最大数组索引。

shell 为我们提供了一种解决方案。通过使用+=赋值运算符, 我们能够自动地把值附加到数组末尾。这里,我们把三个值赋给数组 foo,然后附加另外三个。

1
2
3
4
5
6
$ foo=(a b c)
$ echo ${foo[@]}
a b c
$ foo+=(d e f)
$ echo ${foo[@]}
a b c d e f

数组排序

1
2
3
4
5
#!/bin/bash
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[@]}"
1
2
3
4
$ array-sort
Original array: f e d c b a
Sorted array:
a b c d e f

删除数组

1
2
3
4
5
6
$ foo=(a b c d e f)
$ echo ${foo[@]}
a b c d e f
$ unset foo
$ echo ${foo[@]}
$

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

1
2
3
4
5
6
$ foo=(a b c d e f)
$ echo ${foo[@]}
a b c d e f
$ unset 'foo[2]'
$ echo ${foo[@]}
a b d e f

注意数组元素必须 用引号引起来为的是防止 shell 执行路径名展开操作。

有趣地是,给一个数组赋空值不会清空数组内容:

1
2
3
4
$ foo=(a b c d e f)
$ foo=
$ echo ${foo[@]}
b c d e f

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

1
2
3
4
5
6
$ foo=(a b c d e f)
$ echo ${foo[@]}
a b c d e f
$ foo=A
$ echo ${foo[@]}
A b c d e f

关联数组

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

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

不同于整数索引的数组,关联数组必须用带有 -A 选项的 declare 命令创建。

1
echo ${colors["blue"]}