无用的 cat 命令使用?

152

这可能是很多常见问题中的一个 - 而不是使用:

cat file | command

(这被称为无用的 cat 使用),正确的方式应该是:

command < file
在第二种“正确”的方式中,操作系统不必生成额外的进程。
尽管我知道这一点,但我还是继续使用无用的cat命令有两个原因:
  1. 更美观 - 我喜欢数据均匀从左到右移动。而且更容易将cat替换为其他内容(例如:gzcatecho等),添加第二个文件或插入新的过滤器(例如:pvmbuffergrep等)。

  2. 我“感觉”在某些情况下它可能更快。更快是因为有两个进程,第一个进程(cat)进行读取,第二个进程进行其它操作。它们可以并行运行,这意味着有时执行更快。

我的逻辑正确吗(对于第二个原因)?


29
cat 是一个“标识管道”。它只是将输入流传递到输出流。如果链中的第二个程序可以从您传递给 cat 的相同参数(或者如果您没有传递参数,则可以从标准输入中获取)获取其输入,则 cat 是“绝对无用的”,只会导致创建一个额外的进程和管道。 - Frédéric Hamidi
14
cat没有参数或其参数为-时,它是一个身份管道。当它有多个非短横线文件名参数时,它就不再是一个身份管道,并开始发挥真正的作用。 - kojiro
3
曾经很流行的partmaps.org链接不幸已经失效了,现在内容可以在http://porkmail.org/era/unix/award.html找到。 - tripleee
2
我注意到,如果你想展示向右的数据流(原因1),你可以在命令前放置文件重定向符号,例如<file command1 | command2,尽管这样做可能会有美学上的争议。 - holdenweb
1
在这种情况下使用cat命令是否允许从左到右阅读,而不是从右到左。在编程中通常更常见,因此更受欢迎(当然是主观的)。 - stevec
显示剩余4条评论
9个回答

124
我直到今天才意识到这个奖项,当一位新手试图将UUOC归咎于我回答的一个问题时。它是一个cat file.txt | grep foo | cut ... | cut ...命令。我向他表达了我的看法,只有在这样做之后,我访问了他给我的链接,涉及该奖项的起源和实践。进一步搜索引导我来到了这个问题。有点不幸的是,尽管经过了深思熟虑,但没有一个答案包括我的理由。
我并不打算为自己的回答辩护。毕竟,在我年轻的时候,我会将命令写成grep foo file.txt | cut ... | cut ...,因为每当你进行频繁的单个grep时,你都会学习文件参数的放置,并且它是常识,第一个是模式,后面的是文件名。
使用cat回答问题是一个有意识的选择,部分原因是出于“好的品味”(林纳斯·托瓦兹的话),但主要是出于强有力的功能原因。
后面的原因更为重要,所以我会先提出来。当我提供管道作为解决方案时,我希望它是可重用的。很可能将管道添加到另一个管道的末尾或拼接到其中。在这种情况下,对grep使用文件参数会破坏可重用性,并且如果文件参数存在,则很可能会默默地这样做,而不会出现错误消息。即grep foo xyz | grep bar xyz | wc将给出包含barxyz中的行数,而您期望包含foobar的行数。在使用命令之前更改管道中的参数容易出错。加上潜在的静默故障可能会导致特别阴险的做法。
前面的原因也不是不重要,因为很多“好品味”只是关于像上面的静默故障的直觉潜意识理由,你不能在某个需要教育的人说“但那只猫没用”时立刻想到。
然而,我也会尝试让前面提到的“良好品味”理由变得更加明显。这个理由与Unix的正交设计精神有关。grep不会cut,ls不会grep。因此,至少grep foo file1 file2 file3违反了设计精神。正交的做法是cat file1 file2 file3 | grep foo。现在,grep foo file1仅仅是grep foo file1 file2 file3的一个特例,如果你不把它当作同样的情况来处理,你至少会浪费大脑时钟周期来避免无用的cat奖励。
这让我们得出结论,grep foo file1 file2 file3是在连接,而cat也是在连接,因此cat file1 file2 file3是正确的。但是,在cat file1 | grep foo中,cat并没有连接,因此我们违反了cat和万能的Unix的精神。如果是这样的话,Unix需要一个不同的命令来读取一个文件的输出并将其吐到stdout(不要分页或任何其他操作,只是纯粹地吐到stdout)。因此,你会遇到这种情况,你要么说cat file1 file2,要么说dog file1,并且认真记住避免cat file1以避免获奖,同时也要避免dog file1 file2,因为希望dog的设计会在指定多个文件时抛出错误。
希望此时您能够理解Unix设计者为什么没有包含一个单独的命令将文件输出到标准输出,同时将cat命名为连接而不是其他名称。 <edit> 删除了关于<的错误评论,事实上,<是一种高效的无复制设施,可将文件输出到标准输出,您可以将其定位在管道的开头,因此Unix设计者确实专门包括了这个东西 </edit> 下一个问题是为什么有些命令仅仅将文件或多个文件的串联输出到标准输出,而不进行进一步处理是很重要的?一个原因是避免每个操作标准输入的Unix命令都必须知道如何解析至少一个命令行文件参数并将其用作输入(如果存在)。第二个原因是避免用户记住:(a)文件名参数放在哪里;和(b)避免上述提到的沉默管道漏洞。
这就是为什么grep具有额外逻辑的原因。其理念是允许用户流畅地使用频繁使用和独立使用(而不是作为管道)的命令。这是对正交性的轻微妥协,以获得可用性的重大收益。并非所有命令都应该设计成这种方式,不经常使用的命令应完全避免文件参数的额外逻辑(记住额外逻辑会导致不必要的脆弱性(可能出现错误))。例外情况是允许像grep一样使用文件参数。(顺便说一下,注意ls有一个完全不同的原因,不仅接受,而且几乎需要文件参数)

