Бесполезное использование кошки?


это, вероятно, во многих часто задаваемых вопросах-вместо использования:

cat file | command

(что называется бесполезное использование кошки), правильный способ должен быть:

command < file

во втором, "правильном" способе - ОС не должна порождать дополнительный процесс.
Несмотря на это, я продолжал использовать бесполезную кошку по двум причинам.

  1. более эстетично-мне нравится, когда данные перемещаются равномерно только слева направо. И его легче заменить cat С чем-то еще (gzcat,echo, ...), добавить 2-й файл или вставить новый фильтр (pv,mbuffer,grep ...).

  2. Я "чувствовал", что в некоторых случаях это может быть быстрее. Быстрее, потому что есть 2 процесса, 1-й (cat) делает чтение, а второй делает все, что угодно. И они могут работать параллельно, что означает иногда более быстрое выполнение.

является ли моя логика правильной (по 2-й причине)?

8 55

8 ответов:

я не знал до сегодня, когда какой-то новичок попытался подколоть UUOC на меня за один из моих ответов. Это было cat file.txt | grep foo | cut ... | cut .... Я дал ему часть своего ума, и только после этого посетил ссылку, которую он дал мне, ссылаясь на происхождение награды и практику этого. Дальнейшие поиски привели меня к этому вопросу. К сожалению, несмотря на сознательное рассмотрение, ни один из ответов не включал мое обоснование.

я не хотел быть защищаясь в ответ на него. В конце концов, в мои молодые годы я бы написал команду grep foo file.txt | cut ... | cut ... потому что всякий раз, когда вы делаете часто один greps вы изучаете размещение аргумента файла, и это готовое знание, что первый-это шаблон, а последующие-имена файлов.

это был сознательный выбор, чтобы использовать cat когда я ответил на вопрос, отчасти из-за причины "хорошего вкуса" (по словам Линуса Торвальдса), но главным образом для убедительного причина функции.

последняя причина более важна, поэтому я сначала потушу ее. Когда я предлагаю трубопровод в качестве решения, я ожидаю, что он будет многоразовым. Вполне вероятно, что трубопровод будет добавлен в конце или соединен с другим трубопроводом. В этом случае наличие аргумента файла для grep завинчивает возможность повторного использования, и вполне возможно сделать это молча без сообщения об ошибке, если аргумент файл существует. То есть grep foo xyz | grep bar xyz | wc даст вам, сколько строк в xyz содержать bar в то время как вы ожидаете количество строк, которые содержат оба foo и bar. Необходимость изменения аргументов для команды в конвейере перед ее использованием может привести к ошибкам. Добавьте к этому возможность молчаливых неудач, и это станет особенно коварной практикой.

первая причина тоже не маловажна, так как много "хорошего вкуса" просто является интуитивным подсознательным обоснованием таких вещей, как тихие неудачи выше, которые вы не можете думать правильно в тот момент, когда какой-то человек, нуждающийся в образовании, говорит: "но разве эта кошка не бесполезна".

тем не менее, я постараюсь также осознать прежнюю причину "хорошего вкуса", о которой я упоминал. Эта причина связана с ортогональным духом дизайна 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 не сцепляется в cat file1 | grep foo поэтому мы нарушаем дух обоих cat и всемогущий Unix. Ну, если бы это было так, то Unix понадобилась бы другая команда, чтобы прочитать вывод одного файла и выплюнуть его stdout (не разбивайте его на страницы или что-то еще просто чистый плевок в stdout). Так что у вас будет ситуация, когда вы говорите cat file1 file2 или dog file1 и добросовестно помните, чтобы избежать cat file1 чтобы избежать получения награды, а также избежать dog file1 file2 С тех пор, надеюсь, дизайн dog выдаст ошибку, если указано несколько файлов.

надеюсь, в этот момент Вы сочувствуете дизайнерам Unix за то, что не включили отдельную команду, чтобы выплюнуть файл в stdout, а также именование cat для конкатенации вместо того, чтобы дать ему какое-то другое имя. <edit> удалены некорректные комментарии <, фактически < является эффективным средством без копирования, чтобы выплюнуть файл в stdout, который вы можете разместить в начале конвейера, поэтому разработчики unix включили что-то специально для этого </edit>

