其实通过一个循环就可以产生一系列数,但是有相关工具为什么不用呢!seq
就是这么一个小工具,它可以产生一系列数,你可以指定数的递增间隔,也可以指定相邻两个数之间的分割符。
范例:获取一系列数
$ seq 5
1
2
3
4
5
$ seq 1 5
1
2
3
4
5
$ seq 1 2 5
1
3
5
$ seq -s: 1 2 5
1:3:5
$ seq 1 2 14
1
3
5
7
9
11
13
$ seq -w 1 2 14
01
03
05
07
09
11
13
$ seq -s: -w 1 2 14
01:03:05:07:09:11:13
$ seq -f "0x%g" 1 5
0x1
0x2
0x3
0x4
0x5
一个比较典型的使用 seq
的例子,构造一些特定格式的链接,然后用 wget
下载这些内容:
$ for i in `seq -f"http://thns.tsinghua.edu.cn/thnsebooks/ebook73/%02g.pdf" 1 21`;do wget -c $i; done
或者
$ for i in `seq -w 1 21`;do wget -c "http://thns.tsinghua.edu.cn/thnsebooks/ebook73/$i"; done
补充:在 Bash
版本 3 以上,在 for
循环的 in
后面,可以直接通过 {1..5}
更简洁地产生自 1 到 5 的数字(注意,1 和 5 之间只有两个点),例如:
$ for i in {1..5}; do echo -n "$i "; done
1 2 3 4 5
范例:统计字符串中各单词出现次数
我们先给单词一个定义:由字母组成的单个或者多个字符系列。
首先,统计每个单词出现的次数:
$ wget -c http://tinylab.org
$ cat index.html | sed -e "s/[^a-zA-Z]/\n/g" | grep -v ^$ | sort | uniq -c
接着,统计出现频率最高的前10个单词:
$ wget -c http://tinylab.org
$ cat index.html | sed -e "s/[^a-zA-Z]/\n/g" | grep -v ^$ | sort | uniq -c | sort -n -k 1 -r | head -10
524 a
238 tag
205 href
201 class
193 http
189 org
175 tinylab
174 www
146 div
128 title
说明:
cat index.html
: 输出 index.html 文件里的内容sed -e "s/[^a-zA-Z]/\n/g"
: 把非字母字符替换成空格,只保留字母字符grep -v ^$
: 去掉空行sort
: 排序uniq -c
:统计相同行的个数,即每个单词的个数sort -n -k 1 -r
:按照第一列-k 1
的数字-n
逆序-r
排序head -10
:取出前十行
可以考虑采取两种办法:
- 只统计那些需要统计的单词
- 用上面的算法把所有单词的个数都统计出来,然后再返回那些需要统计的单词给用户
不过,这两种办法都可以通过下面的结构来实现。先看办法一:
#!/bin/bash
# statistic_words.sh
if [ $# -lt 1 ]; then
echo "Usage: basename $0 FILE WORDS ...."
exit -1
fi
FILE=$1
((WORDS_NUM=$#-1))
for n in $(seq $WORDS_NUM)
do
shift
cat $FILE | sed -e "s/[^a-zA-Z]/\n/g" \
| grep -v ^$ | sort | grep ^$1$ | uniq -c
done
说明:
if 条件部分
:要求至少两个参数,第一个单词文件,之后参数为要统计的单词FILE=$1
: 获取文件名,即脚本之后的第一个字符串((WORDS_NUM=$#-1))
:获取单词个数,即总的参数个数$#
减去文件名参数(1个)for 循环部分
:首先通过seq
产生需要统计的单词个数系列,shift
是 Shell 内置变量(请通过help shift
获取帮助),它把用户从命令行中传入的参数依次往后移动位置,并把当前参数作为第一个参数即$1
,这样通过$1
就可以遍历用户所有输入的单词(仔细一想,这里貌似有数组下标的味道)。你可以考虑把shift
之后的那句替换成echo $1
测试shift
的用法
演示:
$ chmod +x statistic_words.sh
$ ./statistic_words.sh index.html tinylab linux python
175 tinylab
43 linux
3 python
再看办法二,我们只需要修改 shift
之后的那句即可:
#!/bin/bash
# statistic_words.sh
if [ $# -lt 1 ]; then
echo "ERROR: you should input 2 words at least";
echo "Usage: basename $0 FILE WORDS ...."
exit -1
fi
FILE=$1
((WORDS_NUM=$#-1))
for n in $(seq $WORDS_NUM)
do
shift
cat $FILE | sed -e "s/[^a-zA-Z]/\n/g" \
| grep -v ^$ | sort | uniq -c | grep " $1$"
done
演示:
$ ./statistic_words.sh index.html tinylab linux python
175 tinylab
43 linux
3 python
说明:很明显,办法一的效率要高很多,因为它提前找出了需要统计的单词,然后再统计,而后者则不然。实际上,如果使用 grep
的 -E
选项,我们无须引入循环,而用一条命令就可以搞定:
$ cat index.html | sed -e "s/[^a-zA-Z]/\n/g" | grep -v ^$ | sort | grep -E "^tinylab$|^linux$" | uniq -c
43 linux
175 tinylab
或者
$ cat index.html | sed -e "s/[^a-zA-Z]/\n/g" | grep -v ^$ | sort | egrep "^tinylab$|^linux$" | uniq -c
43 linux
175 tinylab
说明:需要注意到 sed
命令可以直接处理文件,而无需通过 cat
命令输出以后再通过管道传递,这样可以减少一个不必要的管道操作,所以上述命令可以简化为:
$ sed -e "s/[^a-zA-Z]/\n/g" index.html | grep -v ^$ | sort | egrep "^tinylab$|^linux$" | uniq -c
43 linux
175 tinylab
所以,可见这些命令 sed
,grep
,uniq
,sort
是多么有用,它们本身虽然只完成简单的功能,但是通过一定的组合,就可以实现各种五花八门的事情啦。对了,统计单词还有个非常有用的命令 wc -w
,需要用到的时候也可以用它。
补充:在 Advanced Bash-Scripting Guide 一书中还提到 jot
命令和 factor
命令,由于机器上没有,所以没有测试,factor
命令可以产生某个数的所有素数。如:
$ factor 100
100: 2 2 5 5