Марне використання кота?


101

Це, мабуть, у багатьох поширених запитаннях - замість того, щоб використовувати:

cat file | command

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

command < file

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

  1. більш естетичний - мені подобається, коли дані рухаються рівномірно лише зліва направо. І це легше замінити catніж - то іншим ( gzcat, echo, ...), додайте 2 - й файл або вставити новий фільтр ( pv, mbuffer, grep...).

  2. Я "відчував", що в деяких випадках це може бути швидшим. Швидше, тому що є 2 процеси, 1-й ( cat) робить читання, а другий робить все, що завгодно. І вони можуть працювати паралельно, що означає іноді швидше виконання.

Чи правильна моя логіка (з другої причини)?


22
catє ідентифікаційною трубкою . Він лише передає свої дані на свій вихід. Якщо друга програма в ланцюзі може взяти свій вхід з того ж аргументу, який ви передаєте cat(або зі стандартного вводу, якщо ви не catпередаєте жодного аргументу), то це абсолютно марно і призводить лише до того, що роздвоєння додаткового процесу та подання додаткової труби створено.
Фредерік Хаміді

11
@ FrédéricHamidi, коли у кішки немає аргументів або її аргумент є -, це ідентичність. Якщо у нього є більше одного аргументу, який не має тире, він стає чимось більшим, ніж ідентифікація, і починає слугувати реальній меті.
kojiro

3
Раніше популярне посилання на partmaps.org, на жаль, мертве. Тепер вміст знаходиться на веб-
tripleee


2
Я зауважую, що якщо ви хочете показати потік даних праворуч (причина 1), ви можете зробити це, поставивши перенаправлення файлу перед командою, як у <file command1 | command2, хоч би не було розбіжностей щодо естетики.
holdenweb

Відповіді:


81

Мені не було відомо про нагороду до сьогодні, коли якийсь новичок намагався прив’язати УУПЦ до мене за однією з моїх відповідей. Це було cat file.txt | grep foo | cut ... | cut .... Я віддав йому частину своєї думки, і лише після цього відвідав посилання, він дав мені посилання на походження нагороди та практику цього робити. Подальший пошук привів мене до цього питання. На жаль, незважаючи на свідомий розгляд, жодна з відповідей не включала мого обґрунтування.

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

Це був усвідомлений вибір, 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, і якщо ви не ставитесь до цього так само, ви принаймні використовуєте цикли мозку годинника, намагаючись уникнути марної нагороди котам.

Це призводить нас до аргументації, яка 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, при цьому іменуючи catconcatenate, а не даючи йому якесь інше ім'я. <edit>видалено невірні коментарі <, насправді, <це ефективний інструмент без копіювання, щоб виплюнути файл в stdout, який ви можете розмістити на початку конвеєра, щоб дизайнери Unix включили щось спеціально для цього</edit>

Наступне питання: чому важливо мати команди, які просто плюють файл або об'єднання декількох файлів у stdout, без подальшої обробки? Однією з причин є уникнення наявності кожної однієї команди Unix, яка працює на стандартному вході, щоб знати, як розібрати принаймні один аргумент файлу командного рядка та використовувати його як вхідний файл, якщо він існує. Друга причина - уникнути того, щоб користувачі пам’ятали: (a) куди йдуть аргументи імені файлів; та (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, згортання послідовних грейпів в один, асо) є вирішальним, і часто це можна з упевненістю припустити, виходячи з питання про те, що ОП насправді просто не знає, що маленькі твіни можуть мати величезний вплив на продуктивність. Я повністю погоджуюся з вашою думкою про мозкові цикли, і саме тому я регулярно використовую кота, навіть коли це не потрібно. Але важливо знати, що це не потрібно.
Адріан Фрюхвірт

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
розміщення «<якогось - файл» перед «командою» технічно дає зліва направо, але це робить для неоднозначного читання , тому що немає синтаксичного розмежування: < cat grep dogнадуманий приклад , щоб показати , що ви не можете легко відрізнити від вхідного файлу, команди що отримує вхід та аргументи команди.
некромант

