通常来说,使用 shell 脚本时候不需要太顾及程序的效率性,能完成任务,只要不是太慢,都可以接受。

但在一些特定应用场合,脚本效率也是一个硬性限制,不能耗时太久,比如说,用 shell 来编写一些处理 HTTP 请求的 CGI 程序,脚本的执行效率直接影响了 HTTP 的响应速度。

在 shell 中分割字符串有多种方法,比如利用 awk、cut 工具,还有内置方法,虽然效果相同,但是执行效率差距较大,尤其在处理大数据量上更是显著。

用 cut 工具分割字符串一般是以下用法:

1
2
# -f 指定列序号 -d 指定分隔符
echo $line | cut -f1 -d ' '

用 awk 工具分割字符串类似:

1
2
3
# $1 代表第一列 以此类推
# -F 指定分割符号
echo $line | awk -F ' ' {'print $1'}

内部方法分割字符串,用的是 IFS 变量和 set 命令:

1
2
3
4
5
6
# 设置分隔符
IFS=" "
# 分割 line
set -- $line
# $1 代表第一列,以此类推
echo $1

在 shell 脚本中,使用 awk/cut 这些工具在运行时会创建 sub-shell 来运行,相当于 C 语言中的 system/popen 调用,当需要使用的次数比较多时,这种调用会显著降低脚本的执行速度(创建进程是比较昂贵的)。

而使用内置方法则无需调用外部工具,执行起来效率更高,没有额外开销。

以处理 1000 行的空格分割的数字并分别累加为例,其文件内容大概如下:

1
2
3
4
5
6
7
8
1    25   30  100
2    26   33  102
3    27   36  104

...

999  1023 3024 2096
1000 1024 3027 2098

保存为文件 test.data。然后编写2个测试脚本,split1.shsplit2.sh

split1.sh内容如下:

 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
#!/bin/sh

DataFile="test.data"

if [ ! -e $DataFile ]; then
	echo "Fail to open file"
	exit 1
fi

F1=0
F2=0
F3=0
F4=0
# 设置分割符
IFS=" "

while read line
do
	[ -z "$line" ] && continue
	set -- $line

	F1=$((F1+$1))
	F2=$((F2+$2))
	F3=$((F3+$3))
	F4=$((F4+$4))
done < $DataFile

echo "F1=$F1"
echo "F2=$F2"
echo "F3=$F3"
echo "F4=$F4"

split2.sh文件内容如下:

 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
32
33
#!/bin/sh

DataFile="test.data"

if [ ! -e $DataFile ]; then
	echo "Fail to open file"
	exit 1
fi

F1=0
F2=0
F3=0
F4=0
IFS=" "

while read line
do
	[ -z "$line" ] && continue
	l1=$(echo $line | cut -f1 -d ' ')
	l2=$(echo $line | cut -f2 -d ' ')
	l3=$(echo $line | cut -f3 -d ' ')
	l4=$(echo $line | cut -f4 -d ' ')

	F1=$((F1+$l1))
	F2=$((F2+$l2))
	F3=$((F3+$l3))
	F4=$((F4+$l4))
done < $DataFile

echo "F1=$F1"
echo "F2=$F2"
echo "F3=$F3"
echo "F4=$F4"

split1.sh使用内置方法,split2.sh使用 cut 工具来处理。在 shell 中运行程序并用 time 命令监控执行时间,会发现速度对比特别显著。

以本人测试电脑为例,内置方法执行输出如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
$ time ./split1.sh
F1=500500
F2=524500
F3=1528500
F4=1099000

real	0m0.045s
user	0m0.024s
sys	0m0.020s

$ time ./split2.sh
F1=500500
F2=524500
F3=1528500
F4=1099000

real	0m4.603s
user	0m0.004s
sys	0m0.736s

粗略看出执行速度相差 100 倍左右。

备注:因为要获取各列数据累加,所以在脚本2中,使用了4次 cut 调用,相当于字符串分割了4此,而脚本1中只使用了一次字符串分割,这种策略也显著增加了执行开销。可以通过使用数组返回 cut 结果来避免多次调用,但是这得看 shell 环境是否支持,某些情况下是不支持数组的,比如 busybox 的 sh 环境。

(完)