最后,如果在指定文件参数时标准输入也可用,则可以更好地处理这样的特殊命令,如grep(但不一定是ls),生成错误。


70
注意,当使用grep命令同时搜索多个文件时,它会在找到的匹配行前面加上相应文件的文件名(除非你关闭该功能)。它还可以报告各个文件中匹配行的行号。如果只使用cat将内容传递给grep,则会丢失文件名,并且行号将跨越所有文件而不是单独每个文件计数。因此,有理由让grep自己处理多个文件,而这是cat无法实现的。单个文件和零个文件的情况只是grep一般多文件用法的特殊情况。 - Jonathan Leffler
49
正如kojiro在这个答案中所述,使用< file command1 ...作为管道的起点是完全可行和合法的。尽管I/O重定向运算符的传统位置是在命令名称及其参数之后,但这只是惯例,而不是强制性的放置方式。 <必须位于文件名之前。因此,在>output<input重定向之间存在接近完美的对称性:<input command1 -opt 1 | command2 -o | command3 >output - Jonathan Leffler
20
我认为人们(包括我)批评使用“UUoC”是为了教育他人。有时人们需要处理几千兆字节大小的文本文件,此时最小化管道(如使用“UUoC”,将连续的grep合并为一个,等等)至关重要,而且往往可以安全地假定问题的提出者确实不知道微小的调整可能会有巨大的性能影响。我完全同意你关于脑力负担的观点,这也是我发现自己经常使用cat的原因,即使不需要。但是很重要的一点是知道它不是必需的。 - Adrian Frühwirth
19
请理解,我并不是在说 cat 没有用处。问题不在于 cat 无用,而在于某个特定的结构不需要使用 cat。如果你愿意,可以将其称为“UUoC”(无用地使用 cat),而不是“UoUC”(使用无用的 cat)。在许多情况下,cat 是正确的工具;当它是正确的工具时,我没有任何反对它的使用(实际上,在我的回答中提到了一个案例)。 - Jonathan Leffler
7
我明白你的意思,但我认为它取决于使用情况。在命令行中使用时,根据数据的大小,管道中多一个cat可能并不是很重要,但在作为编程环境使用时,实现这些性能关键的操作可能是绝对必要的,特别是在处理bash时,性能方面就像是一个长方形的轮子(无论如何与ksh相比都慢上10倍,不开玩笑)。当处理更大的脚本或巨大的循环时,确实需要优化forks(不只是forks)的性能。 - Adrian Frühwirth
显示剩余9条评论

