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


101

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

cat file | command

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

command < file

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

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

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

Моя логика верна (по 2-й причине)?


22
catэто трубка идентичности . Он только передает свой ввод на свой вывод. Если вторая программа в цепочке может принимать входные данные из того же аргумента, которому вы передаете cat(или из стандартного ввода, если вы не передаете аргумент), то catона абсолютно бесполезна и приводит только к разветвлению дополнительного процесса и созданию дополнительной трубы. создан.
Frédéric Hamidi

11
@ FrédéricHamidi, когда у кота нет аргументов или его аргумент есть -, это канал идентификации. Когда он имеет более одного аргумента имени файла без тире, он становится чем-то большим, чем канал идентификации, и начинает служить реальной цели.
kojiro

3
Ранее популярная ссылка на partmaps.org, к сожалению, мертва. Содержание теперь в porkmail.org/era/unix/award.html
tripleee


2
Я заметил, что если вы хотите показать правый поток данных (причина 1), вы можете сделать это, поместив перенаправление файла перед командой, например <file command1 | command2, хотя возникнут разногласия по поводу эстетики.
holdenweb

Ответы:


82

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

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

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

Последняя причина более важна, поэтому я расскажу об этом первым. Когда я предлагаю в качестве решения конвейер, я ожидаю, что его можно будет использовать повторно. Вполне вероятно, что конвейер будет добавлен в конце или соединен с другим конвейером. В этом случае файл , имеющий аргумент Grep щурит многократное использование, и вполне возможно , сделать это тихо и без сообщения об ошибке , если файл существует аргумент. I. e. 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, и если вы не относитесь к этому тому же вы , по крайней мере , используя до тактовых циклов мозга пытаются избежать бесполезную награды кошки.

Это приводит нас к аргументу grep foo file1 file2 file3конкатенации и catконкатенации, так что он уместен, cat file1 file2 file3но потому что catон не конкатенируется, cat file1 | grep fooпоэтому мы нарушаем дух как catUnix, так и всемогущего Unix. Что ж, если бы это было так, тогда Unix потребовалась бы другая команда для чтения вывода одного файла и вывода его на stdout (не разбивать его на страницы или что-то просто чистое слюнение на stdout). Таким образом, у вас может возникнуть ситуация, когда вы говорите cat file1 file2или говорите dog file1и сознательно помните, чтобы избежать cat file1получения награды, а также избегайте, dog file1 file2поскольку, надеюсь, конструкция dogвыдаст ошибку, если указано несколько файлов.

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

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

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

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


53
Обратите внимание, что при grepвызове с несколькими именами файлов он добавляет к найденным строкам префикс имени файла, в котором он был найден (если вы не отключите это поведение). Он также может сообщать номера строк в отдельных файлах. Если использовать только catдля подачи grep, вы потеряете имена файлов, и номера строк будут непрерывными для всех файлов, а не для каждого файла. Таким образом, есть причины, по которым приходится grepобрабатывать несколько файлов самостоятельно cat. Случаи с одним файлом и с нулевым файлом - это просто частные случаи общего использования нескольких файлов grep.
Джонатан Леффлер

38
Как отмечалось в ответе по Кодзиро , это вполне возможно и законно начать трубопровод < file command1 .... Хотя обычно операторы перенаправления ввода-вывода располагаются после имени команды и ее аргументов, это всего лишь соглашение, а не обязательное размещение. Имя <должно предшествовать имени файла. Таким образом, есть близко к идеальной симметрии между >outputи <inputперенаправлениям: <input command1 -opt 1 | command2 -o | command3 >output.
Джонатан Леффлер

15
Я думаю, что одна из причин, по которой люди бросают камень UUoC (включая меня), - это в первую очередь просвещение. Иногда люди обрабатывают гигабайты огромных текстовых файлов, и в этом случае минимизация каналов (UUoC, сворачивание последовательных greps в один, aso) имеет решающее значение, и часто можно с уверенностью предположить, основываясь на вопросе, что OP действительно просто не знает, что небольшие настройки могут иметь огромное влияние на производительность. Я полностью согласен с вашим мнением о циклах мозга, и поэтому я регулярно использую кошку, даже когда в ней нет необходимости. Но важно знать, что это не нужно.
Адриан Фрювирт

13
Пожалуйста, поймите; Я ни в коем случае не говорю, что catэто бесполезно. Это не catбесполезно; дело в том, что конкретная конструкция не нуждается в использовании cat. Если хотите, обратите внимание, что это UUoC (бесполезное использование cat), а не UoUC (использование бесполезного cat). Есть много случаев, когда catэто правильный инструмент; У меня нет проблем с его использованием, когда это правильный инструмент (и, действительно, упомянул случай в моем ответе).
Джонатан Леффлер

