为什么带有“-c”选项和 set 2 扩展名的“tr”会在末尾添加一个不必要的字符?

为什么带有“-c”选项和 set 2 扩展名的“tr”会在末尾添加一个不必要的字符?

我想用tr替换字符替换字符串中的“非法”字符,其中“非法”字符全部位于一组“允许”字符之外(IE它们是允许的字符集的补充)。但是,当使用该-c选项以及显式*重复说明符或“set 2”的隐式扩展时,tr会附加一个额外的输出的替换字符的实例。

重现

  • 令“允许”的字符为a-n,按字面指定为abcdefghijklmn
  • 令替换字符为z
  • 让输入字符串为hellhello。预期的输出字符串分别是 thenhellhellz

示范

  1. 存在非法字符,隐式集 2 扩展

    $ echo "hello" | tr -c 'abcdefghijklmn' 'z'
    hellzz
    

    预期输出是hellz

  2. 仅允许存在字符,隐式集 2 扩展

    $ echo "hell" | tr -c 'abcdefghijklmn' 'z'
    hellz
    

    预期输出是hell

  3. 存在非法字符,显式设置 2 扩展名

    $ echo "hello" | tr -c 'abcdefghijklmn' '[z*]'
    hellzz
    

    预期输出是hellz

  4. 只允许存在字符,显式设置 2 扩展名

    $ echo "hell" | tr -c 'abcdefghijklmn' '[z*]'
    hellz
    

    预期输出是hell

  5. 当我使用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停止输出:bashposixxpg_echo\cecho

$ 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工作于内容行,因此换行符会自动保留。

相关内容