72

不对!

首先,在命令中重定向发生的位置并不重要。因此,如果你喜欢在命令左侧进行重定向,那也可以:

< somefile command

等同于

command < somefile

其次,当你使用管道时会发生一个子进程和n + 1个进程,这明显会使速度变慢。有些情况下n可能为零(例如,当你重定向到shell内建命令时),因此通过使用cat命令,你完全不必要地增加了一个新的进程。

总的来说,每当你发现自己在使用管道时,都值得花30秒钟查看是否可以消除它。(但可能不值得花太长时间。)以下是一些经常不必要地使用管道和进程的示例:

for word in $(cat somefile); … # for word in $(<somefile); … (or better yet, while read < somefile)

grep something | awk stuff; # awk '/something/ stuff' (similar for sed)

echo something | command; # command <<< something (although echo would be necessary for pure POSIX)

欢迎编辑以添加更多示例。


6
好的,速度增加不会太多。 - Dakkaron
13
在"command"之前放置"< somefile"技术上给了你从左到右的顺序,但这样会导致阅读不明确,因为没有语法分界符:"< cat grep dog"是一个假想的例子,它展示了你无法轻松区分输入文件、接收输入的命令以及命令的参数。 - necromancer
2
我采用的决定 STDIN 重定向位置的经验法则是尽量减少歧义和潜在的惊喜。固执地说它在前面会引起死灵法师的问题,但固执地说它在后面也可能会产生同样的问题。考虑:stdout=$(foo bar -exec baz <qux | ENV=VAR quux)。问:<qux 应用于 foo 还是 -execbaz,由 foo 执行?答案是应用于 foo,但可能会出现歧义。在这种情况下将 <qux 放在 foo 之前 更清晰,尽管不太常见,并且类似于尾随的 ENV=VAR quux - Mark G.
4
@necromancer,这里<"cat" grep dog更易读一些。(虽然我通常支持使用空格,但这个特定情况是个例外)。 - Charles Duffy
3
“它明显更慢。”如果没有数据支持,你不能这样说。我在这里提供了数据:http://oletange.blogspot.com/2013/10/useless-use-of-cat.html(数据显示只有在高通量情况下才会更慢)。请问你的数据在哪里? - Ole Tange
显示剩余2条评论

52

捍卫猫的立场:

是的,

   < input process > output 
或者
   process < input > output 

更高效,但许多调用没有性能问题,所以你不必关心。

人体工效原因:

我们习惯从左到右阅读,所以像

    cat infile | process1 | process2 > outfile

很容易理解。

    process1 < infile | process2 > outfile

必须跳过process1,然后从左到右阅读。以下方法可以解决此问题:

    < infile process1 | process2 > outfile

看起来有一只箭头指向左侧,但实际上那里什么也没有。更加混淆和看起来像花式引用的是:

    process1 > outfile < infile

生成脚本通常是一个迭代的过程,

    cat file 
    cat file | process1
    cat file | process1 | process2 
    cat file | process1 | process2 > outfile

在这里,您可以逐步查看自己的进度,同时

    < file 

甚至不起作用。简单的方法少出错且命令连结使用cat简单。

另一个话题是,大多数人在使用计算机之前长期接触过>和<作为比较运算符,而在成为程序员并使用计算机时,更经常将其作为这样的运算符使用。

使用<和>比较两个操作数是反交换的,这意味着

(a > b) == (b < a)

我记得第一次使用 < 进行输入重定向时,我感到害怕

a.sh < file 

可以意味着与之相同

file > a.sh

