Shell 用 tr 进行转换,tr
是Unix命令行专家工具箱中的一件万能工具。它可用于编写优雅的单行命令。tr
可以对来自标准输入的内容进行字符替换、字符删除以及重复字符压缩。tr
是translate(转换)的简写,因为它可以将一组字符转换成另一组字符。在本章中,我们会看到如何使用tr
进行基本的集合转换。
预备知识
tr
只能通过stdin
(标准输入)接收输入(无法通过命令行参数接收)。其调用格式如下:
tr [options] set1 set2
来自stdin
的输入字符会按照位置从set1
映射到set2
(set1
中的第一个字符映射到set2
中的第一个字符,以此类推),然后将输出写入stdout
(标准输出)。set1
和set2
是字符类或字符组。如果两个字符组的长度不相等,那么set2
会不断复制其最后一个字符,直到长度与set1
相同。如果set2
的长度大于set1
,那么在set2
中超出set1
长度的那部分字符则全部被忽略。
实战演练
要将输入中的字符由大写转换成小写,可以使用下面的命令:
$ echo "HELLO WHO IS THIS" | tr 'A-Z' 'a-z'
hello who is this
'A-Z'
和'a-z'
都是字符组。我们可以按照需要追加字符或字符类来构造自己的字符组。
'ABD-}'
、'aA.,'
、'a-ce-x'
以及'a-c0-9'
等均是合法的集合。定义集合也很简单,不需要书写一长串连续的字符序列,只需要使用“起始字符-终止字符”这种格式就行了。这种写法也可以和其他字符或字符类结合使用。如果“起始字符-终止字符”不是有效的连续字符序列,那么它就会被视为含有3个元素的集合(起始字符、-和终止字符)。你也可以使用像'\t'
、'\n'
这种特殊字符或其他ASCII字符。
工作原理
在tr
中利用集合的概念,可以轻松地将字符从一个集合映射到另一个集合中。下面来看一个用tr
进行数字加密和解密的例子:
$ echo 12345 | tr '0-9' '9876543210'
87654 # 已加密
$ echo 87654 | tr '9876543210' '0-9'
12345 # 已解密
tr
命令可以用来加密。ROT13是一个著名的加密算法。在ROT13算法中,字符会被移动13个位置,因此文本加密和解密都使用同一个函数:
$ echo "tr came, tr saw, tr conquered." | tr 'a-zA-Z' 'n-za-mN-ZA-M'
输出如下:
ge pnzr, ge fnj, ge pbadhrerq.
对加密后的密文再次使用同样的ROT13函数,我们可以采用:
$ echo ge pnzr, ge fnj, ge pbadhrerq. | tr 'a-zA-Z' 'n-za-mN-ZA-M'
输出如下:
tr came, tr saw, tr conquered.
tr
还可以将制表符转换成单个空格:
$ tr '\t' ' ' < file.txt
补充内容
我们已经学习了tr
的一些基本转换,接下来看看tr
还能帮我们实现的其他功能。
- 用
tr
删除字符
tr
有一个选项-d
,可以通过指定需要被删除的字符集合,将出现在stdin
中的特定字符清除掉:
$ cat file.txt | tr -d '[set1]'
#只使用set1,不使用set2
例如:
$ echo "Hello 123 world 456" | tr -d '0-9'
Hello world
# 将stdin中的数字删除并打印删除后的结果
- 字符组补集
我们可以利用选项-c
来使用set1
的补集。下面的命令中,set2
是可选的:
tr -c [set1] [set2]
如果只给出了set1
,那么tr
会删除所有不在set1
中的字符。如果也给出了set2
,tr
会将不在set1
中的字符转换成set2
中的字符。如果使用了-c
选项,set1
和set2
必须都给出。如果-c
与-d
选项同时出现,你只能使用set1
,其他所有的字符都会被删除。
下面的例子会从输入文本中删除不在补集中的所有字符:
$ echo hello 1 char 2 next 4 | tr -d -c '0-9 \n'
124
接下来的例子会将不在set1
中的字符替换成空格:
$ echo hello 1 char 2 next 4 | tr -c '0-9' ' '
1 2 4
- 用
tr
压缩字符
tr
命令能够完成很多文本处理任务。例如,它可以删除字符串中重复出现的字符。基本实现形式如下:
tr -s '[需要被压缩的一组字符]'
如果你习惯在点号后面放置两个空格,你需要在不删除重复字母的情况下去掉多余的空格:
$ echo "GNU is not UNIX. Recursive right ?" | tr -s ' '
GNU is not UNIX. Recursive right ?
tr
命令还可以用来删除多余的换行符:
$ cat multi_blanks.txt | tr -s '\n'
line 1
line 2
line 3
line 4
上面的例子展示了如何使用tr
删除多余的'\n'
字符。接下来让我们用tr
以一种巧妙的方式将文件中的数字列表进行相加:
$ cat sum.txt
1
2
3
4
5
$ cat sum.txt | echo $[ $(tr '\n' '+' ) 0 ]
15
这招是如何起效的?
在命令中,tr
命令将'\n'
替换成了'+'
,我们因此得到了字符串1+2+3+..5+
,但是在字符串的尾部多了一个操作符+
。为了抵消这个多出来的操作符,我们再追加一个0
。
$[ operation ]
执行算术运算,因此就形成了以下命令:
echo $[ 1+2+3+4+5+0 ]
如果我们利用循环从文件中读取数字,然后再进行相加,那肯定得用几行代码。有了tr
,只用一行就搞定了。
如果有一个包含字母和数字的文件,我们想计算其中的数字之和,这需要更强的技巧性:
$ cat test.txt
first 1
second 2
third 3
利用tr
的-d
选项删除文件中的字母,然后将空格替换成+
:
$ cat test.txt | tr -d [a-z] | echo "total: $[$(tr ' ' '+')]"
total: 6
- 字符类
tr
可以将不同的字符类作为集合使用,所支持的字符类如下所示:
alnum
:字母和数字。alpha
:字母。cntrl
:控制(非打印)字符。digit
:数字。graph
:图形字符。lower
:小写字母。print
:可打印字符。punct
:标点符号。space
:空白字符。upper
:大写字母。xdigit
:十六进制字符。
可以按照下面的方式选择所需的字符类:
tr [:class:] [:class:]
例如:
tr '[:lower:]' '[:upper:]'