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

Adm*_*Bee 5 text-processing tr

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

重现

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

示范

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

    $ echo "hello" | tr -c 'abcdefghijklmn' 'z'
    hellzz
    
    Run Code Online (Sandbox Code Playgroud)

    预期输出是hellz.

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

    $ echo "hell" | tr -c 'abcdefghijklmn' 'z'
    hellz
    
    Run Code Online (Sandbox Code Playgroud)

    预期输出是hell.

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

    $ echo "hello" | tr -c 'abcdefghijklmn' '[z*]'
    hellzz
    
    Run Code Online (Sandbox Code Playgroud)

    预期输出是hellz.

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

    $ echo "hell" | tr -c 'abcdefghijklmn' '[z*]'
    hellz
    
    Run Code Online (Sandbox Code Playgroud)

    预期输出是hell.

  5. 当我使用here-string而不是echo-pipe时,也会发生同样的情况(实际上,here-string是我第一次偶然发现这种效果时使用的构造):

    $ tr -c 'abcdefghijkl' '[z*]' <<< "hello"
    hellzz
    
    Run Code Online (Sandbox Code Playgroud)

为什么这里要tr追加一个呢?z

这是在 Linux 上,使用 bash、UTF-8 语言环境,并且tr来自 GNU coreutils 8.25 和 8.30。

ter*_*don 12

这是因为在echo你告诉它打印的内容的末尾添加了一个换行符。如果您使用此处字符串,情况也是如此。

所以echo "hello"实际上打印hello\n

$ echo hello | od -c
0000000   h   e   l   l   o  \n
0000006
Run Code Online (Sandbox Code Playgroud)

这就是为什么你会看到这个:

$ echo "hell" | tr -c 'abcdefghijklmn' 'z'
hellz$
Run Code Online (Sandbox Code Playgroud)

请注意那里没有尾随换行符,并且$我的提示符出现在最后一个z. 这是因为\n末尾打印的内容hello\n被替换为z. 如果你使用printf它,它会按预期工作:

$ printf "hello" | tr -c 'abcdefghijklmn' 'z'
hellz$
Run Code Online (Sandbox Code Playgroud)

printf %s "$string"对于任意字符串,不是printf "$string"

或者,如果您使用echo支持它的,请使用echo -n

$ echo -n "hello" | tr -c 'abcdefghijklmn' 'z'
hellz$
Run Code Online (Sandbox Code Playgroud)

或者,如果您有标准的 UNIX echo(如同时启用和选项时echo的内置),请使用which Causes停止输出:bashposixxpg_echo\cecho

$ echo 'hello\c' | tr -c 'abcdefghijklmn' 'z'
hellz$
Run Code Online (Sandbox Code Playgroud)

但很可能您想在输入中保留该行分隔符,以便输出仍然是正确的文本:

printf '%s\n' "$string" | tr -c 'abcdefghijklmn\n' '[z*]'
Run Code Online (Sandbox Code Playgroud)

(这里使用标准 POSIX 语法,而printf不是使用echo它,这样可以更明显地添加换行符,并且还可以避免以字符开头-或包含\字符的字符串出现问题)。

另请注意,根据tr实现的不同,它可能会留下无法单独解码为字符的字节(未更改为z),而在其他一些(如 GNU )中tr,它仅适用于具有单个字符的文本(以及区域设置)每个字符字节。

另一种方法是使用sed至少在 GNU 实现中在这方面效果更好的方法:

sed 's/[^abcdefghijklmnz]/z/g'
Run Code Online (Sandbox Code Playgroud)

sed作用于该行的内容,因此换行符会自动保留。

  • @AdminBee仅供参考,这里的字符串必须附加换行符,因为它的目的是将字符串转换为文本处理工具可以像处理输入文件一样处理的内容,并且每个 POSIX 的有效文本文件总是有一个终止符新队。因此,要使字符串 `hello` 成为 awk、sed、grep、read 等工具的有效输入文件,`&lt;&lt;&lt;'hello'` 必须输出 `hello\n`。 (4认同)
  • @EdMorton 嗯,如果你这样解释,它实际上听起来相当明显:) (2认同)