为什么 ls 命令在 tty 上打印多列,但在其他地方只打印一列?

为什么 ls 命令在 tty 上打印多列,但在其他地方只打印一列?

即使使用类 Unix 操作系统几年后,这种行为仍然让我感到困惑。

当我在包含大量文件的目录中使用 ls 命令时,输出通常会被很好地格式化为多列。以下是示例:

$ ls
a.txt  C.txt  f.txt  H.txt  k.txt  M.txt  p.txt  R.txt  u.txt  W.txt  z.txt
A.txt  d.txt  F.txt  i.txt  K.txt  n.txt  P.txt  s.txt  U.txt  x.txt  Z.txt
b.txt  D.txt  g.txt  I.txt  l.txt  N.txt  q.txt  S.txt  v.txt  X.txt
B.txt  e.txt  G.txt  j.txt  L.txt  o.txt  Q.txt  t.txt  V.txt  y.txt
c.txt  E.txt  h.txt  J.txt  m.txt  O.txt  r.txt  T.txt  w.txt  Y.txt

但是,如果我尝试将输出重定向到文件或将其通过管道传输到另一个命令,则输出中只会显示一列。使用与上面相同的示例目录,当我将 ls 通过管道传输到 wc 时,我得到的结果如下:

$ ls | wc
     52      52     312

换句话说,wc 认为有 52 行,尽管输出到终端的只有 5 行。

我还没有观察到这种行为任何其他命令。你愿意给我解释一下吗?

答案1

它只是检测其输出是否是终端设备,并格式化输出以使其看起来更好。您可以使用选项 -C 将相同格式的 ls 输出打印到文件或管道中。

如何检测输出是否为终端:https://stackoverflow.com/questions/1061575/detect-in-c-if-outputting-to-a-terminal

答案2

当程序的输出提供了某种列表,并且您将该程序的输出重定向到文件,或通过管道传输到另一个程序时,通常第二个程序的目的是循环读取原始程序输出(或文件),一次读取一行。

在这种情况下,读取的每一行包含一个项目(1 个文件名)或 1 个有关单个项目的信息“记录”,而不是必须分开的多个不同项目,这几乎总是更方便。

对于可以包含空格的文件名尤其如此,因为空格也是分隔每个项目(文件名)的分隔符。

例如 ...

首先,我创建一些文件:

% touch "300"
% cp "300" "301"
% cp "300" "302  304"
% cp "300" "303  305"
% cp "300" "306"
% cp "300" "307"
% cp "300" "308"
% cp "300" "309"
% cp "300" "310  312"
% cp "300" "311  313"
% cp "300" "314  316"
% cp "300" "315  317"
% cp "300" "318"
% cp "300" "319"
% cp "300" "320  322"
% cp "300" "321  323"
% cp "300" "324"
% cp "300" "325"
% cp "300" "bar  bin"
% cp "300" "baz  boo"
% cp "300" "def"
% cp "300" "etc"
% cp "300" "foo  mos"
%

现在,我得到了我创建的文件的“长”列表:

% ls -l
-rw-r--r-- 1 username username 0 Nov 28 01:51 300
-rw-r--r-- 1 username username 0 Nov 28 01:51 301
-rw-r--r-- 1 username username 0 Nov 28 01:51 302  304
-rw-r--r-- 1 username username 0 Nov 28 01:51 303  305
-rw-r--r-- 1 username username 0 Nov 28 01:51 306
-rw-r--r-- 1 username username 0 Nov 28 01:51 307
-rw-r--r-- 1 username username 0 Nov 28 01:51 308
-rw-r--r-- 1 username username 0 Nov 28 01:51 309
-rw-r--r-- 1 username username 0 Nov 28 01:51 310  312
-rw-r--r-- 1 username username 0 Nov 28 01:51 311  313
-rw-r--r-- 1 username username 0 Nov 28 01:51 314  316
-rw-r--r-- 1 username username 0 Nov 28 01:51 315  317
-rw-r--r-- 1 username username 0 Nov 28 01:51 318
-rw-r--r-- 1 username username 0 Nov 28 01:51 319
-rw-r--r-- 1 username username 0 Nov 28 01:51 320  322
-rw-r--r-- 1 username username 0 Nov 28 01:51 321  323
-rw-r--r-- 1 username username 0 Nov 28 01:51 324
-rw-r--r-- 1 username username 0 Nov 28 01:51 325
-rw-r--r-- 1 username username 0 Nov 28 01:51 bar  bin
-rw-r--r-- 1 username username 0 Nov 28 01:51 baz  boo
-rw-r--r-- 1 username username 0 Nov 28 01:51 def
-rw-r--r-- 1 username username 0 Nov 28 01:51 etc
-rw-r--r-- 1 username username 0 Nov 28 01:51 foo  mos
% 

好的,文件名是什么就很明显了。

现在,我得到了按列列出的文件列表:

% ls
300  302  304  306  308  310  312  314  316  318  320  322  324  bar  bin  def  foo  mos
301  303  305  307  309  311  313  315  317  319  321  323  325  baz  boo  etc
% 

如您所见,读取输出的程序(甚至是人类观察者)无法挑选出文件的正确名称,并且可能会认为这是一个包含 3 个字符的文件名的长列表。

如果我们将 ls 的输出导入到 more 中,我们会得到:

% ls | more
300
301
302  304
303  305
306
307
308
309
310  312
311  313
314  316
315  317
318
319
320  322
321  323
324
325
bar  bin
baz  boo
def
etc
foo  mos
% 

虽然不是很漂亮,但现在选择正确的文件名变得容易得多,因为每个文件名都在自己的行上。

答案3

实际上发生的事情是终端格式化了 给出的输出ls。 ls 命令的实际输出如下:

hussain@ai:~/test$ ls # The terminal formats this output by itself
text1   text11  text13  text15  text17  text19  text20  text22  text24  text26  text28  text3   text4  text6  text8
text10  text12  text14  text16  text18  text2   text21  text23  text25  text27  text29  text30  text5  text7  text9

现在,如果我将其通过管道传递给less命令,则会显示如下内容:

hussain@ai:~/test$ ls | less # This means that the actual output is the below
text1
text10
text11
text12
text13
text14
text15
text16
text17
text18
...
...
...

因此,这意味着如果你将相同的命令提供给wc任何其他程序,上面给出的输出将是它将工作的输出(即不是花哨的终端输出)。但是,如果你想要将那个花哨的输出传递给,你可以使用-C带有ls命令的标志,例如@piokuc 的答案中给出的

除此之外,如果您想查看发送到下一个命令的输出(不使用管道“ |”符号,您可以使用-w--width标志。

这就是它们之间的区别。

ai:test hussain$ ls # The terminal formats this output by itself as it can be seen in Kevin Fegan's Answer
text1   text11  text13  text15  text17  text19  text20  text22  text24  text26  text28  text3   text4  text6  text8
text10  text12  text14  text16  text18  text2   text21  text23  text25  text27  text29  text30  text5  text7  text9
ai:test hussain$ ls -w0 # width 0 is the default behavior. it comes preformatted to the terminal 
text1  text10  text11  text12  text13  text14  text15  text16  text17  text18  text19  text2  text20  text21  text22  text23  text24  text25  text26  text27  text28  text29  text3  text30  text4  text5  text6  text7  text8  text9

不要-w0在脚本中使用对文件执行操作。而是使用*( for file_and_dir in *; do ...)

ai:test hussain$ ls -w1 # The output comes preformatted but in a new line. It is just like you force to see it in this way.
text1
text10
text11
text12
text13
text14
...
...
...

相关内容