而且不知何故覆盖了我的a.sh脚本。也许这是许多初学者面临的问题。

罕见差异

wc -c journal.txt
15666 journal.txt
cat journal.txt | wc -c 
15666

后者可以直接用于计算。

factor $(cat journal.txt | wc -c)

当然,在这里也可以使用 < ,而不是文件参数:

< journal.txt wc -c 
15666
wc -c < journal.txt
15666
    

但是谁在意 - 1.5万?

如果我偶尔遇到问题,肯定会改变我调用cat的习惯。

当使用非常大的或很多文件时,避免使用cat是可以的。对于大多数问题,使用cat是正交的,与主题无关,不是问题。

在每个第二个shell主题上开始这些无用的cat讨论只是令人烦恼和无聊。找点事情做等待你的成功机会,当处理性能问题时。


15
作为目前被接受的答案的作者,我强烈推荐这篇令人愉悦的补充内容。具体的例子阐明了我常常抽象且啰嗦的论点,并且你从作者早期对 file > a.sh 的恐惧中获得的笑声就值得花时间阅读这篇文章 :) 感谢分享! - necromancer
3
在这个调用中 cat file | wc -cwc 需要读取标准输入直到 EOF,计算字节数。但是在这个调用中 wc -c < file,它只是统计标准输入,发现它是一个常规文件并打印 st_size 而不是读取任何输入。对于大文件,性能差异将明显可见。 - oguz ismail

32
我不同意过度自负的UUOC奖项,因为在教别人时,`cat`是任何命令或复杂管道命令的方便占位符,这些命令可以产生适合所讨论的问题或任务的输出。在像Stack Overflow、ServerFault、Unix & Linux或任何SE网站上尤其如此。如果有人专门询问优化,或者您觉得需要添加额外的信息,则可以谈论使用cat是低效的。但是,不要因为他们选择在示例中追求简单和易于理解而不是复杂性而责备人们。简而言之,因为cat并不总是cat。此外,大多数喜欢颁发UUOC的人之所以这样做,是因为他们更关心展示自己有多聪明,而不是帮助或教导别人。实际上,他们表明自己可能只是另一个找到打击同行的小棍子的新手。

更新

这是我在https://unix.stackexchange.com/a/301194/7696的回答中发布的另一个UUOC:

sqlq() {
  local filter
  filter='cat'

  # very primitive, use getopts for real option handling.
  if [ "$1" == "--delete-blank-lines" ] ; then
    filter='grep -v "^$"'
    shift
  fi

  # each arg is piped into sqlplus as a separate command
  printf "%s\n" "$@" | sqlplus -S sss/eee@sid | $filter
}

UUOC pedants 会说这是一个 UUOC,因为可以很容易地让 $filter 默认为空字符串,并让 if 语句执行 filter='| grep -v "^$"' ,但在我看来,通过不将管道字符嵌入到 $filter 中,这个“无用”的 cat 具有极其有用的自说明作用,即在 printf 行上,$filter 不仅仅是传递给 sqlplus 的另一个参数,而且是一个可选的用户可选择的输出过滤器。

如果需要多个可选的输出过滤器,则选项处理可以根据需要将 | whatever 追加到 $filter 中-管道中的一个额外的 cat 不会对任何事情造成伤害或导致任何明显的性能损失。


15
顺便提一下,在方括号内使用 == 不是 POSIX 规范定义的,而且并不是所有实现都支持它。标准化运算符只有 = - Charles Duffy

31

使用UUoC版本,cat必须将文件读入内存,然后写入管道,命令必须从管道读取数据,因此内核必须复制整个文件三次,而在重定向的情况下,内核只需复制一次文件。做一件事一次比做三次更快。

使用:

cat "$@" | command