2
Я вирішив вирішити, куди йде перенаправлення STDIN, щоб зробити все, що мінімізує появу неоднозначності / потенціалу для здивування. Догматично кажучи, що йдеться раніше, виникає проблема некроманта, але догматично кажучи, що це йде далі, можна зробити те ж саме. Розглянемо: stdout=$(foo bar -exec baz <qux | ENV=VAR quux). З. Чи <quxзастосовується fooчи до того baz, що -exec'd by 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 (і вони показують, що це лише повільніше, коли у вас висока пропускна здатність) Де ви?
Оле Танге

30

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

Особливо це стосується сайтів, таких як переповнення стека, ServerFault, Unix & Linux або будь-якого з сайтів SE.

Якщо хтось конкретно запитує про оптимізацію, або якщо ви хочете додати додаткову інформацію про це тоді, чудово, поговоріть про те, як використання кота неефективно. Але не бійте людей, бо вони вирішили націлитись на простоту та легкість розуміння у своїх прикладах, а не на погляд-на-мене-як-круто-я-я! складність.

Коротше кажучи, тому що кішка не завжди є котом.

Крім того, тому, що більшість людей, які люблять ходити в нагороду за УУПЦ, роблять це, тому що вони більше переймаються тим, як показувати, наскільки вони "розумні", ніж допомагати чи навчати людей. Насправді вони демонструють, що вони, мабуть, просто черговий новачок, який знайшов крихітну паличку, з якою бив своїх однолітків.


Оновлення

Ось ще один 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 "^$"'але IMO, не вставляючи символу труби в $filterцей, "марний" catслужить надзвичайно корисною метою самодокументування факту що $filterв printfрядку не просто ще один аргумент sqlplus, це необов'язковий вихідний фільтр, обраний користувачем.

Якщо є необхідність мати кілька додаткових вихідних фільтрів, обробка варіант може просто додати | whateverдо $filterтак часто , як необхідно - один додатковий catв трубопроводах не буде боляче що - небудь або заподіяти будь - якої помітної втрати продуктивності.


11
Як убік - ==всередині [ ]не визначено POSIX, і не всі реалізації приймають його. Стандартизований оператор справедливий =.
Чарльз Даффі

27

З версією UUoC, catмає прочитати файл в пам'ять, а потім записати його в трубу, і команда повинна зчитувати дані з труби, тому ядро має скопіювати весь файл в три рази , тоді як в перенаправлений випадку, ядро має скопіювати файл лише один раз. Швидше щось зробити один раз, ніж зробити це три рази.

Використання:

cat "$@" | command

- це зовсім інше і не обов'язково марне використання cat. Це все ще марно, якщо команда є стандартним фільтром, який приймає нульові або більше аргументів імен файлів і обробляє їх по черзі. Розглянемо trкоманду: це чистий фільтр, який ігнорує або відкидає аргументи імені файлів. Щоб подати до нього кілька файлів, ви повинні використовувати, catяк показано. (Звичайно, існує окрема дискусія про те, що дизайн trне дуже хороший; немає реальної причини, щоб він не міг бути розроблений як стандартний фільтр.) Це також може бути дійсним, якщо ви хочете, щоб команда розглядала всі вхідні дані як один файл, а не декілька окремих файлів, навіть якщо команда прийме кілька окремих файлів: наприклад, wcтака команда.

Це cat single-fileсправа безумовно марна.


26

На захист кота:

так,

   < 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
    

але кого турбує - 15к?

Якби я час від часу стикався з проблемами, напевно я змінив би свою звичку викликати кота.

Якщо ви використовуєте дуже великі чи багато-багато файлів, уникати кота - це добре. У більшості питань використання кота є ортогональним, поза темою, а не проблемою.

