关于shell:无用的猫?

Useless use of cat?

这可能是许多常见问题解答 - 而不是使用:

1
cat file | command

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

1
command < file

在第二,"正确"的方式 - 操作系统不必产生额外的过程。
尽管知道这一点,我继续使用无用的猫有两个原因。

  • 更美观 - 我喜欢数据仅从左到右均匀移动。 并且更容易用其他东西(gzcatecho,...)替换cat,添加第二个文件或插入新过滤器(pvmbuffergrep ...)。

  • 我"觉得"在某些情况下可能会更快。 更快,因为有2个进程,第一个(cat)执行读取而第二个执行任何操作。 它们可以并行运行,这意味着有时可以更快地执行。

  • 我的逻辑是否正确(第二个原因)?


    直到今天我还没有意识到这个奖项,当时有一个新手试图将UUOC钉在我身上,以获得我的一个答案。那是cat file.txt | grep foo | cut ... | cut ...。我给了他一个想法,只有在这样做之后,我才能看到他给我的链接,指的是奖项的起源和这样做的实践。进一步的搜索引导我这个问题。有点不幸的是,尽管有意识地考虑,但没有一个答案包括我的理由。

    我并不打算在回应他时采取防御措施。毕竟,在我年轻的时候,我会把命令编写为grep foo file.txt | cut ... | cut ...,因为无论何时你经常使用单个grep,你都会学习文件参数的位置,并且知道第一个是模式和后来的是文件名。

    当我回答问题时,使用cat是一种有意识的选择,部分是因为"良好品味"的原因(用Linus Torvalds的话说),但主要是出于功能的令人信服的理由。

    后一个原因更重要,所以我会先说出来。当我提供管道作为解决方案时,我希望它可以重复使用。管道很可能会在最后添加或拼接到另一个管道中。在这种情况下,如果文件参数存在,那么grep的文件参数会破坏可重用性,并且很可能在没有错误消息的情况下静默执行。 I. e。 grep foo xyz | grep bar xyz | wc将为您提供xyz中包含bar的行数,同时期望包含foobar的行数。在使用之前必须在管道中更改参数的参数很容易出错。除此之外,它还存在无声失败的可能性,并成为一种特别阴险的做法。

    由于很多"好味道",前一个原因并不重要
    对于诸如上面的沉默失败之类的东西来说,仅仅是一个直观的潜意识理论,当一些需要教育的人说"但不是那只猫没用"时,你无法想到这一点。

    但是,我会尽量让我意识到前面提到的"好味道"的原因。这个原因与Unix的正交设计精神有关。 grepcutlsgrep。因此,至少grep foo file1 file2 file3违背了设计精神。正交方式是cat file1 file2 file3 | grep foo。现在,grep foo file1仅仅是grep foo file1 file2 file3的一个特例,如果你没有对它进行相同处理,你至少会耗尽大脑时钟周期试图避免无用的猫奖励。

    这导致我们认为grep foo file1 file2 file3是连接的,并且cat连接所以它适用于cat file1 file2 file3但是因为cat没有连接在cat file1 | grep foo因此我们违反了cat的精神和全能的Unix。那么,如果是这种情况那么Unix就需要一个不同的命令来读取一个文件的输出并将其吐出到stdout(不是将它分页或者只是纯粹的spit到stdout)。所以你会遇到这样的情况你说cat file1 file2或你说dog file1并且认真记得避免cat file1以避免获得奖励,同时也避免dog file1 file2,因为希望dog的设计会引发错误如果指定了多个文件。

    希望,在这一点上,你同情Unix设计者没有包含一个单独的命令来将文件吐出到stdout,同时也命名cat用于连接而不是给它一些其他名称。 删除<上的错误注释,实际上,<是一个有效的无复制工具,可以将文件吐出到stdout,您可以将其放置在管道的开头,这样Unix设计人员就可以为此特别添加一些内容

    接下来的问题是,为什么在没有任何进一步处理的情况下将命令仅仅吐出文件或将多个文件串联到stdout很重要?一个原因是避免让每个运行在标准输入上的Unix命令知道如何解析至少一个命令行文件参数,并将其用作输入(如果存在)。第二个原因是避免用户必须记住:(a)文件名参数的去向; (b)避免如上所述的无声管道错误。

    这让我们知道为什么grep确实有额外的逻辑。其基本原理是允许用户流畅地使用频繁且独立使用的命令(而不是作为管道)。对于可用性的显着增加,这是对正交性的轻微折衷。并非所有命令都应该以这种方式设计,并且不经常使用的命令应该完全避免文件参数的额外逻辑(记住额外的逻辑会导致不必要的脆弱性(bug的可能性))。例外是允许文件参数,例如grep。 (顺便说一句,请注意ls有一个完全不同的理由,不仅要接受,而且几乎需要文件参数)

    最后,如果在指定文件参数时标准输入也可用,那么如果grep(但不一定是ls)这样的异常命令会产生错误,那么可以做得更好的是。

    好。


    不!

    首先,重定向发生在命令的哪个位置并不重要。所以如果你喜欢你的命令左边的重定向,那很好:

    1
    < somefile command

    是相同的

    1
    command < somefile

    其次,使用管道时会发生n + 1个进程和子shell。它最明显更慢。在某些情况下,n将为零(例如,当您重定向到内置的shell时),因此通过使用cat,您将完全不必要地添加新进程。

    作为概括,每当你发现自己使用管道时,值得花30秒时间来看看你是否可以消除它。 (但可能不值得花费超过30秒。)以下是一些不必要地经常使用管道和流程的示例:

    1
    2
    3
    4
    5
    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)

    随意编辑以添加更多示例。


    我不同意过度自负的UUOC奖的大多数情况,因为在教别人时,cat是任何命令或硬件复杂的命令管道的方便占位符,其产生适合于所讨论的问题或任务的输出。

    在Stack Overflow,ServerFault,Unix和Linux或任何SE站点等网站上尤其如此。

    如果某人专门询问优化,或者您想添加有关它的额外信息,那么,很好,请谈谈如何使用cat效率低下。但是不要因为他们选择以他们的例子中的简单性和易于理解为目标而不是看我这么酷的时候来谴责别人!复杂。

    总之,因为猫并不总是猫。

    另外,因为大多数喜欢四处寻找UUOC的人都是这样做的,因为他们更关心的是炫耀他们的"聪明"程度,而不是帮助或教导人们。实际上,他们证明了他们可能只是另一个新手,他们找到了一个小棒来击败同龄人。

    更新

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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
    ""$@" | sqlplus -S sss/eee@sid | $filter
    }

    UUOC的学生会说这是一个UUOC,因为很容易将$filter默认为空字符串并且让if语句执行filter='| grep -v"^$"'但是IMO,通过不在$filter中嵌入管道字符,这个"没用""cat提供了非常有用的目的,即自我记录printf行上的$filter不仅是sqlplus的另一个参数,它是一个可选的用户可选输出过滤器。

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


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

    使用:

    1
    cat"$@" | command

    cat是完全不同的,不一定无用的。如果命令是接受零个或多个文件名参数的标准过滤器并依次处理它们,那么它仍然是无用的。考虑tr命令:它是一个忽略或拒绝文件名参数的纯过滤器。要向其提供多个文件,您必须使用cat,如图所示。 (当然,有一个单独的讨论,tr的设计不是很好;没有真正的理由它不能被设计为标准过滤器。)如果你想让命令对待所有的,这也可能是有效的。输入为单个文件而不是多个单独的文件,即使该命令可以接受多个单独的文件:例如,wc就是这样的命令。

    cat single-file案件无条件无用。


    为了防御猫:

    是,

    1
       < input process > output

    要么

    1
       process < input > output

    效率更高,但许多调用没有性能问题,所以你不在乎。

    人体工程学原因:

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

    1
        cat infile | process1 | process2 > outfile

    理解是微不足道的。

    1
        process1 < infile | process2 > outfile

    必须跳过process1,然后从左到右阅读。这可以通过以下方式治愈:

    1
        < infile process1 | process2 > outfile

    看起来不知何故,好像有一个箭头指向左边,没有任何东西。更令人困惑,看起来像花哨的引用是:

    1
        process1 > outfile < infile

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

    1
    2
    3
    4
        cat file
        cat file | process1
        cat file | process1 | process2
        cat file | process1 | process2 > outfile

    你在哪里逐步看到你的进步

    1
        < file

    甚至不起作用。简单的方法不易出错,符合人体工程学的命令连接对于cat来说很简单。

    另一个话题是,大多数人在使用计算机之前和作为程序员使用计算机之前,都接触过>和<作为比较运算符,因此更多地接触到这些。

    并且将两个操作数与<和>进行比较是对比交换,这意味着

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

    我记得第一次使用<输入重定向时,我担心

    1
    a.sh < file

    可能意味着相同

    1
    file > a.sh

    并以某种方式覆盖我的a.sh脚本。对许多初学者来说,这可能是一个问题。

    罕见的差异

    1
    2
    3
    4
    wc -c journal.txt
    15666 journal.txt
    cat journal.txt | wc -c
    15666

    后者可以直接用于计算。

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

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

    1
    2
    3
    4
    < journal.txt wc -c
    15666
    wc -c < journal.txt
    15666

    但是谁在乎 - 15k?

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

    当使用非常大或许多文件时,避免使用cat就可以了。对于大多数问题,猫的使用是正交的,偏离主题,而不是问题。

    在每个第二个shell主题上开始这些无用的无用的cat讨论只会令人烦恼和无聊。在处理表演问题时,获得生命,等待一分钟的成名。


    另一个问题是管道可以静默掩盖子壳。对于此示例,我将cat替换为echo,但存在同样的问题。

    1
    2
    3
    4
    5
    echo"foo" | while read line; do
        x=$line
    done

    echo"$x"

    您可能希望x包含foo,但事实并非如此。您设置的x是在子shell中生成的,用于执行while循环。启动管道的shell中的x具有不相关的值,或者根本没有设置。

    在bash4中,您可以配置一些shell选项,以便管道的最后一个命令在与启动管道的shell相同的shell中执行,但是您可以尝试这个

    1
    2
    3
    echo"foo" | while read line; do
        x=$line
    done | awk '...'

    并且x再次位于while的子shell中。


    作为经常指出这个以及其他一些shell编程反模式的人,我觉得有必要,姗姗来迟,权衡。

    Shell脚本是一种复制/粘贴语言。对于大多数编写shell脚本的人来说,他们不是在学习语言;这只是他们必须克服的一个障碍,以便继续用他们实际上熟悉的语言来做事。

    在这种情况下,我认为传播各种shell脚本反模式具有破坏性,甚至可能具有破坏性。理想情况下,有人在Stack Overflow上找到的代码可以通过最少的更改和不完整的理解来复制/粘贴到他们的环境中。

    在网络上的众多shell脚本资源中,Stack Overflow很不寻常,因为用户可以通过编辑网站上的问题和答案来帮助塑造网站的质量。但是,代码编辑可能会有问题,因为很容易进行代码作者不想要的更改。因此,我们倾向于留下评论来建议对代码的更改。

    UUCA和相关的反模式评论不仅适用于我们评论的代码的作者;他们同样是一个警告,可以帮助网站的读者了解他们在这里找到的代码中的问题。

    我们无法希望实现Stack Overflow上没有答案推荐无用的cat(或未引用的变量,或chmod 777,或其他各种反模式瘟疫)的情况,但我们至少可以帮助教育用户谁将要将此代码复制/粘贴到其脚本的最内部紧密循环中,该循环执行数百万次。

    就技术原因而言,传统观点认为我们应尽量减少外部流程的数量;在编写shell脚本时,这仍然是一个很好的一般指导。


    我经常在示例中使用cat file | myprogram。有时我被指控无用猫(http://porkmail.org/era/unix/award.html)。我不同意以下原因:

    • 很容易理解发生了什么。

      在读取UNIX命令时,您需要一个命令,后跟参数,然后是重定向。可以将重定向放在任何地方但很少见 - 因此人们将更难以阅读该示例。我相信

      1
      cat foo | program1 -o option -b option | program2

      比阅读更容易阅读

      1
      program1 -o option -b option < foo | program2

      如果将重定向移至开头,则会让那些不熟悉此语法的人感到困惑:

      1
      < foo program1 -o option -b option | program2

      示例应该易于理解。

    • 它很容易改变。

      如果您知道程序可以从cat读取,您通常可以假设它可以读取输出到STDOUT的任何程序的输出,因此您可以根据自己的需要调整它并获得可预测的结果。

    • 它强调如果STDIN不是文件,程序不会失败。

      假设如果program1 < foo起作用,那么cat foo | program1也将起作用是不安全的。但是,假设相反是安全的。如果STDIN是文件,则此程序有效,但如果输入是管道,则该程序失败,因为它使用seek:

      1
      2
      3
      4
      5
      # 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)。

    1
    2
    3
    4
    5
    6
    7
    8
    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)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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%。这完全在测量的不确定性范围内,因此在实践中没有区别。

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

    这导致了结论:如果出现以下情况,则应使用<而不是cat |

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

    否则,使用<cat |无关紧要。

    因此,您应该仅在以下情况下颁发UUoC奖励:

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

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

    没有管道:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    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.
    #"..., 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>

    int 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
    ", 313
    )                     = 3
    close(0)                                = 0
    close(1)                                = 0
    munmap(0xb7260000, 4096)                = 0
    close(2)                                = 0
    exit_group(0)                           = ?

    并用管道:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    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>

    int main(int"..., 32768) = 180
    write(1,"#include<stdio.h>

    int main(int"..., 180) = 180
    read(3,"", 32768)                      = 0
    close(3)                                = 0
    close(1)                                = 0
    close(2)                                = 0
    exit_group(0)                           = ?
    13

    您可以使用stracetime命令进行一些测试,使用越来越多的命令进行良好的基准测试。