cat的使用方法可以完全不同,也并非一定没有用处。如果命令是一个标准过滤器,可以接受零个或多个文件名参数并依次处理它们,那么这种使用方法仍然是无用的。考虑tr命令:它是一个纯过滤器,忽略或拒绝文件名参数。要将多个文件传递给它,必须像示例中使用cat。当然,还有一个单独的讨论,即tr的设计不太好;实际上它本可以被设计为标准过滤器。如果您想让命令将所有输入视为单个文件而不是多个单独的文件进行处理,即使命令可以接受多个单独的文件,这种使用方法也可能是有效的,例如wc

而仅仅是cat single-file 这种情况是绝对没有用的。


19
作为一个经常指出shell编程反模式和其他问题的人,我觉得有责任晚些时候发表一下看法。Shell脚本非常适合复制/粘贴。对于大多数编写shell脚本的人来说,他们并不是为了学习语言而写脚本;它只是他们必须克服的障碍,以便继续使用他们实际上比较熟悉的语言。在这种情况下,我认为传播各种shell脚本反模式可能会造成破坏甚至破坏性。人们在Stack Overflow上找到的代码理想情况下应该可以最小限度地更改并复制/粘贴到他们的环境中,而无需完全理解。在网络上的许多shell脚本资源中,Stack Overflow是不同寻常的,因为用户可以通过编辑站点上的问题和答案来帮助塑造站点的质量。但是,代码编辑可能存在问题,因为很容易进行未经代码作者批准的更改。因此,我们倾向于留下评论以建议对代码进行更改。UUCA和相关的反模式注释不仅适用于我们评论的代码的作者;它们同样也是购买者注意,以帮助读者了解他们在此处找到的代码中存在的问题。
我们无法指望在Stack Overflow上没有任何答案是推荐使用无用的cat(或未引用的变量、chmod 777或其他各种反模式),但至少我们可以帮助教育那些将此代码复制/粘贴到执行数百万次的最内部紧密循环的用户。

就技术原因而言,传统智慧认为我们应该尽量减少外部进程的数量;当编写shell脚本时,这仍然是一个很好的通用指导。


2
同时,对于大文件而言,通过cat进行管道传输会增加很多上下文切换和内存带宽(并在cat的读取缓冲区以及管道缓冲区中产生额外的数据副本,从而污染L3高速缓存)。特别是在大型多核机器上(比如许多主机设备),缓存/内存带宽是共享资源。 - Peter Cordes
1
@PeterCordes请发布您的测量结果,这样我们才能看看它在实践中是否真的很重要。我的经验是,通常情况下并不重要:http://oletange.blogspot.com/2013/10/useless-use-of-cat.html - Ole Tange
2
你自己的博客显示高吞吐量时减速了50%,而且你甚至没有考虑总吞吐量的影响(如果有其他核心保持繁忙状态)。如果我有时间,我可能会在x264或x265使用所有核心编码视频时运行你的测试,并查看它会减慢视频编码多少。与“cat”单独使用(机器空闲时)相比,“bzip2”和“gzip”压缩都非常慢。很难阅读你的表格(数字中间换行了?)。系统时间增加了很多,但仍然比用户或实际时间小? - Peter Cordes

18

另一个问题是管道可能会默默地屏蔽子shell的输出。在这个例子中,我会用echo代替cat,但同样的问题仍然存在。

echo "foo" | while read line; do
    x=$line
done

echo "$x"
你可能期望变量x包含foo,但实际上它并不包含。你所设置的x位于产生while循环的子shell中。在开始管道的shell中,x有一个无关的值或者根本没有被设置。
在bash4中,你可以配置一些shell选项,使得管道的最后一个命令在与开始管道的shell相同的shell中执行,但是这时你可能会尝试这样做:
echo "foo" | while read line; do
    x=$line
done | awk '...'

此时x再次成为while子shell内的局部变量。


5
在严格的POSIX shell中,这可能是一个棘手的问题,因为你没有here strings或者进程替换来避免使用管道。即使在这种情况下,BashFAQ 24也提供了一些有用的解决方案。 - kojiro
4
在某些shell中,所示的管道符不会创建子shell。例如Korn和Z shell。它们还支持进程替换和here strings语法。当然,它们并非严格遵循POSIX标准。Bash 4提供了shopt -s lastpipe选项来避免创建子shell。 - Dennis Williamson