следующий вопрос: почему важно иметь команды, которые просто плюют файл или конкатенацию нескольких файлы в stdout, без дальнейшей обработки? Одна из причин заключается в том, чтобы не иметь каждую команду Unix, которая работает на стандартном входе, чтобы знать, как проанализировать хотя бы один аргумент файла командной строки и использовать его в качестве ввода, если он существует. Вторая причина заключается в том, чтобы пользователи не помнили: (a) где идут аргументы имени файла; и (b) избегайте ошибки беззвучного конвейера, как упоминалось выше.

это подводит нас к тому, почему grep имеет дополнительную логику. Обоснование заключается в том, чтобы позволить пользователю беглость для команд, которые используются часто и на автономной основе (а не как конвейер). Это небольшой компромисс ортогональности для значительного повышения удобства использования. Не все команды должны быть разработаны таким образом, и команды, которые не часто используются, должны полностью избегать дополнительной логики аргументов файла (помните, что дополнительная логика приводит к ненужной хрупкости (возможность ошибки)). Исключение-разрешить аргументы файла, как в случае grep. (кстати Кстати обратите внимание, что ls имеет совершенно другую причину не просто принять, но в значительной степени требуют аргументов файла)

наконец, что можно было бы сделать лучше, если бы такие исключительные команды, как grep (но не обязательно ls) генерирует ошибку, если стандартный ввод также доступен при указании аргументов файла. Это разумно, потому что команды включают логику, которая нарушает ортогональный дух Unix для удобства пользователя. Для дальнейшего удобства пользователя, то есть для предотвращения страданий, вызванных молчаливым отказом, такие команды не должны колебаться нарушать свое собственное нарушение, имея дополнительную логику, чтобы предупредить пользователя, если есть возможность молчаливой ошибки.

Неа!

прежде всего, не имеет значения, где в команде происходит перенаправление. Поэтому, если вам нравится перенаправление слева от вашей команды, это нормально:

< somefile command

это то же самое, что

command < somefile

во-вторых, есть n + 1 процессы и подоболочка происходит, когда вы используете трубу. Это определенно медленнее. В некоторых случаях n было бы равно нулю (например, когда вы перенаправляете на встроенную оболочку), поэтому используя cat вы добавляете новый процесс совершенно излишне.

как обобщение, всякий раз, когда вы обнаружите, что используете трубу, стоит потратить 30 секунд, чтобы увидеть, можете ли вы ее устранить. (Но, вероятно, не стоит брать намного больше, чем 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)

не стесняйтесь редактировать, чтобы добавить больше примеров.

С версией UUoC,cat должен прочитать файл в память, затем записать его в канал, и команда должна прочитать данные из канала, поэтому ядро должно скопировать весь файл три раз, тогда как в перенаправленном случае ядро должно скопировать файл только один раз. Быстрее сделать что-то один раз, чем сделать это три раза.

использование:

cat "$@" | command

это совершенно другое и не обязательно бесполезное использование cat. Это по-прежнему бесполезно, если команда является стандартным фильтром, который принимает ноль или более аргументов имени файла и обрабатывает их по очереди. Рассмотрим tr command: это чистый фильтр, который игнорирует или отклоняет аргументы имени файла. Чтобы передать ему несколько файлов, вы должны использовать cat как показано на рисунке. (Конечно, есть отдельное обсуждение, что конструкция tr не очень хорошо; нет никакой реальной причины, по которой он не мог быть разработан как стандартный фильтр.) Это также может быть действительным, если вы хотите команда для обработки всех входных данных как один файл, а не как несколько отдельных файлов, даже если команда будет принимать несколько отдельных файлов: например, wc такая команда.

это cat single-file случай, который безусловно бесполезен.

Я не согласен с большинством случаев чрезмерно самодовольной награды UUOC, потому что, когда вы учите кого-то другого,cat является удобным местом-держателем для любой команды или жесткого сложного конвейера команд, которые производят вывод, подходящий для обсуждаемой проблемы или задачи.

это особенно верно на таких сайтах, как Stack Overflow, ServerFault, Unix & Linux или любой из сайтов SE.

