bash 重定向到文件会添加意外的 0A 字节

bash redirection to file adds unexpected 0A bytes

我认为,如果我将 ls 的输出重定向到一个文件,那么将以其他方式发送到控制台的完全相同的字符序列将写入该文件.

为了测试这一点,我创建了 3 个文件,然后列出它们

$ touch a b c
$ ls
a  b  c

我现在再次 运行 ls,这次重定向到我 cat

的文件
$ ls > out
$ cat out
a
b
c
out

出乎意料的是,out中的每个文件名之间都有一个0A换行符

$ xxd out
00000000: 610a 620a 630a 6f75 740a                 a.b.c.out.

将 ls 的输出通过管道传输到 xxd

$ ls | xxd
00000000: 610a 620a 630a 6f75 740a                 a.b.c.out.

换行符仍然存在。

0A 字节是如何到达那里的?如果 ls 被重定向或者 shell 在某些情况下忽略换行符,它的行为是否会有所不同?

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.3 LTS
Release:    20.04
Codename:   focal

是的,ls 如果被重定向,行为会有所不同。您可以使用 -x:

获得您期望的输出
$ mkdir /tmp/t
$ cd /tmp/t
$ touch a b c
$ ls | cat
a
b
c
$ ls -x | cat
a b c
$ ls --format=single-column
a
b
c

@GordonDavisson 将我们指向 POSIX spec for ls,上面写着

The default format shall be to list one entry per line to standard output; the exceptions are to terminals or when one of the -C, -m, or -x options is specified. If the output is to a terminal, the format is implementation-defined.

因此,无论如何在POSIX中,它是'norm'的逐行输出;终端输出可以是任何东西(尽管我从未见过除了 space-separation 之外的任何东西)。据推测,这是为了可以逐行迭代响应。我也从来没有注意过,虽然很多次依赖它现在我开始考虑它!

实施

这是一个 ls 实现的源代码,明确检查:

    case LS_LS:
      /* This is for the `ls' program.  */
      if (isatty (STDOUT_FILENO))
        {
          format = many_per_line;
          /* See description of qmark_funny_chars, above.  */
          qmark_funny_chars = true;
        }
      else
        {
          format = one_per_line;
          qmark_funny_chars = false;
        }
      break;

source

或者在当前的gnu coreutils中:

  format = (0 <= format_opt ? format_opt
            : ls_mode == LS_LS ? (stdout_isatty ()
                                  ? many_per_line : one_per_line)
            : ls_mode == LS_MULTI_COL ? many_per_line
            : /* ls_mode == LS_LONG_FORMAT */ long_format);

其中 stdout_isatty 的定义与前面的示例相同。

source