6
@randomstring Я вас слышу, но думаю, это действительно зависит от варианта использования. При использовании в командной строке один дополнительный элемент catв конвейере может не иметь большого значения в зависимости от данных, но при использовании в качестве среды программирования может быть абсолютно необходимо реализовать эти важные для производительности вещи; особенно когда имеешь дело с bashколесом прямоугольной формы (по сравнению с kshлюбым другим. Я говорю здесь до 10 раз медленнее - без шуток). Вы действительно хотите оптимизировать свои вилки (и не только) при работе с более крупными скриптами или огромными циклами.
Адриан Фрювирт

58

Нет!

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

< 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)

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


2
Что ж, прирост скорости будет незначительным.
Даккарон,

9
размещение "<somefile" перед "command" технически дает вам слева направо, но это делает неоднозначное чтение, потому что нет синтаксического разграничения: < cat grep dogэто надуманный пример, чтобы показать, что вы не можете легко отличить входной файл от команды который получает ввод и аргументы команды.
necromancer

2
Эмпирическое правило, которое я принял для решения, куда направить перенаправление STDIN, - это делать все, что минимизирует появление двусмысленности / потенциальной неожиданности. Догматическое утверждение, что оно предшествует, поднимает проблему некроманта, но догматическое утверждение, что оно идет после, может сделать то же самое. Рассмотрим: stdout=$(foo bar -exec baz <qux | ENV=VAR quux). Q. Имеет ли <quxприменять к foo, или baz, что -exec«г на foo? A. Это применимо к foo, но может показаться неоднозначным. Полагая , <qux прежде чем foo в этом случае является более ясным, хотя и менее распространенным, и аналогичен роспускным ENV=VAR quux.
Марк Дж.

3
@necromancer, там <"cat" grep dogлегче читать. (Я обычно выступаю за пробелы, но этот конкретный случай - исключение).
Чарльз Даффи

1
@kojiro "Он определенно медленнее." Вы не можете написать это, не подкрепив это числами. Мои числа здесь: oletange.blogspot.com/2013/10/useless-use-of-cat.html (и они показывают, что он медленнее только при высокой пропускной способности). Где ваши?
Ole Tange

30

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

Это особенно актуально для таких сайтов, как Stack Overflow, ServerFault, Unix и Linux, или любых других сайтов SE.

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

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

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


Обновить

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

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 сказали бы, что это UUOC, потому что легко можно установить по $filterумолчанию пустую строку и сделать ifоператор, filter='| grep -v "^$"'но ИМО, не вставляя вертикальный символ в $filter, этот «бесполезный» catслужит чрезвычайно полезной цели самодокументирования факта это $filterв printfстроке не просто еще один аргумент sqlplus, это необязательный выходной фильтр, выбираемый пользователем.

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


12
Кстати, ==внутри [ ]не указано в POSIX, и не все реализации его принимают. Стандартизированный оператор просто =.
Чарльз Даффи

27

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

С помощью:

cat "$@" | command

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

Это cat single-fileдело безоговорочно бесполезное.


27

В защиту кота:

Да,

   < input process > output 

или

   process < input > output 

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

эргономические причины:

Мы привыкли читать слева направо, поэтому такая команда, как

    cat infile | process1 | process2 > outfile

легко понять.

    process1 < infile | process2 > outfile

должен перепрыгнуть через процесс1, а затем прочитать слева направо. Это можно вылечить:

    < 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
    

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

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

При использовании очень больших или многих, многих файлов можно избежать cat. На большинство вопросов использование кошки ортогонально, не по теме, не проблема.

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


6
+11111 .. Как автор принятого на данный момент ответа, я очень рекомендую это восхитительное дополнение. Конкретные примеры проясняют мои часто абстрактные и многословные аргументы, и только тот смех, который вы получаете от раннего трепета автора, file > a.shстоит того, чтобы потратить время на чтение этого :) Спасибо, что поделились!
некромант

В этом вызове cat file | wc -c, wcнужно читать не стандартный ввод до конца файла, подсчет байт. Но в этом случае wc -c < fileон просто stdin определяет, что это обычный файл, и печатает st_size вместо чтения любого ввода. Для большого файла будет отчетливо видна разница в производительности.
огуз

18

Дополнительная проблема заключается в том, что канал может незаметно маскировать подоболочку. В этом примере я заменю 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подоболочки.


5
В оболочках строго POSIX это может быть сложной проблемой, потому что здесь нет строк или подстановок процессов, чтобы избежать конвейера. BashFAQ 24 имеет несколько полезных решений даже в этом случае.
kojiro

