Підкоряємо термінал

Зміст статті
Баззворди на зразок Big Data, Data Science, Spark і Hadoop вже досить щільно в'їлися в наш мозок. Зазвичай при згадці ми відразу уявляємо собі великий дата-центр, поверх якого працює потужна розподілена система, яка шукає складні закономірності і тренує моделі. Але не дарма хтось із титанів сказав: «Ви заслуговуєте отримати другий комп'ютер, тільки коли навчитеся правильно користуватися першим». Багато утиліти командного рядка *nix вже давно написані на C, добре оптимізовані і дозволяють вирішувати багато сучасних завдань з дуже високою ефективністю лише на одній машині. Не віриш? Тоді читай далі.
(Вже прочитав? Тоді переходь до другої частини)
З різкого стрибка популярності аналізу даних вже пройшов деякий час, і прості смертні поступово починають розуміти, чим саме займаються ці найславніші data scientist'и та data-інженери. Однак, обертання гігантськими масивами даних зазвичай вимагає часу, тому важкі джоби на Hadoop або Spark інженери запускають далеко не кожен день. Якщо говорити точніше, то все, що можна автоматизувати, автоматизується, але це аж ніяк не означає, що можна начхати в стелю весь день, є безліч невеликих, але важливих завдань, які доводиться вирішувати. І зазвичай це рутина, яка якраз і являє собою те саме поєднання технології та магії data-інженерії, прикладного програмування та наукових методів. Я взяв на себе сміливість скласти напевно неповний, але основний список:
- фільтрація набору записів за якимось критерієм;
- семплювання;
- вилучення конкретних колонок чи сортування з них;
- заміназначень, їх формату або заповнення перепусток;
- підрахунок базових статистичних показників та операції GroupBy.
«Все ясно, — одразу скаже розробник/аналітик. — Завантажуємо все побічно в Pandas або ж запускаємо Hadoop MapReduce job'у». Стоп, серйозно? Це ж просто файли даних, розділені на колонки та поля, нас з такими вчили працювати ще на вступних лекціях з Лінукс. Плюс вони доступні з більш-менш осудною швидкістю читання найчастіше не локально, а з якогось сервера, на якому і Pandas збереться не завжди (наприклад, через комбінацію древнього CentOS'а та відсутність адмінських прав). А вже використовувати потужності корпоративного Hadoop'у для того, щоб порахувати середнє арифметичне, та й чекати на результат багато хвилин — це якось не виглядає ефективно.
Ах, якби була можливість робити всі перераховані операції швидко, з використанням кількох ядер CPU, читаючи файли поколоночно і витягуючи потрібні дані регулярними виразами, використовуючи готові, перевірені часом утиліти, написані низькорівневою мовою! OH WAIT
Shell-команди на кожен день
Так, практично в будь-якій *nix/BSD-системі є командна оболонка Bash (або Zsh, або навіть Tcsh), в якій типовий інженер проводить досить велику частку свого робочого часу. У вінді з цим дуже гірше (не будемо про PowerShell), але більшість команд цілком можна змусити працювати через Cygwin. Ось список команд, які ми використовуємо щодня: cat, wc, tar, sort, echo, chmod, ls, ps, grep, less, find, history. Далі в тексті я маю на увазі, що цей стандартний мінімум тобі відомий (а якщо ні - прочитай man за тим, що незнайомі).
Крім базового набору, існує також ціла низка команд, що дають додаткові плюшки в окремих випадках;частина з них - це різновиди наведених вище. Давай подивимося на деякі.
seq - генерує послідовність чисел із заданим кроком:
tr — здійснює найпростішу заміну символів у вхідному потоці:
zcat / gzcat / gunzip -c - те саме, що cat, але для файлів, стиснутих в gzip-архів. Перша команда під OS X працює інакше, тому можна поставити gzcat через brew install coreutils або просто використовувати третій варіант - він працює скрізь, хоч і довше.
head - виводить кілька (за замовчуванням десять) рядків з початку файлу. Зручна тим, що не вимагає завантаження файлу в пам'ять. Допомагає підглянути формат даних, наприклад назви колонок і кілька рядків значень CSV.
tail - те саме, тільки виводить рядки не з початку, а n останніх. І цю, і попередню команду можна безстрашно запускати на файлах практично будь-якого розміру.
zgrep — аналог grep для пошуку вмісту файлів в архівах.
uniq — передати на висновок лише рядки, що не повторюються:
Варто пам'ятати про те, що за умовчанням uniq відстежує лише однакові рядки, що йдуть поспіль, тому, якщо хочеться знайти унікальні рядки по всьому входу, треба спочатку його відсортувати.
shuf — робить випадкову вибірку з переданих на вхід рядків:
Ми також маємо готову систему pipe'інгу, яку за нас реалізує система, дозволяючи пов'язувати стандартний висновок однієї команди зі стандартним введенням іншої. У Bash це вертикальна межа між двома командами, ну, ти знаєш:
Щоправда, є невелика каверза — така конструкція завжди при виконанні поверне exit-код останньої команди в ланцюжку, навіть якщо посередині щось упало. Але ж ми хочемо бути в курсі! Тому у продакшенібільшість shell-скриптів містять такий ось виклик:
Таким чином ми перехоплюємо коди виходу у всіх команд і падаємо, якщо щось не так. Однак це одразу може призвести до нових відкриттів — наприклад, ти знав, що grep повертає ненульовий exit-код і повалить весь трубопровід, якщо нічого не знайде? Але ця ситуація теж вирішувана (зловживати цим не варто, але у разі grep ненульовий код з іншої причини ми навряд чи отримаємо). Можна зробити так:
Подвійна вертикальна риса тут означає, що друга команда відпрацює тільки у разі ненульового exit-коду першої, а круглі дужки запускають усю конструкцію в subshell'і, тобто як одну команду. Зрозуміло, всі мінуси такого підходу очевидні — ми маскуємо будь-які невдачі, проте іноді це все-таки допомагає. До речі, а як запустити другу лише за успіху першої? Правильно, через &&.
Ну і ще один хак: буває, нам потрібно поєднати результат запуску кількох незалежних команд в один висновок і використовувати його як частину нашого трубопроводу. Робиться це так:
Зверніть увагу, що є тонка грань між використанням круглих дужок вище та фігурних тут. Круглі дужки — це гарантоване створення сабшелла, тобто системний виклик fork() з очікуванням на результат виконання child-процесу. Фігурні ж дужки - це просто обмеження області видимості та створення блоку коду в контексті поточного шелла, як у мовах програмування (яким, власне, можна назвати і Bash).
Чомусь підсвідомість змушує нас думати, що ці команди запускаються послідовно і починають щось робити тільки коли приходить щось на стандартне введення. Щоб позбавитися цього переконання, спробуй запустити таку команду:
Так, одиницю ми побачимо одразу, а потім будемоще три секунди чекати. Хитрість у тому, що всі команди у ланцюжку запускаються завжди одночасно, а в цьому випадку вони просто ще й не чекають жодних даних на вхід і не пишуть нічого на вихід.
Робота з HTTP
Як є суперечки між любителями Nikon і Canon, так існують і суперечки між прихильниками різних консольних HTTP-клієнтів, наприклад, cURL і Wget. Я розповім про cURL, тому що він краще:).