Links

16.4 文本处理命令

处理文本和文本文件的命令

sort

这是给文本排序的实用程序,通常用作管道的过滤器。该命令按照升序或降序给文本流或文件排序,或者根据各种键或字符位置排序。使用-m选项,它会合并预排序(presorted)的输入文件。info手册列出了该命令的许多功能和选项。你可以参阅样例 11-10样例 11-11样例 A-8

tsort

拓扑排序,会成对读取空格分隔的字符串并根据输入模式进行排序。tsort最初目的是在UNIX的“古早”版本中对过时的ld链接器的依赖项列表进行排序。
tsort的结果通常与上述标准的sort命令执行的结果明显不同。

uniq

此过滤器从已排序的文件中删除重复的行。通常能够在与sort结合的管道中看到。
cat list-1 list-2 list-3 | sort | uniq > final.list
# 连接这些列表文件,
# 给它们排序,
# 删除重复行,
# 最后将结果写入输出文件。
-c选项会以出现次数在输入文件的每行前添加前缀。这是一个很有用的选项。
bash$ cat testfile
This line occurs only once.
This line occurs twice.
This line occurs twice.
This line occurs three times.
This line occurs three times.
This line occurs three times.
bash$ uniq -c testfile
1 This line occurs only once.
2 This line occurs twice.
3 This line occurs three times.
bash$ sort testfile | uniq -c | sort -nr
3 This line occurs three times.
2 This line occurs twice.
1 This line occurs only once.
sort INPUTFILE | uniq -c | sort -nr命令字符串会在INPUTFILE文件上生成频率列表 (sort-nr选项会导致反向数值排序)。此模板可用于分析日志文件和字典列表,以及需要检查文档的词汇结构的任何地方。
样例 16-12. 词频分析
#!/bin/bash
# wf.sh: 文本文件上的粗词频分析。
# 这是"wf2.sh"脚本的更高效的一个版本。
# 在命令行检查输入文件。
ARGS=1
E_BADARGS=85
E_NOFILE=86
if [ $# -ne "$ARGS" ] # 检查是否给脚本传递了正确数量的参数。
then
echo "Usage: `basename $0` filename"
exit $E_BADARGS
fi
if [ ! -f "$1" ] # 检查文件是否存在。
then
echo "File \"$1\" does not exist."
exit $E_NOFILE
fi
########################################################
# main ()
sed -e 's/\.//g' -e 's/\,//g' -e 's/ /\
/g' "$1" | tr 'A-Z' 'a-z' | sort | uniq -c | sort -nr
# =========================
# 出现频率
# 过滤掉句点和逗号,
# 并将单词之间的空格更改为换行,
# 然后将字符转换为小写,
# 最后添加计数和数字排序前缀。
# Arun Giridhar建议将如上修改为:
# . . . | sort | uniq -c | sort +1 [-f] | sort +0 -nr
# 这增加了一个辅助排序键,
# 相等出现的词汇按字母顺序排序。
# 正如他如此解释:
# “这实际上是基数排序,
# 首先是最低有效列
# (单词或字符串,可选择不区分大小写),
# 最后是最高有效列 (频率)。”
# 正如Frank Wang解释,以上等价于
# . . . | sort | uniq -c | sort +0 -nr
# 以下代码也同样有效
# . . . | sort | uniq -c | sort -k1nr -k
########################################################
exit 0
# 练习:
# ---------
# 1) 添加 “sed” 命令以过滤掉其他标点符号,例如分号。
# 2) 修改脚本,以同时过滤掉多个空格和其他空格标志。
bash$ cat testfile
This line occurs only once.
This line occurs twice.
This line occurs twice.
This line occurs three times.
This line occurs three times.
This line occurs three times.
bash$ ./wf.sh testfile
6 this
6 occurs
6 line
3 times
3 three
2 twice
1 only
1 once

expand, unexpand

expand过滤器将制表符转换为空格。它经常用于管道(pipe)中。
unexpand过滤器将空格转换为制表符。起着和expand相反的作用。

cut

这是从文件中提取字段的工具。它类似于awk中的print $ N命令集,但作用更有限。在脚本中使用cut可能比awk更简单。特别重要的是-d (分隔符) 和-f (字段说明符) 选项。
使用cut获得已挂载文件系统的列表:
cut -d ' ' -f1,2 /etc/mtab
使用cut列出操作系统和内核版本:
uname -a | cut -d" " -f1,3,11,1
使用cut从电子邮件文件夹中提取消息头(message header):
bash$ grep '^Subject:' read-messages | cut -c10-80
Re: Linux suitable for mission-critical apps?
MAKE MILLIONS WORKING AT HOME!!!
Spam complaint
Re: Spam complaint
使用cut解析文件:
# 列出所有在/etc/passwd中的用户。
FILENAME=/etc/passwd
for user in $(cut -d: -f1 $FILENAME)
do
echo $user
done
# 感谢Oleg Philon提出的建议。
cut -d ' ' -f2,3 filenameawk -F'[ ]' '{ print $2, $3 }' filename 等价。
note
甚至可以将换行符指定为分隔符。诀窍就是实际在命令序列中键入一个换行符(RETURN)。
bash$ cut -d'
' -f3,7,19 testfile
This is line 3 of testfile.
This is line 7 of testfile.
This is line 19 of testfile.
感谢Jaka Kranjc指出。
你也可以参阅样例 16-48

paste

用于将不同文件合并到单个、多列文件中的工具。与cut结合使用,对于创建系统日志文件很有用。
bash$ cat items
alphabet blocks
building blocks
cables
bash$ cat prices
$1.00/dozen
$2.50 ea.
$3.75
bash$ paste items prices
alphabet blocks $1.00/dozen
building blocks $2.50 ea.
cables $3.75

join

该命令类似于paste,有着特殊的用途。这个强大的实用程序允许以有意义的方式合并两个文件,这实质上创建了简单版本的关系数据库。
join命令仅对两个文件进行操作,但是仅将具有公共标记字段 (通常是数字标签) 的那些行粘贴在一起,然后将结果写入标准输出(stdout)。要加入的文件应根据标记的字段进行排序,以使配对正常工作。
File: 1.data
100 Shoes
200 Laces
300 Socks
File: 2.data
100 $40.00
200 $1.00
300 $2.00
bash$ join 1.data 2.data
File: 1.data 2.data
100 Shoes $40.00
200 Laces $1.00
300 Socks $2.00
note
标记字段在输出中只出现一次。
将文件的起始传递给标准输出(stdout)。默认为10行,但可以指定不同的数字。该命令有许多有趣的选项。
样例 16-13. 哪些文件是脚本?
#!/bin/bash
# script-detector.sh: 检测目录内的脚本。
TESTCHARS=2 # 测试前2个字符。
SHABANG='#!' # 以"sha-bang"开头的脚本。
for file in * # 遍历当前目录中的所有文件。
do
if [[ `head -c$TESTCHARS "$file"` = "$SHABANG" ]]
# head -c2 #!
# “head” 的 “-c” 选项输出指定数量的字符,而不是行 (默认值)。
then
echo "File \"$file\" is a script."
else
echo "File \"$file\" is *not* a script."
fi
done
exit 0
# 练习:
# ---------
# 1) 修改此脚本,以将扫描脚本的目录 (而不仅仅是当前工作目录) 作为可选参数。
#
# 2) 就目前而言,该脚本会“误报”Perl,awk和其他脚本语言脚本。
# 尝试纠正这个。
样例 16-14. 生成10位随机数
#!/bin/bash
# rnd.sh: 输出10位随机数
# Stephane Chazelas所写的脚本.
head -c4 /dev/urandom | od -N4 -tu4 | sed -ne '1s/.* //p'
# =================================================================== #
# 分析
# --------
# head:
# -c4 选项读取前4个字符。
# od:
# -N4 选项限制输出为4个字符。
# -tu4 选项选择无符号十进制格式进行输出。
# sed:
# -n 选项,结合"s"命令的"p"标志
# 仅输出匹配的行。
# 该脚本的作者解释了'sed'所做的动作,如下所示。
# head -c4 /dev/urandom | od -N4 -tu4 | sed -ne '1s/.* //p'
# ----------------------------------> |
# 假设到达"sed"的输出为 --------> |
# 0000000 1198195154\n
# sed开始读取字符串: 0000000 1198195154\n。
# 这里它发现了一个换行符,
# 所以它准备处理第一行 (0000000 1198195154)。
# 它查看其<范围><动作>。第一个且唯一一个是
# 范围 动作
# 1 s/.* //p
# 行号在范围内,因此它执行如下操作:
# 尝试替换行中以空格结尾的最长字符串
# ("0000000 ")不带任何东西(//),如果成功,则打印结果
# (这里"p" 是"s" 命令的标志, 这与"p"命令有所不同)。
# 现在,sed已经准备好继续读取输入。(请注意,在继续之前,
# ,如果没有通过-n选项,则sed会再次打印该行)。
# 现在,sed读取其余字符,并找到文件的结尾。
# 现在可以处理其第二行(由于是最后一行,因此也编号为 “$”)。
# 它似乎与任何<range>均不匹配,所以工作结束。
# sed命令简要来说:
# "仅在第一行, 删除最右边的任何字符,
# 然后打印内容"
# 更好的解决方法是:
# sed -e 's/.* //;q'
# 这里,有两个<范围><动作> (也可以写成
# sed -e 's/.* //' -e q):
# 范围 动作
# nothing (matches line) s/.* //
# nothing (matches line) q (退出)
# 在这里,sed只读取它的第一行输入。
# 它执行这两个操作,并在退出之前 (由于 “q” 操作) 打印行 (替换),因为未传递 “-n” 选项。
# =================================================================== #
# 与上述单行脚本相比,一个更简单的替代方案为:
# head -c4 /dev/urandom| od -An -tu4
exit
你也可以参阅样例 16-39

tail

将文件的末尾传递给标准输出(stdout)。默认为10行,但这可以通过-n选项进行更改。通常用于跟踪系统日志文件的更改,使用-f选项,它输出附加到文件的行。
样例 16-15. 使用tail来监控系统日志
#!/bin/bash
filename=sys.log
cat /dev/null > $filename; echo "Creating / cleaning out file."
# 如果文件不存在,则创建文件,
# 如果存在,则截断为0长度(清空该文件)。
# : > filename 和 > filename 同样有效。
tail /var/log/messages > $filename
# /var/log/messages必须具有全局读取权限,该命令才有效。
echo "$filename contains tail end of system log."
exit 0
要列出文本文件的特定行,请通过管道(pipe)head的输出输送给tail -n 1。例如,head -n 8 database.txt | tail -n 1列出了文件database.txt的第8行。
将文本文件的给定块(block)设置为变量:
var=$(head -n $m $filename | tail -n $n)
# filename = 文件名
# m = 从文件的开头,行数到块的结尾
# n = 要设置变量的行数 (从块末尾修剪)
note
新版本的tail弃用了旧版本的tail -$LINES filename的用法。标准的tail -n $LINES filename仍然是正确的。
你也可以参阅样例 16-5样例 16-39样例 32-6

grep

使用正则表达式的多用途文件搜索工具。它最初是古老的ed行编辑器中的命令/过滤器: g/re/p -- global - regular expression - print

grep pattern [file...]

在目标文件中搜索pattern,其中pattern可以是文字文本或正则表达式。
bash$ grep '[rst]ystem.$' osinfo.txt
The GPL governs the distribution of the Linux operating system.
如果目标文件没有指定,grep会对标准输出(stdout)进行过滤,就像在管道(pipe)中一样。
bash$ ps ax | grep clock
765 tty1 S 0:00 xclock
901 pts/1 S 0:00 grep clock
-i选项为不区分大小写的搜索。
-w选项为全局匹配。
-l选项仅列出找到匹配的文件,而不列出匹配的行。
-r (递归) 选项搜索当前工作目录中的文件及其下方的所有子目录。
-n选项列出了匹配的行以及行号。
bash$ grep -n Linux osinfo.txt
2:This is a file containing information about Linux.
6:The GPL governs the distribution of the Linux operating system.
-v(或--invert-match)选项过滤掉匹配项。
grep pattern1 *.txt | grep -v pattern2
# 匹配 "*.txt" 文件中包含 "pattern1" 的所有行,
# 但 ***不*** 包含 "pattern2"。
-c (--count) 选项给出匹配的数字计数,而不是实际列出匹配项。
grep -c txt *.sgml # (在"*.sgml"文件中"txt"出现的次数)
# grep -cz .
# ^ 点
# 表示匹配".",计数 (-c) 且以零数据分割(-z)的项
# 即非空的 (包含至少1个字符)。
#
printf 'a b\nc d\n\n\n\n\n\000\n\000e\000\000\nf' | grep -cz . # 3
printf 'a b\nc d\n\n\n\n\n\000\n\000e\000\000\nf' | grep -cz '$' # 5
printf 'a b\nc d\n\n\n\n\n\000\n\000e\000\000\nf' | grep -cz '^' # 5
#
printf 'a b\nc d\n\n\n\n\n\000\n\000e\000\000\nf' | grep -c '$' # 9
# 默认情况下,换行符(\n)会将需要匹配的项目分开。
# 请注意,-z选项是GNU "grep"命令的特性。
# 感谢S.C.
--color(或者--colour)选项会用颜色在(控制台或xterm窗口)中标记匹配的字符串。由于grep会打印出包含匹配模式的整行,这会让你清晰地看到哪个被匹配到了。另请参阅-o选项,该选项仅显示行中匹配到的部分。
样例 16-16. 打印出本地电子邮件中的From
#!/bin/bash
# from.sh
# 在Solaris、BSD等系统中模拟'from'使用工具。
# 输出电子邮件目录中所有邮件中的 “发件人(From)” 标题行。
MAILDIR=~/mail/* # 不引用变量。为什么?
# 或许需要检查目录是否存在: if [ -d $MAILDIR ] . . .
GREP_OPTS="-H -A 5 --color" # 展示文件,展示额外上下文
# 并用颜色显示"from"。
TARGETSTR="^From" # 行开头的"From"。
for file in $MAILDIR # 不引用变量。
do
grep $GREP_OPTS "$TARGETSTR" "$file"
# ^^^^^^^^^^ # 同样,不引用变量。
echo
done
exit $?
# 你可能想要将该脚本的输出通过管道传递给'more'命令
# 或者重定向到文件. . .
当在给定多个目标文件的情况下调用时,grep会显示是哪个文件包含匹配项。
bash$ grep Linux osinfo.txt misc.txt
osinfo.txt:This is a file containing information about Linux.
osinfo.txt:The GPL governs the distribution of the Linux operating system.
misc.txt:The Linux operating system is steadily gaining in popularity.
要强制grep在仅搜索一个目标文件时显示文件名,只需将/dev/null作为第二个文件即可。
bash$ grep Linux osinfo.txt /dev/null
osinfo.txt:This is a file containing information about Linux.
osinfo.txt:The GPL governs the distribution of the Linux operating system.
如果匹配成功,grep将返回0的退出状态,因此这在脚本中的条件测试中很有用,尤其是与-q选项结合使用以取消输出。
SUCCESS=0 # 如果grep查找成功
word=Linux
filename=data.file
grep -q "$word" "$filename" # "-q"选项会
# 导致什么都不在stdout中输出
if [ $? -eq $SUCCESS ]
# if grep -q "$word" "$filename" 可以替换 5 - 7 行。
then
echo "$word found in $filename"
else
echo "$word not found in $filename"
fi
样例 32-6演示了如何使用grep命令在系统日志文件中搜索单词。
样例 16-17. 在脚本中模仿grep命令
#!/bin/bash
# grp.sh: 实现grep命令的基本功能。
E_BADARGS=85
if [ -z "$1" ] # 检查脚本的参数。
then
echo "Usage: `basename $0` pattern"
exit $E_BADARGS
fi
echo
for file in * # 遍历$PWD中的全部文件
do
output=$(sed -n /"$1"/p $file) # 命令替换。
if [ ! -z "$output" ] # 如果"$output"没有引用会发生什么?
then
echo -n "$file: "
echo "$output"
fi # sed -ne "/$1/s|^|${file}: |p" 与上等效。
echo
done
echo
exit 0
# 练习:
# ---------
# 1) 如果在任何给定文件中有多个匹配项,则将换行符添加到输出中。
# 2) 添加特性。
grep如何同时搜索两个 (或多个) 单独的模式(pattern)?如果你希望grep显示一个文件中的所有行或同时包含 “pattern1” 和 “pattern2” 的文件,该怎么办?
一种方法是将grep pattern1的结果通过管道(pipe)输送给grep pattern2
例如,给定以下文件:
# Filename: tstfile
This is a sample file.
This is an ordinary text file.
This file does not contain any unusual text.
This file is not unusual.
Here is some text.
现在,让我们在这个文件中搜索同时包含"file"和"text"的行. . .
bash$ grep file tstfile
# Filename: tstfile
This is a sample file.
This is an ordinary text file.
This file does not contain any unusual text.
This file is not unusual.
bash$ grep file tstfile | grep text
This is an ordinary text file.
This file does not contain any unusual text.
现在,让我们来玩一下grep吧~~
样例 16-18. 纵横字谜解算器
#!/bin/bash
# cw-solver.sh
# 这脚本实际上就是围绕一条命令的包装而已(46行).
# 填字游戏和anagramming文字游戏求解器。
# 你知道你要找的单词中的 *一些* 字母,
# 所以你需要一个列表,
# 列出在给定的位置上所有有效的单词和已知的字母。
# 例如: w...i....n
# 1???5????10
# w在位置1, 3个未知字母, i在位置5, 4个未知字母, n在末尾。
# (请参阅脚本末尾的注释。)
E_NOPATT=71
DICT=/usr/share/dict/word.lst
# ^^^^^^^^ 在这里寻找单词列表。
# ASCII单词列表,一行一个字母。
# 如果你碰巧需要这么一个单词列表,
# 请下载作者的 “yawl” 单词列表包。
# http://ibiblio.org/pub/Linux/libs/yawl-0.3.2.tar.gz
# 或者
# http://bash.deta.in/yawl-0.3.2.tar.gz
if [ -z "$1" ] # 如果没有单词模式指定为命令行参数 . . .
then
echo #+ . . . 那么 . . .
echo "Usage:" #+ 打印使用信息。
echo
echo ""$0" \"pattern,\""
echo "where \"pattern\" is in the form"
echo "xxx..x.x..."
echo
echo "The x's represent known letters,"
echo "and the periods are unknown letters (blanks)."
echo "Letters and periods can be in any position."
echo "For example, try: sh cw-solver.sh w...i....n"
echo
exit $E_NOPATT
fi
echo
# ===============================================
# 这是所有工作完成的地方。
grep ^"$1"$ "$DICT" # 没错,就一行代码!
# | |
# ^ 匹配输入字符串的开始位置。
# $ 匹配输入字符串的结束位置。
# 摘自 _Stupid Grep Tricks_, vol. 1,
# 《ABS指南》(本书)的作者会再去里面找找看还有什么好玩的
# . . . 可能就这几天吧 . . .
# ===============================================
echo
exit $? # 脚本到此停止执行。
# 如果生成的单词太多,
# 请将输出重定向到文件。
$ sh cw-solver.sh w...i....n
wellington
workingman
workingmen
egrep -- 扩展的grep -- 与grep -E相同。这用起来有一点不一样,它支持扩展的正则表达式,令搜索更加地灵活。它同时也支持布尔 | (or)运算符。
bash $ egrep 'matches|Matches' file.txt
Line 1 matches.
Line 3 Matches.
Line 4 contains matches, but also Matches
fgrep -- 快速的grep -- 与grep -F相同。它只做文字字符串搜索(没有正则表达式),通常会加快一点速度。
note
在某些Linux发行版上,egrepfgrepgrep的符号链接或者别名(aliase),但分别使用-E-F选项调用。
样例 16-19. 在Webster 1913字典中查找定义
#!/bin/bash
# dict-lookup.sh
# 此脚本在1913 Webster的词典中查找定义.
# 该公共领域词典可从多个站点下载,
# 比如Gutenberg项目(http://www.gutenberg.org/etext/247)。
#
# 在使用该脚本之前,
# 请将其从DOS格式转换成UNIX格式(行末尾只有LF)
# 将文件存储在普通的、未压缩的ASCII文本中。
# 将下面的DEFAULT_DICTFILE变量设置为路径/文件名。
E_BADARGS=85
MAXCONTEXTLINES=50 # 要显示的最大行数。
DEFAULT_DICTFILE="/usr/share/dict/webster1913-dict.txt"
# 默认的字典文件路径。
# 有必要的话请重新设置。
# 注意:
# ----
# 该特定版本的1913 Webster字典每个单词以大写字母开头
# (单词剩下的部分为小写字母)。
# 只有条目的 *第一行* 以这种方式开始,
# 这就是下面的搜索算法起作用的原因。
if [[ -z $(echo "$1" | sed -n '/^[A-Z]/p') ]]
# 必须至少要指定要查找的单词,并且
# 它必须以大写字母开头。
then
echo "Usage: `basename $0` Word-to-define [dictionary-file]"
echo
echo "Note: Word to look up must start with capital letter,"
echo "with the rest of the word in lowercase."
echo "--------------------------------------------"
echo "Examples: Abandon, Dictionary, Marking, etc."
exit $E_BADARGS
fi
if [ -z "$2" ] # 可以指定不同的字典
# 来作为此脚本的参数。
then
dictfile=$DEFAULT_DICTFILE
else
dictfile="$2"
fi
# ---------------------------------------------------------
Definition=$(fgrep -A $MAXCONTEXTLINES "$1 \\" "$dictfile")
# 展现形式定义为 "Word \..."
#
# 而且,是的,“fgrep” 足够快,甚至可以搜索非常大的文本文件。
# 现在,裁剪定义块。
echo "$Definition" |
sed -n '1,/^[A-Z]/p' |
# 从输出的第一行打印到下一个条目的第一行。
sed '$d' | sed '$d'
# 删除最后两行输出
# (下一个条目的空行和第一行)。
# ---------------------------------------------------------
exit $?
# 练习:
# ---------
# 1) 修改脚本以接受任何类型的字母输入
# (大写、小写、混合格式),并且将其转换为可接受的格式进行处理。
#
# 2) 将脚本转换为GUI应用程序,
# 使用诸如'gdialog'或'zenity'等工具 . . .
# 脚本将不再从命令行中获取参数。
#
# 3) 修改脚本以解析其他可用的公共领域词典之一,
# 例如美国人口普查局地名词典(U.S. Census Bureau Gazetteer)。
note
另请参阅样例 A-41,了解如何在大型文本文件上进行快速fgrep查找。
agrep (近似的grep) 将grep的能力扩展到近似匹配。搜索字符串可能与结果匹配的字符数不同。此实用程序不是核心Linux发行版的一部分。
要搜索压缩文件,请使用zgrepzegrepzfgrep命令。这些也适用于非压缩文件,尽管比grepegrepfgrep慢。对于搜索一组有些是压缩文件有些不是的混合文件来说很方便。
搜索bzip压缩文件,请使用bzgrep

look

look命令像grep,但是在 “字典”,即已排序的单词列表上进行查找。默认情况下,look在/usr/dict/words中搜索匹配项,但也有可能会指定其他字典文件。
样例 16-20. 检查列表中的单词是否有效
#!/bin/bash
# lookup: 对数据文件中的每个单词进行字典查找。
file=words.data # 将从中读取测试单词的数据文件。
echo
echo "Testing file $file"
echo
while [ "$word" != end ] # 数据文件中的最后一个单词。
do # ^^^
read word # 从数据文件中read,因为循环结束时的重定向。
look $word > /dev/null # 不想显示字典文件中的行。
# 在文件/usr/share/dict/words中寻找单词
# (经常链接linux.words)。
lookup=$? # 'look'命令的退出状态。
if [ "$lookup" -eq 0 ]
then
echo "\"$word\" is valid."
else
echo "\"$word\" is invalid."
fi
done <"$file" # 重定向标准输入(stdin)到$file,所以'read'命令从这读取。
echo
exit 0
# ----------------------------------------------------------------
# 由于上面的"exit"命令,行以下的代码将无法执行。
# Stephane Chazelas提出了以下更简洁的替代方案:
while read word && [[ $word != end ]]
do if look "$word" > /dev/null
then echo "\"$word\" is valid."
else echo "\"$word\" is invalid."
fi
done <"$file"
exit 0

sed,awk

特别适合解析文本文件和命令输出的脚本语言。可以单独嵌入,也可以组合嵌入管道(pipe)和shell脚本中。

sed

非交互式 “流编辑器”,允许在批处理模式下使用许多ex命令。它在shell脚本中有许多用途。

awk

可编程文件提取器和格式化处理器,适用于处理和/或提取结构化文本文件中的字段 (列)。它的语法类似于C语言。

wc

wc命令对一个文件或者I/O流进行“词汇统计”:
bash $ wc /usr/share/doc/sed-4.1.2/README
13 70 447 README
[13 lines 70 words 447 characters]
wc -w仅给出词汇统计。
wc -l仅给出行数统计。
wc -c仅给出字节数统计。
wc -m仅给出字符数统计。
wc -L仅给出最长行的长度。
使用wc命令统计当前工作目录下有多少.txt文件:
$ ls *.txt | wc -l
# 只要 “*.txt” 文件的名称中没有嵌入换行符,就可以工作。
# 以下为其他的等效方案:
# find . -maxdepth 1 -name \*.txt -print0 | grep -cz .
# (shopt -s nullglob; set -- *.txt; echo $#)
# 感谢S.C.
使用wc统计所有文件名以d - h范围内的字母开头的文件大小
bash$ wc [d-h]* | grep total | awk '{print $3}'
71832
使用wc统计本书主源文件中"Linux"一词的数量。
bash$ grep Linux abs-book.sgml | wc -l
138
你也可以参阅样例 16-39样例 20-8
某些命令会使用wc的一些功能作为选项。
... | grep foo | wc -l
# 这类经常使用的结构可以更简洁地呈现。
... | grep -c foo
# 只需使用grep命令的 "-c" (或"--count") 选项即可。
# 感谢S.C.

tr

字符翻译过滤器。
note
必须酌情使用引号和/或括号。引号阻止shell重新解释tr命令序列中的特殊字符。括号应加上,以防止shell的扩展行为。
tr "A-Z" "*" <filename以及tr A-Z * <filename两者都会将filename中的大写字母更改为星号(写入标准输出(stdout))。在一些系统上,这可能会失效,但是tr A-Z '[**]'仍然有效。
-d选项会删除范围内的字符。
echo "abcdef" # abcdef
echo "abcdef" | tr -d b-d # aef
tr -d 0-9 <filename
# 删除文件"filename"中所有数字。
--squeeze-repeats(或者-s)选项删除除了第一个连续字符串的所有实例。这个选项在移除多余的空格时很有用。
bash$ echo "XXXXX" | tr --squeeze-repeats 'X'
X
-c“补码”选项将字符集反转来进行匹配。当使用该选项时,tr命令仅作用于与指定集匹配的字符。
bash$ echo "acfdeb123" | tr -c b-d +
+c+d+b++++
请注意,tr命令仅识别POSIX字符集[1]
bash$ echo "abcd2ef1" | tr '[:alpha:]' -
----2--1*
样例 16-21. toupper:将文本全部转换为大写字母
#!/bin/bash
# 将文本全部转换为大写字母。
E_BADARGS=85
if [ -z "$1" ] # 命令行参数的标准检查。
then
echo "Usage: `basename $0` filename"
exit $E_BADARGS
fi
tr a-z A-Z <"$1"
# 效果同上,但使用POSIX字符集表示法:
# tr '[:lower:]' '[:upper:]' <"$1"
# 感谢S.C.
# 甚至 . . .
# cat "$1" | tr a-z A-Z
# 或其他更多的方式 . . .
exit 0
# 练习:
# 重写该脚本,提供将文件 *既* 可以转换为大写 *又*可以转换为小写的选项。
# 提示:你可以使用"select"或"case"命令。
样例 16-22. lowercase: 将工作目录中所有文件名转为小写
#!/bin/bash
#
# 将工作目录中所有文件名转为小写。
#
# 受到John Dubois所编写的脚本的启发,
# 并被Chet Ramey翻译成Bash脚本,
# 且由本书作者进行了相当大的简化。
for filename in * # 遍历目录下所有文件。
do
fname=`basename $filename`
n=`echo $fname | tr A-Z a-z` # 将名称转为小写。
if [ "$fname" != "$n" ] # 仅重命名名称不是小写的文件。
then
mv $fname $n
fi
done
exit $?
# 该行以下代码不会被执行因为"exit"命令。
#--------------------------------------------------------#
# 若想执行以下命令,请删除本脚本以上的代码。
# 上面的脚本对包含空格或换行符的文件名不起作用。
# 因此Stephane Chazelas therefore建议使用如下的替换方案:
for filename in * # 没有必要使用basename命令,
# 因为 "*" 不会返回任何包含 "/" 的文件。
do n=`echo "$filename/" | tr '[:upper:]' '[:lower:]'`
# POSIX字符集表示方法。
# 添加了斜线,因此不会通过命令替换删除尾随换行符。
# 变量替换:
n=${n%/} # 从文件名中删除上面添加的拖尾斜杠。
[[ $filename == $n ]] || mv "$filename" "$n"
# 检查文件名是不是本来就是小写。