если кто-то специально спрашивает об оптимизации, или если вы хотите добавить дополнительная информация об этом тогда, отлично, расскажет о том, как использовать cat неэффективно. Но не ругайте людей, потому что они решили стремиться к простоте и легкости понимания в своих примерах, а не смотреть на меня, как я крут! сложность.

короче говоря, потому что кошка не всегда кошка.

кроме того, потому что большинство людей, которым нравится ходить вокруг награждения UUOCs, делают это, потому что они больше озабочены тем, чтобы показать, насколько они "умны", чем они помогают или обучение людей. На самом деле, они демонстрируют, что они, вероятно, просто еще один новичок, который нашел крошечную палку, чтобы бить своих сверстников.


обновление

вот еще один UUOC, который я опубликовал в ответе на https://unix.stackexchange.com/a/301194/7696:

sqlq() {
  local filter
  filter='cat'

  # very primitive, use getopts for real option handling.
  if [ "" == "--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 сказали бы, что это UUOC, потому что это легко можно сделать $filter по умолчанию пустая строка и есть if заявление filter='| grep -v "^$"' но Имо, не вставляя символ трубы в $filter этот "бесполезный" cat служит чрезвычайно полезной цели самодокументирования того факта, что $filter на printf line-это не просто еще один аргумент для sqlplus, это дополнительный выбираемый пользователем выходной фильтр.

если есть необходимость иметь несколько дополнительных выходных фильтров, обработка параметров может просто добавить | whatever до $filter так часто, как это необходимо - один дополнительный cat в конвейере нет это может повредить что-либо или вызвать заметную потерю производительности.

дополнительная проблема заключается в том, что труба может молча маскировать подоболочку. Для этого примера я заменю cat С echo, но та же проблема существует.

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

echo "$x"

вы могли бы ожидать x содержат foo, но это не так.x вы установили был в подрешетке породил для выполнения while петли. x в оболочке, которая запустила конвейер, имеет несвязанное значение или вообще не задано.

в bash4, вы можете настроить некоторые параметры оболочки так что последняя команда конвейера выполняется в той же оболочке, что и та, которая запускает конвейер, но тогда вы можете попробовать это

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

и x снова локально для while's-подуровне.

сценарий оболочки очень похож на язык копирования/вставки. Для большинства людей, которые пишут скрипты, они не в нем, выучить язык, это просто препятствие, они должны преодолеть, чтобы продолжать делать вещи на языке(языках) они вообще-то знакомы.

в этом контексте я рассматриваю его как разрушительный и потенциально даже разрушительно распространять различные анти-шаблоны сценариев оболочки. Код, который кто-то находит при переполнении стека, в идеале должен быть возможен для копирования/вставки в их среду с минимальными изменениями и неполным пониманием.

редактирование кода может быть проблематично потому что легко внести изменения, которые не были предназначены автором кода. Поэтому мы склонны оставлять комментарии, чтобы предложить изменения в коде.

комментарии UUCA и связанные с ними антипаттерны предназначены не только для авторов кода, который мы комментируем; они так же a предостережение emptor помочь читателей на сайте становится известно о проблемах в коде, который они находят здесь.

мы не можем надеяться достичь ситуации, когда нет ответы на переполнение стека рекомендуют бесполезно cats (или некотируемые переменные, или chmod 777, или большое разнообразие других антипаттернов), но мы можем, по крайней мере, помочь обучить пользователя, который собирается скопировать/вставить этот код в самый внутренний жесткий цикл своего скрипта, который выполняется миллионы раз.

на обороны КПП:

да

   < 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

но кого это волнует-15k?

если бы я иногда сталкивался с проблемами, конечно, я бы изменил свою привычку вызывать кошку.

при использовании очень больших или многих, многих файлов, избегая cat штраф. Для большинства вопросов использование cat является ортогональным, вне темы, а не проблемой.

начиная эти бесполезные бесполезное использование обсуждения кошки на каждую вторую тему оболочки только раздражает и скучно. Получить жизнь и ждать Минуты славы, когда дело с вопросами производительности.

Я думаю ,что (традиционный способ) с помощью трубы немного быстрее; на моем поле я использовал чтобы увидеть, что происходит:

без трубы:

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, "7ELFp204"..., 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, "7ELFp204"..., 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

вы можете сделать некоторые испытания с strace и time команда с большим количеством и более длинными командами для хорошего бенчмаркинга.