Должен ли я заботиться о ненужных кошках?

50

Многие утилиты командной строки могут получать свои данные либо из канала, либо в качестве аргумента имени файла. Для длинных сценариев оболочки, я считаю, что начало цепочки с помощью catделает ее более читаемой, особенно если первая команда будет нуждаться в многострочных аргументах.

сравнить

sed s/bla/blaha/ data \
| grep blah \
| grep -n babla

а также

cat data \
| sed s/bla/blaha/ \
| grep blah \
| grep -n babla

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

tshepang
источник
30
Я трачу гораздо больше времени, наблюдая, как люди нападают друг на друга из-за бесполезного использования кошек на этом сайте, чем моя система фактически запускает процессы кошек
Майкл Мрозек
4
@ Майкл: 100% согласны. Черт возьми, мне потребовалось больше времени, чтобы один раз связать со старой наградой usenet, чем мой компьютер когда-либо потратит впустую cat. Однако я думаю, что главный вопрос здесь - читаемость кода, которая часто является приоритетом перед производительностью. Когда быстрее можно написать красивее , почему бы и нет? Указание на проблему catобычно приводит к тому, что пользователь лучше понимает конвейеры и процессы в целом. Это стоит того, чтобы в следующий раз они написали понятный код.
Калеб
3
У меня на самом деле есть еще одна причина, по которой мне не нравится первая форма - если вы хотите добавить еще одну команду в начале конвейера, вам также нужно переместить аргумент, чтобы редактирование было более раздражающим. (Конечно, это не означает, что вы должны использовать cat; точка зрения Калеба об использовании функций и перенаправления также решает эту проблему.)
Каскабель
Связанный: Удалить бесполезное использование кошки или нет?   (Мета)
G-Man говорит: «Восстановите Монику»
1
Вечер на работе, мой отказывается работать. Я открываю stackoverflow и нахожу вопрос под названием «Должен ли я заботиться о ненужных кошках?» и увидеть некоторых бездомных животных и программиста, размышляющих о том, кормить их или нет ...
Борис Бурков

Ответы:

46

«Окончательный» ответ, конечно же, принес вам «Бесполезное использование catнаграды» .

Цель cat состоит в том, чтобы объединять (или «связывать») файлы. Если это всего лишь один файл, то его объединение вообще ни с чем не является пустой тратой времени и стоит вам процесса.

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

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

Вы даже можете проявить творческий подход, ГДЕ вы пишете. Обычно это будет помещено в конец команды, прежде чем вы укажете какие-либо перенаправления вывода или каналы, как это:

sed s/blah/blaha/ < data | pipe

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

< data \
    sed s/bla/blaha/ |
    grep blah |
    grep -n babla

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

function fix_blahs () {
    sed s/bla/blaha/ |
    grep blah |
    grep -n babla
}

fix_blahs < data

Затем вы можете продолжить fix_blahs < data | fix_frogs | reorder | format_for_sql. Пипллайн, который читается так, очень легко следовать, и отдельные компоненты могут быть легко отлажены в своих соответствующих функциях.

Калеб
источник
26
Я не знал, что <fileможет прийти раньше команды. Это решает все мои проблемы!
3
@Tim: Bash и Zsh поддерживают это, хотя я думаю, что это ужасно. Когда я беспокоюсь о том, что мой код красив и удобен в обслуживании, я обычно использую функции для его очистки. Смотрите мое последнее редактирование.
Калеб
8
@Tim <fileможет находиться в любом месте командной строки: <file grep needleили grep <file needleили grep needle <file. Исключение составляют сложные команды, такие как циклы и группировки; там перенаправление должно наступить после закрытия done/ }/ )/ etc. @Caleb Это относится ко всем оболочкам Bourne / POSIX. И я не согласен, что это некрасиво.
Жиль "ТАК - перестань быть злым"
9
@Gilles, в Bash вы можете заменить $(cat /some/file)на $(< /some/file), что делает то же самое, но избегает порождения процесса.
CJM
3
Просто чтобы подтвердить, что $(< /some/file)это ограниченная мобильность. Он работает в bash, но не в BusyBox, например, в FreeBSD sh. Вероятно, не работает в тире, так как эти последние три снаряда все близкие родственники.
dubiousjim
22

Вот краткое изложение некоторых недостатков:

cat $file | cmd

над

< $file cmd
  • Во-первых, примечание: здесь (намеренно для целей обсуждения) пропущены двойные кавычки $file. В случае cat, это всегда проблема, за исключением zsh; в случае перенаправления это проблема только для bashили, ksh88а для некоторых других оболочек только в интерактивном режиме (не в сценариях).
  • Наиболее часто упоминаемый недостаток - дополнительный процесс, который порождается. Обратите внимание, что если cmdвстроен, это даже 2 процесса в некоторых оболочках, как bash.
  • Все еще на переднем плане производительности, за исключением catвстроенных оболочек , в которых также выполняется дополнительная команда (и, конечно, она загружается и инициализируется (и библиотеки, с которыми она связана)).
  • Еще на фронте производительности для больших файлов, это означает , что система должна поочередно графику catи cmdпроцессы и постоянно пополняет и опустошение буфера трубы. Даже если за один раз cmdвыполняются 1GBбольшие read()системные вызовы, управление должно идти назад и вперед между catи cmdпотому, что канал не может хранить более нескольких килобайт данных за раз.
  • Некоторые cmds (например wc -c) могут выполнять некоторую оптимизацию, когда их стандартный stdin - это обычный файл, с которым они не могут работать, так cat | cmdкак их стандартный stdin - это просто канал. С catи труба, это также означает, что они не могут seek()в файле. Для таких команд, как tacили tail, это имеет огромное значение для производительности, так как это означает, что catони должны хранить весь ввод в памяти.
  • И cat $fileдаже его более правильная версия cat -- "$file"не будет работать должным образом для некоторых конкретных имен файлов, таких как -( --helpили что-нибудь, начиная с, -если вы забудете --). Если кто-то настаивает на использовании cat, он, вероятно, должен использовать cat < "$file" | cmdвместо этого для надежности.
  • Если $fileне может быть открыт для чтения (доступ запрещен, не существует ...), < "$file" cmdсообщит о непротиворечивом сообщении об ошибке (оболочкой) и не запустится cmd, пока cat $file | cmdбудет работать, cmdно с его stdin, похожим на пустой файл. Это также означает, что в таких вещах, как < file cmd > file2, file2не засоряется, если fileне может быть открыт.