4
В некоторых оболочках показанная труба не создает подоболочку. Примеры включают Korn и Z. Они также поддерживают подстановку процессов и здесь строки. Конечно, это не совсем POSIX. Bash 4 должен shopt -s lastpipeизбегать создания подоболочки.
Приостановлено до дальнейшего уведомления.

14

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

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

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

Среди множества ресурсов сценариев оболочки в сети, Stack Overflow необычен тем, что пользователи могут помочь сформировать качество сайта, редактируя вопросы и ответы на сайте. Однако редактирование кода может быть проблематичным, потому что легко вносить изменения, которые не были запланированы автором кода. Поэтому мы склонны оставлять комментарии, чтобы предложить изменения в коде.

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

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

Что касается технических причин, традиционная мудрость состоит в том, что мы должны стараться минимизировать количество внешних процессов; это остается хорошим общим руководством при написании сценариев оболочки.


2
Кроме того, для больших файлов передача по конвейеру cat- это множество дополнительных переключателей контекста и пропускной способности памяти (и загрязнение кеша L3 дополнительными копиями данных в catбуфере чтения и буферах конвейера). Пропускная способность кеша / памяти является общим ресурсом, особенно на большой многоядерной машине (например, на многих хостингах).
Питер Кордес

1
@PeterCordes Пожалуйста, опубликуйте свои измерения. Так что мы можем, если это действительно имеет значение на практике. Мой опыт показывает,
Ole Tange

1
В вашем собственном блоге показано 50% -ное замедление при высокой пропускной способности, и вы даже не смотрите на влияние на общую пропускную способность (если у вас были вещи, которые заставляли другие ядра загружаться). Если я дойду до этого, я могу запустить ваши тесты, пока x264 или x265 кодируют видео с использованием всех ядер, и посмотреть, насколько это замедляет кодирование видео. bzip2и gzipсжатие выполняются очень медленно по сравнению с накладными расходами, catдобавляемыми только к этому (в противном случае машина простаивает). Трудно читать ваши таблицы (перенос строки в середине числа?). sysвремя сильно увеличивается, но все равно мало по сравнению с пользовательским или реальным?
Питер Кордес

8

Я часто использую cat file | myprogramв примерах. Иногда меня обвиняют в бесполезном использовании кошки ( 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 - это файл, но не работает, если вход представляет собой канал, потому что он использует поиск:

    # 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 ГГц) и обычном ноутбуке (2,2 ГГц). Они были запущены по 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%. Это находится в пределах погрешности измерений, поэтому на практике нет никакой разницы.

Для высокой пропускной способности разница больше, и между ними есть явная разница.

Отсюда следует вывод: <вместо cat |if следует использовать :

  • сложность обработки аналогична простой grep
  • производительность важнее удобочитаемости.

В противном случае не имеет значения, используете ли вы <или cat |.

Таким образом, вы должны присваивать UUoC-награду только тогда и только тогда, когда:

  • вы можете измерить значительную разницу в производительности (опубликуйте свои измерения при вручении награды)
  • производительность важнее удобочитаемости.

-3

Я думаю, что (традиционный способ) использование pipe немного быстрее; в моем ящике я использовал 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

Вы можете провести некоторое тестирование с помощью straceи, используя timeвсе больше и больше команд для хорошего тестирования.


9
Я не понимаю, что вы подразумеваете под (традиционным способом) использованием канала , или почему вы думаете, что это straceпоказывает, что это быстрее - во втором случае выполнение straceне отслеживается wc -l. Здесь отслеживается только первая команда конвейера.
kojiro

@kojiro: я имею в виду традиционный способ = наиболее используемый способ (я думаю, что мы используем конвейер больше, чем косвенное обращение), я не могу подтвердить, что он быстрее или нет, по моему следу я видел больше системных вызовов для косвенного обращения. Вы можете использовать программу переменного тока и цикл, чтобы увидеть, как они потребляют больше времени. Если вам интересно, мы можем разместить это здесь :)
TOC

3
Сопоставление яблок с яблоками будет strace -f sh -c 'wc -l < wrong_output.c'рядом strace -f sh -c 'cat wrong_output.c | wc -l'.
Чарльз Даффи

5
Вот результаты ideone.com, которые в настоящее время явно в пользу без cat: ideone.com/2w1W42#stderr
tripleee

1
@CharlesDuffy: mkfifoсоздает именованный канал. Анонимный канал устанавливается с pipe(2)последующим разветвлением, и родитель и потомок закрывают разные концы канала. Но да, этот ответ - полная чушь, и он даже не пытался подсчитывать системные вызовы или использовать strace -Oдля измерения накладных расходов или -rдля отметки времени каждого вызова относительно последнего ...
Питер Кордес
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.