我想用tr
替换字符替换字符串中的“非法”字符,其中“非法”字符全部位于一组“允许”字符之外(IE它们是允许的字符集的补充)。但是,当使用该-c
选项以及显式*
重复说明符或“set 2”的隐式扩展时,tr
会附加一个额外的输出的替换字符的实例。
重现
- 令“允许”的字符为
a-n
,按字面指定为abcdefghijklmn
。 - 令替换字符为
z
。 - 让输入字符串为
hell
或hello
。预期的输出字符串分别是 thenhell
和hellz
。
示范
存在非法字符,隐式集 2 扩展
$ echo "hello" | tr -c 'abcdefghijklmn' 'z' hellzz
预期输出是
hellz
。仅允许存在字符,隐式集 2 扩展
$ echo "hell" | tr -c 'abcdefghijklmn' 'z' hellz
预期输出是
hell
。存在非法字符,显式设置 2 扩展名
$ echo "hello" | tr -c 'abcdefghijklmn' '[z*]' hellzz
预期输出是
hellz
。只允许存在字符,显式设置 2 扩展名
$ echo "hell" | tr -c 'abcdefghijklmn' '[z*]' hellz
预期输出是
hell
。当我使用here-string而不是echo-pipe时,也会发生同样的情况(实际上,here-string是我第一次偶然发现这种效果时使用的构造):
$ tr -c 'abcdefghijkl' '[z*]' <<< "hello" hellzz
为什么这里要tr
追加一个z
呢?
这是在 Linux 上,使用 bash、UTF-8 语言环境,并且tr
来自 GNU coreutils 8.25 和 8.30。
答案1
这是因为echo
在你告诉它打印的内容的末尾添加了一个换行符。如果您使用此处字符串,情况也是如此。
所以echo "hello"
实际上打印hello\n
:
$ echo hello | od -c
0000000 h e l l o \n
0000006
这就是为什么你会看到这个:
$ echo "hell" | tr -c 'abcdefghijklmn' 'z'
hellz$
请注意那里没有尾随换行符,并且$
我的提示符出现在最后一个z
.这是因为\n
末尾打印的内容hello\n
被替换为z
.如果你使用printf
它,它会按预期工作:
$ printf "hello" | tr -c 'abcdefghijklmn' 'z'
hellz$
(printf %s "$string"
对于任意字符串,不是)printf "$string"
或者,如果您使用echo
支持它的,请使用echo -n
:
$ echo -n "hello" | tr -c 'abcdefghijklmn' 'z'
hellz$
或者,如果您有一个标准 UNIX echo
(如同时启用和选项时echo
的内置),请使用which Causes停止输出:bash
posix
xpg_echo
\c
echo
$ echo 'hello\c' | tr -c 'abcdefghijklmn' 'z'
hellz$
但很可能您希望在输入中保留该行分隔符,以便输出仍然是正确的文本:
printf '%s\n' "$string" | tr -c 'abcdefghijklmn\n' '[z*]'
(这里使用标准 POSIX 语法,而printf
不是使用echo
它,这样可以更明显地添加换行符,并且还可以避免以字符开头-
或包含\
字符的字符串出现问题)。
另请注意,根据tr
实现的不同,它可能会留下无法单独解码为字符的字节(未更改为z
),而在其他一些(例如 GNU )中tr
,它仅适用于具有单个字符的文本(以及区域设置)每个字符字节。
另一种方法是使用sed
至少在 GNU 实现中在这方面效果更好的方法:
sed 's/[^abcdefghijklmnz]/z/g'
sed
工作于内容行,因此换行符会自动保留。