Стефан Шазелас
источник
2
Относительно производительности: этот тест показывает, что разница составляет порядка 1%, если вы не выполняете очень мало обработки в потоке oletange.blogspot.dk/2013/10/useless-use-of-cat.html
Ole Tange
2
@OleTange. Вот еще один тест: truncate -s10G a; time wc -c < a; time cat a | wc -c; time cat a | cat | wc -c. Есть много параметров, которые входят в картину. Падение производительности может составлять от 0 до 100%. В любом случае, я не думаю, что штраф может быть отрицательным.
Стефан Шазелас
2
wc -cэто довольно уникальный случай, потому что он имеет ярлык. Если вы вместо этого сделаете это, wc -wто это сравнимо с grepмоим примером (т. Е. Очень мало обработки - то есть ситуация, когда «<» может иметь значение).
Оле Танге
@OleTange, даже ( wc -wдля разреженного файла объемом 1 ГБ в локали C на linux 4.9 amd64) я обнаружил, что подход cat занимает больше времени на 23% в многоядерной системе и 5% при привязке их к одному ядру. Показаны дополнительные издержки, связанные с доступом к данным более чем одним ядром. Возможно, вы получите другие результаты, если вы измените размер канала, будете использовать разные данные, задействуете реальный ввод-вывод, используйте реализацию cat, которая использует splice () ... Все это подтверждает, что на рисунке много параметров. и это в любом случае catне поможет.
Стефан
1
Для меня с 1 ГБ файлом wc -wэто разница примерно в 2% ... 15%, если это просто простой grep. Затем, как ни странно, если он находится в общем файловом ресурсе NFS, на самом деле он читается на 20% быстрее, если передается по каналуcat ( gist.github.com/rdp/7162414833becbee5919cda855f1cb86 ) Странно ...
rogerdpack
16

Помещение <fileв конец конвейера менее читабельно, чем cat fileв начале. Естественный английский читает слева направо.

Ввод <fileначало трубопровода также менее читабельным , чем кошки, я бы сказал. Слово более читабельно, чем символ, особенно символ, который, кажется, указывает неправильный путь.

Использование catсохраняет command | command | commandформат.

Джим
источник
Я согласен, использование <одного раза делает код менее читаемым, так как это нарушает синтаксическую согласованность многопоточной линии.
А.Данищевский
@Jim Вы можете решить удобочитаемость, создав псевдоним, <подобный этому: alias load='<'и затем используйте например load file | sed .... Псевдонимы могут использоваться в скриптах после запуска shopt -s expand_aliases.
niieani
1
Да, я знаю об псевдонимах. Однако, хотя этот псевдоним заменяет символ словом, он требует, чтобы читатель знал о ваших личных настройках псевдонима, поэтому он не очень переносим.
Джим
8

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

В случае этих двух:

sed 's/foo/bar/' somefile
<somefile sed 's/foo/bar/'

оболочка запускает процесс sed, который читает из somefile или stdin (соответственно), а затем выполняет некоторую обработку - он читает до попадания на новую строку, заменяет первый «foo» (если есть) в этой строке на «bar», затем печатает эта строка в стандартный вывод и петли.

На случай, если:

cat somefile | sed 's/foo/bar/'

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

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

godlygeek
источник
2
См. Oletange.blogspot.dk/2013/10/useless-use-of-cat.html для проверки издержек использования дополнительного cat.
Оле Танге
@OleTange: я просто наткнулся на это и посетил ваш блог. (1) В то время как я вижу контент (в основном) на английском языке, я вижу несколько слов на (я полагаю) датском: «Классиск», «Флипкарта», «Магазин», «Мозаик», «Сайдбьюлке», «Эжебликсбилле» , «Tidsskyder», «Blog-arkiv», «Om mig», «Skrevet» и «Vis kommentarer» (но «Tweet», «Like» и баннер печенья на английском языке). Знаете ли вы об этом, и это под вашим контролем? (2) У меня проблемы с чтением ваших таблиц (2a), потому что линии сетки неполны, и (2b) я не понимаю, что вы подразумеваете под «Diff (pct)».
G-Man говорит «Восстановить Монику»
blogspot.dk находится в ведении Google. Попробуйте заменить на blogspot.com. «Diff (pct)» - это мс с catделением на мс без catпроцентов (например, 264 мс / 216 мс = 1,22 = 122% = 22% медленнее с cat)
Ole Tange