16

在示例中,我经常使用cat file | myprogram。有时候,我会被指责使用了无用的cat命令 (http://porkmail.org/era/unix/award.html)。但我不同意这种说法,理由如下:

  • 这样更容易理解。

    当阅读UNIX命令时,您期望看到一个命令,后面跟着参数,再后面是重定向。虽然可以把重定向放在任何位置,但很少见——因此人们会更难以阅读示例。我认为

cat foo | program1 -o option -b option | program2
比起其他选项,阅读起来更容易。
program1 -o option -b option < foo | program2
如果您将重定向放在开头,会让不熟悉这种语法的人感到困惑。
< foo program1 -o option -b option | program2

示例应易于理解。

  • 它很容易更改。

    如果您知道该程序可以从 cat 读取内容,则通常可以假定它可以读取任何输出到 STDOUT 的程序的输出,因此您可以根据自己的需要进行适应并获得可预测的结果。

  • 强调该程序不会失败,即使 STDIN 不是文件。

    不能安全地假设如果 program1 < foo 成功,则 cat foo | program1 也将成功。然而,可以安全地假设相反。该程序仅在 STDIN 是文件时才有效,但是如果输入是管道,则会失败,因为它使用了 seek:

  • # works
    < foo perl -e 'seek(STDIN,1,1) || die;print <STDIN>'
    
    # fails
    cat foo | perl -e 'seek(STDIN,1,1) || die;print <STDIN>'
    

    性能成本

    进行额外的cat操作会带来一定的成本。为了让大家了解成本的大小,我进行了几项测试来模拟基准(cat),低吞吐量(bzip2),中等吞吐量(gzip)和高吞吐量(grep)。

    cat $ISO | cat
    < $ISO cat
    cat $ISO | bzip2
    < $ISO | bzip2
    cat $ISO | gzip
    < $ISO gzip
    cat $ISO | grep no_such_string
    < $ISO grep no_such_string
    

    测试运行在一台低端系统(0.6 GHz)和一台普通笔记本电脑(2.2 GHz)上。每个系统运行了10次测试,并选择最佳时间来模拟每个测试的最佳情况。$ISO是ubuntu-11.04-desktop-i386.iso。 (更美观的表格在这里:http://oletange.blogspot.com/2013/10/useless-use-of-cat.html

    CPU                       0.6 GHz ARM
    Command                   cat $ISO|                        <$ISO                            Diff                             Diff (pct)
    Throughput \ Time (ms)    User       Sys        Real       User       Sys        Real       User       Sys        Real       User       Sys        Real
    Baseline (cat)                     55      14453      33090         23       6937      33126         32       7516        -36        239        208         99
    Low (bzip2)                   1945148      16094    1973754    1941727       5664    1959982       3420      10430      13772        100        284        100
    Medium (gzip)                  413914      13383     431812     407016       5477     416760       6898       7906      15052        101        244        103
    High (grep no_such_string)      80656      15133      99049      79180       4336      86885       1476      10797      12164        101        349        114
    
    CPU                       Core i7 2.2 GHz
    Command                   cat $ISO|           <$ISO             Diff          Diff (pct)
    Throughput \ Time (ms)    User     Sys Real   User   Sys Real   User Sys Real User       Sys Real
    Baseline (cat)                    0 356    215      1  84     88    0 272  127          0 423  244
    Low (bzip2)                  136184 896 136765 136728 160 137131 -545 736 -366         99 560   99
    Medium (gzip)                 26564 788  26791  26332 108  26492  232 680  298        100 729  101
    High (grep no_such_string)      264 392    483    216  84    304   48 308  179        122 466  158
    

    结果表明,对于低和中等吞吐量,成本大约为1%。这远远在测量的不确定性范围内,因此在实践中没有区别。

    对于高吞吐量,差异更大,两者之间存在明显差异。

    由此得出结论:如果:

    • 处理的复杂性类似于简单的grep
    • 性能比可读性更重要。

    那么你应该使用<而不是cat |

    否则,无论您使用<还是cat |都没有关系。

    因此,只有当:

    • 您可以测量到性能上的显着差异(在授予奖项时发布您的测量结果)
    • 性能比可读性更重要。

    您才应该授予UUoC奖项。


    -3

    我认为(传统方式)使用管道会更快一些;在我的电脑上,我使用 strace 命令来查看发生了什么:

    不使用管道:

    toc@UnixServer:~$ strace wc -l < wrong_output.c
    execve("/usr/bin/wc", ["wc", "-l"], [/* 18 vars */]) = 0
    brk(0)                                  = 0x8b50000
    access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
    mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77ad000
    access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
    open("/etc/ld.so.cache", O_RDONLY)      = 3
    fstat64(3, {st_mode=S_IFREG|0644, st_size=29107, ...}) = 0
    mmap2(NULL, 29107, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb77a5000
    close(3)                                = 0
    access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
    open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3
    read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0p\222\1\0004\0\0\0"..., 512) = 512
    fstat64(3, {st_mode=S_IFREG|0755, st_size=1552584, ...}) = 0
    mmap2(NULL, 1563160, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7627000
    mmap2(0xb779f000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x178) = 0xb779f000
    mmap2(0xb77a2000, 10776, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb77a2000
    close(3)                                = 0
    mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7626000
    set_thread_area({entry_number:-1 -> 6, base_addr:0xb76268d0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
    mprotect(0xb779f000, 8192, PROT_READ)   = 0
    mprotect(0x804f000, 4096, PROT_READ)    = 0
    mprotect(0xb77ce000, 4096, PROT_READ)   = 0
    munmap(0xb77a5000, 29107)               = 0
    brk(0)                                  = 0x8b50000
    brk(0x8b71000)                          = 0x8b71000
    open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE) = 3
    fstat64(3, {st_mode=S_IFREG|0644, st_size=5540198, ...}) = 0
    mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7426000
    mmap2(NULL, 1507328, PROT_READ, MAP_PRIVATE, 3, 0x2a8) = 0xb72b6000
    close(3)                                = 0
    open("/usr/share/locale/locale.alias", O_RDONLY) = 3
    fstat64(3, {st_mode=S_IFREG|0644, st_size=2570, ...}) = 0
    mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77ac000
    read(3, "# Locale name alias data base.\n#"..., 4096) = 2570
    read(3, "", 4096)                       = 0
    close(3)                                = 0
    munmap(0xb77ac000, 4096)                = 0
    open("/usr/share/locale/fr_FR.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
    open("/usr/share/locale/fr_FR.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
    open("/usr/share/locale/fr_FR/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
    open("/usr/share/locale/fr.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
    open("/usr/share/locale/fr.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
    open("/usr/share/locale/fr/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
    open("/usr/share/locale-langpack/fr_FR.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
    open("/usr/share/locale-langpack/fr_FR.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
    open("/usr/share/locale-langpack/fr_FR/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
    open("/usr/share/locale-langpack/fr.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
    open("/usr/share/locale-langpack/fr.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
    open("/usr/share/locale-langpack/fr/LC_MESSAGES/coreutils.mo", O_RDONLY) = 3
    fstat64(3, {st_mode=S_IFREG|0644, st_size=316721, ...}) = 0
    mmap2(NULL, 316721, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7268000
    close(3)                                = 0
    open("/usr/lib/i386-linux-gnu/gconv/gconv-modules.cache", O_RDONLY) = 3
    fstat64(3, {st_mode=S_IFREG|0644, st_size=26064, ...}) = 0
    mmap2(NULL, 26064, PROT_READ, MAP_SHARED, 3, 0) = 0xb7261000
    close(3)                                = 0
    read(0, "#include<stdio.h>\n\nint main(int "..., 16384) = 180
    read(0, "", 16384)                      = 0
    fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
    mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7260000
    write(1, "13\n", 313
    )                     = 3
    close(0)                                = 0
    close(1)                                = 0
    munmap(0xb7260000, 4096)                = 0
    close(2)                                = 0
    exit_group(0)                           = ?
    

    使用管道:

    toc@UnixServer:~$ strace cat wrong_output.c | wc -l
    execve("/bin/cat", ["cat", "wrong_output.c"], [/* 18 vars */]) = 0
    brk(0)                                  = 0xa017000
    access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
    mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb774b000
    access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
    open("/etc/ld.so.cache", O_RDONLY)      = 3
    fstat64(3, {st_mode=S_IFREG|0644, st_size=29107, ...}) = 0
    mmap2(NULL, 29107, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7743000
    close(3)                                = 0
    access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
    open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3
    read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0p\222\1\0004\0\0\0"..., 512) = 512
    fstat64(3, {st_mode=S_IFREG|0755, st_size=1552584, ...}) = 0
    mmap2(NULL, 1563160, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb75c5000
    mmap2(0xb773d000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x178) = 0xb773d000
    mmap2(0xb7740000, 10776, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7740000
    close(3)                                = 0
    mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb75c4000
    set_thread_area({entry_number:-1 -> 6, base_addr:0xb75c48d0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
    mprotect(0xb773d000, 8192, PROT_READ)   = 0
    mprotect(0x8051000, 4096, PROT_READ)    = 0
    mprotect(0xb776c000, 4096, PROT_READ)   = 0
    munmap(0xb7743000, 29107)               = 0
    brk(0)                                  = 0xa017000
    brk(0xa038000)                          = 0xa038000
    open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE) = 3
    fstat64(3, {st_mode=S_IFREG|0644, st_size=5540198, ...}) = 0
    mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb73c4000
    mmap2(NULL, 1507328, PROT_READ, MAP_PRIVATE, 3, 0x2a8) = 0xb7254000
    close(3)                                = 0
    fstat64(1, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0
    open("wrong_output.c", O_RDONLY|O_LARGEFILE) = 3
    fstat64(3, {st_mode=S_IFREG|0664, st_size=180, ...}) = 0
    read(3, "#include<stdio.h>\n\nint main(int "..., 32768) = 180
    write(1, "#include<stdio.h>\n\nint main(int "..., 180) = 180
    read(3, "", 32768)                      = 0
    close(3)                                = 0
    close(1)                                = 0
    close(2)                                = 0
    exit_group(0)                           = ?
    13
    

    你可以使用stracetime命令进行一些测试,使用更多和更长的命令来进行良好的基准测试。


    11
    我不理解你所说的“使用管道的传统方式”,也不明白为什么你认为这个strace表明它更快 - 这个strace并没有追踪第二个命令wc -l的执行。它只在这里跟踪了管道的第一个命令。 - kojiro
    @kojiro:我所说的传统方式是指最常用的方式(我认为我们使用管道比间接寻址更多),我无法确认它是否更快,但在我的跟踪中,我看到了更多的间接系统调用。您可以使用C程序和循环来查看哪个需要更多时间。如果您感兴趣,我们可以在这里展示它 :) - TOC
    3
    一个苹果对苹果的比较将会把 strace -f sh -c 'wc -l < wrong_output.c'strace -f sh -c 'cat wrong_output.c | wc -l' 放在一起进行比较。 - Charles Duffy
    5
    以下是来自ideone.com的结果,目前明显支持不使用cat:https://ideone.com/2w1W42#stderr - tripleee
    3
    @CharlesDuffy: mkfifo 创建的是一种命名管道。使用 pipe(2) 可以设置匿名管道,然后进行 fork 操作,让父进程和子进程关闭管道的不同端口。但是,是的,这个答案是完全没有意义的,甚至没有尝试计算系统调用数量或使用 strace -O 来测量开销,或者使用 -r 以相对于上一次时间戳化每个调用... - Peter Cordes
    显示剩余2条评论

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接