Починати цю марну марну дискусію про котів на кожну другу тему оболонки лише дратує і нудно. Займайтеся життям і чекайте своєї хвилини слави, займаючись питаннями про ефективність.


5
+11111 .. Як автор прийнятої на даний момент відповіді, настійно рекомендую цей чудовий додаток. Конкретні приклади висвітлюють мої часто абстрактні та багатослівні аргументи, і сміх, який ви отримуєте від раннього трепету автора, file > a.shодин варто того, щоб прочитати це :) Дякую за обмін!
некромант

У цьому виклику cat file | wc -c, wcпотрібно читати не стандартне введення до кінця файлу, підрахунок байт. Але в цьому, wc -c < fileвін просто зазначає stdin, з'ясовує, що це звичайний файл і друкує st_size замість того, щоб читати будь-які дані. Для великого файлу різниця в продуктивності була б добре видно.
oguz ismail

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передплати 's.


5
У строго оболонках POSIX це може бути складною проблемою, оскільки у вас немає тут рядків або підстановок, щоб уникнути труби. BashFAQ 24 має деякі корисні рішення навіть у цьому випадку.
kojiro

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

13

Як хтось, хто регулярно вказує на це та ряд інших антипакетів програмування оболонок, я відчуваю, що я зобов’язаний запізнитися.

Шрифт Shell - це дуже багато мов копіювання / вставки. Для більшості людей, які пишуть сценарії оболонок, вони не в курсі мови; це лише перешкода, яку їм доводиться долати, щоб продовжувати робити речі мовою, якою вони насправді дещо знайомі.

У цьому контексті я вважаю це руйнівним і потенційно навіть руйнівним для розповсюдження різних сценаріїв сценаріїв оболонок. Код, який хтось знайде в Stack Overflow, в ідеалі повинен бути можливим для копіювання / вставлення у своє оточення з мінімальними змінами та неповним розумінням.

Серед безлічі ресурсів сценаріїв оболонок в мережі, Stack Overflow незвичний тим, що користувачі можуть допомогти формувати якість сайту, редагуючи питання та відповіді на сайті. Однак редагування коду може бути проблематичним, оскільки легко вносити зміни, які не були призначені автором коду. Отже, ми, як правило, залишаємо коментарі, щоб запропонувати зміни до коду.

UUCA та пов'язані з цим антипатернічні коментарі не лише для авторів кодексу, який ми коментуємо; вони є стільки ж застереженням, щоб допомогти читачам сайту усвідомити проблеми коду, який вони тут знаходять.

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

Що стосується технічних причин, традиційна мудрість полягає в тому, що ми повинні намагатися мінімізувати кількість зовнішніх процесів; це як і раніше є хорошим загальним керівництвом при написанні скриптів оболонок.


2
Крім того, що для великих файлів, пропуск по каналах cat- це багато додаткових контекстних комутаторів і пропускної здатності пам'яті (та забруднення кешу L3 від зайвих копій даних у catбуфері зчитування, і буфери труби). Особливо на великій багатоядерній машині (як і у багатьох установках хостингу) пропускна здатність кешу / пам'яті є спільним ресурсом.
Пітер Кордес

1
@PeterCordes Будь ласка, опублікуйте свої вимірювання. Так ми можемо, якщо це дійсно має значення на практиці. Мій досвід полягає в тому, що це зазвичай не має значення: oletange.blogspot.com/2013/10/useless-use-of-cat.html
Оле Танге

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

  • складність обробки схожа на просту греп
  • продуктивність має більше значення, ніж читабельність.

Інакше не має значення, використовуєте ви <чи cat |.

І, таким чином, ви повинні вручати нагороду UUoC лише тоді, і лише якщо:

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

-3

Я думаю, що (традиційний спосіб) використання труби трохи швидше; я використовував straceкоманду, щоб побачити, що відбувається:

Без труби:

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

І з трубою:

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

Ви можете зробити деякі тести з 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.