Чи варто дбати про зайвих котів?


50

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

Порівняйте

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

і

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

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


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

4
@Michael: 100% згоден. Чорт забирав, що мені знадобилося більше часу, щоб один раз прив’язатись до старої нагороди Usenet, ніж мій комп’ютер ніколи не витрачає примірник cat. Однак я думаю, що тут головне питання - читабельність коду, яка часто є пріоритетною порівняно з продуктивністю. Коли швидше насправді можна писати красивіше , чому ні? Вказівка ​​на проблему catзазвичай призводить до того, що користувач краще розуміє трубопроводи та процеси в цілому. Варто докласти зусиль, щоб вони наступного разу писали зрозумілий код.
Калеб

3
У мене фактично є ще одна причина, що мені не подобається перша форма - якщо ви хочете додати ще одну команду на початку конвеєра, вам доведеться також перемістити аргумент, тож редагування більше дратує. (Звичайно, це не означає, що вам потрібно користуватися cat; пункт Калеба про використання функцій та перенаправлення також вирішує це.)
Cascabel


1
Вечір на роботі, мій мав відмову працювати. Я відкриваю stackoverflow і знаходжу запитання під назвою "Чи варто мені піклуватися про зайвих котів?" і побачити деяких безпритульних тварин та програміста, розмірковуючи про те, чи годувати їх чи ні ...
Борис Бурков

Відповіді:


46

"Остаточну" відповідь, безумовно, приносить вам "Безкорисне використання 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/ }/ )/ тощо. @Caleb Це справедливо у всіх оболонках Bourne / POSIX. І я не згоден, що це некрасиво.
Жил 'ТАК - перестань бути злим'

9
@Gilles в БАШЕЄВ ви можете замінити $(cat /some/file)з $(< /some/file), який робить те ж саме , але уникає породження процесу.
cjm

3
Просто для підтвердження того, що $(< /some/file)це обмежена портативність. Він працює в bash, але не попел BusyBox, наприклад, або FreeBSD sh. Напевно, теж не працює в тирі, оскільки ці три останні снаряди - це близькі родичі.
сумнівним

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 pct, якщо ви не дуже обробляєте в потоці 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), то я вважаю, що підхід до котів займає на 23% більше часу, коли в багатоядерній системі, і на 5% при прив’язуванні їх до одного ядра. Показані додаткові накладні витрати, отримані завдяки доступу до даних більш ніж одного ядра. Можливо, ви отримаєте різні результати, якщо зміните розмір труби, використовуєте різні дані, залучаєте реальні введення-виведення, використовуйте котячу реалізацію, яка використовує сплайс () ... Все підтверджує, що на зображенні багато параметрів. і це ні в якому разі catне допоможе.
Стефан Шазелас

1
Для мене з файлом 1 Гб wc -w- це різниця приблизно в 2% ... 15% різниці, якщо справа в простому грепі. Тоді, дивно, якщо він знаходиться на спільному доступі до файлів NFS, насправді на 20% швидше його читати, якщо з нього cat( gist.github.com/rdp/7162414833becbee5919cda855f1cb86 ) Дивно ...
rogerdpack

16

Поставити <fileна кінець трубопроводу менш читабельно, ніж cat fileна початку. Натуральна англійська мова читається зліва направо.

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

Використання catзбереже command | command | commandформат.


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

@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, який зчитується з якогось файлу або stdin (відповідно), а потім виконує деяку обробку - вона читає, поки не потрапить у новий рядок, замінить перший 'foo' (якщо такий є) у цьому рядку на 'bar', а потім надрукує цю лінію для stdout та циклів.

У випадку:

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

Оболонка породжує котячий відросток і сед-процес, і з'єднує котячу живучість до седдіна. Котячий процес зчитує кілька кілограмових або, можливо, мегабайтних фрагментів із файлу, а потім записує їх у свій stdout, де звідти піднімається sed sommand, як у другому прикладі вище. Поки sed обробляє цей шматок, кішка читає ще один шматок і записує його в свій stdout, щоб sed працював далі.

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


2
Дивіться oletange.blogspot.dk/2013/10/useless-use-of-cat.html для перевірки накладних витрат використання додаткового cat.
Оле Танге

@OleTange: Я просто натрапив на це і відвідав ваш блог. (1) Поки я бачу вміст (в основному) англійською мовою, я бачу купу слів на (я думаю) датській мові: "Klassisk", "Flipcard", "Magasin", "Mosaik", "Sidebjælke", "Øjebliksbillede" , “Tidsskyder”, “Blog-arkiv”, “Om mig”, “Skrevet” та “Vis komentator” (але “Tweet”, “Like” та банер cookie є англійською мовою). Чи знали ви про це, і чи це під вашим контролем? (2) У мене виникають проблеми з читанням ваших таблиць (2a), оскільки сітки лінії є неповними, і (2b) я не розумію, що ви маєте на увазі під "Diff (pct)".
G-Man каже: "Відновіть Моніку"

blogspot.dk управляє Google. Спробуйте замінити його на blogspot.com. "Різниця (pct)" - це мс, catподілене на ms без catвідсотків (наприклад, 264 мс / 216 мс = 1,22 = 122% = на 22% повільніше cat)
Оле Танге
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.