Канали та фільтри

Огляд

Викладання: 25 хв
Вправи: 10 хв
Питання
  • Як я можу комбінувати команди, що вже існують, щоб робити нові речі?

Цілі
  • Перенаправити вивід команди до файлу.

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

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

  • Пояснити перевагу поєднання команд з каналами та фільтрами.

Тепер, коли ми знаємо декілька основних команд, ми можемо нарешті розглянути найпотужнішу можливість терміналу: легкість, з якою він дозволяє нам комбінувати програми, що існують, новими способами. Ми почнемо з каталогу shell-lesson-data/exercise-data/proteins, який містить шість файлів, що описують деякі прості органічні молекули. Розширення .pdb вказує на те, що ці файли у форматі Protein Data Bank, простому текстовому форматі, який визначає тип і положення кожного атома в молекулі.

$ ls
cubane.pdb    methane.pdb    pentane.pdb
ethane.pdb    octane.pdb     propane.pdb

Запустимо приклад команди:

$ wc cubane.pdb
20  156 1158 cubane.pdb

wc - команда ‘порахуй слова’ (англ. ‘word count’): вона підраховує кількість рядків, слів і символів у файлах (зліва направо, у такому порядку).

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

$ wc *.pdb
  20  156  1158  cubane.pdb
  12  84   622   ethane.pdb
   9  57   422   methane.pdb
  30  246  1828  octane.pdb
  21  165  1226  pentane.pdb
  15  111  825   propane.pdb
 107  819  6081  total

Зверніть увагу, що wc *.pdb також показує загальну кількість усіх рядків в останньому рядку виводу.

Якщо ми виконаємо wc -l замість просто wc, у виводі буде показано лише кількість рядків у файлі:

$ wc -l *.pdb
  20  cubane.pdb
  12  ethane.pdb
   9  methane.pdb
  30  octane.pdb
  21  pentane.pdb
  15  propane.pdb
 107  total

Параметри -m і -w також можна використовувати з командою wc, щоб показати тільки кількість символів або тільки кількість слів у файлах.

Чому воно нічого не робить?

Що станеться, якщо команда має обробити файл, але ми не вкажемо імені файлу? Наприклад, що буде, якщо ми наберемо:

$ wc -l

але не будемо вводити *.pdb (або щось інше) після команди? Оскільки команда не має жодних назв файлів, wc вважає, що вона повинна обробляти дані, введені у командному рядку, тож вона просто сидить і чекає, поки ми надамо їй інтерактивно якісь дані. Ззовні, однак, все, що ми бачимо, це те, що вона, здається, нічого не робить.

Якщо ви припустилися такої помилки, ви можете вийти з цього стану, утримуючи клавішу control (Ctrl), один раз натиснувши клавішу C, відпустивши після цього клавішу Ctrl. Ctrl+C

Перехоплення виводу з команд

Який з цих файлів містить найменше рядків? На це питання легко відповісти, коли файлів всього шість, але що, якщо їх 6000? Наш перший крок до пошуку рішеня - це запуск команди:

$ wc -l *.pdb > lengths.txt

Символ ‘більше ніж’, >, вказує терміналу перенаправити вивід команди до файла замість виведення його на екран. (Саме тому виведення на екран не відбувається: все, що мала вивести команда wc, було збережено у файлі lengths.txt замість цього). Термінал створить файл, якщо його не існує. Якщо файл існує, його буде буде перезаписано без будь-яких повідомлень, що може призвести до втрати даних, а отже, вимагає певної обережності. Файл ls lengths.txt підтверджує, що файл існує:

$ ls lengths.txt
lengths.txt

Тепер ми можемо вивести вміст файлу lengths.txt на екран за допомогою команди cat lengths.txt. Команда cat отримала свою назву від слова ‘concatenate’, тобто об’єднувати, і вона виводить вміст файлів один за одним. У цьому випадку є лише один файл, тому cat просто відображає нам його вміст:

$ cat lengths.txt
  20  cubane.pdb
  12  ethane.pdb
   9  methane.pdb
  30  octane.pdb
  21  pentane.pdb
  15  propane.pdb
 107  total

Виведення сторінка за сторінкою

У цьому уроці ми продовжимо використовувати команду cat для зручності та послідовності, але вона має той недолік, що завжди виводить весь файл на екран. Більш корисною на практиці є команда less, яку ви використовуєте за зразком less lengths.txt. Вона виводить один екран вмісту файлу, а потім зупиняється. Ви можете пересунутися на один екран вперед, натиснувши клавішу пробіл, або на один екран назад натисканням клавіші b. Щоб вийти, натисніть q.

Фільтрування виводу

Далі ми скористаємося командою sort для сортування вмісту файлу lengths.txt. Але спочатку ми виконаємо вправу, щоб дізнатися більше про команду sort:

Що робить sort -n?

Файл shell-lesson-data/exercise-data/numbers.txt містить наступні рядки:

10
2
19
22
6

Якщо ми виконаємо команду sort для цього файлу, то отримаємо наступне:

10
19
2
22
6

Якщо ми виконаємо команду sort -n для того ж файлу, то отримаємо наступне:

2
6
10
19
22

Поясніть, чому -n має такий ефект.

Розв’язання

Опція -n задає числове, а не алфавітно-цифрове сортування.

Ми також будемо використовувати опцію -n, щоб вказати, що сортування буде числовим, а не алфавітно-цифровим. Це не змінить файл; натомість відсортований результат буде виведено на екран:

$ sort -n lengths.txt
  9  methane.pdb
 12  ethane.pdb
 15  propane.pdb
 20  cubane.pdb
 21  pentane.pdb
 30  octane.pdb
107  total

Ми можемо помістити відсортований список рядків в інший тимчасовий файл, який називається sorted-lengths.txt. додавши > sorted-lengths.txt після команди, так само, як ми використовували > lengths.txt, щоб помістити виведення wc у lengths.txt. Після того, як ми це зробили, ми можемо виконати іншу команду, що має назву head, щоб отримати перші кілька рядків у sorted-lengths.txt:

$ sort -n lengths.txt > sorted-lengths.txt
$ head -n 1 sorted-lengths.txt
  9  methane.pdb

Використання -n 1 з head говорить команді, що нам потрібен лише перший рядок файлу; -n 20 отримає перші 20, і так далі. Оскільки файл sorted-lengths.txt містить довжини наших файлів, впорядковані від найменшої до найбільшої, виведенням head має бути файл з найменшою кількістю рядків.

Перенаправлення у той самий файл

Це дуже погана ідея - намагатися перенаправити вихідні дані команди, яка оперує з файлом, у той самий файл. Наприклад:

$ sort -n lengths.txt > lengths.txt

Виконання таких дій може надати вам некоректні результати та/або видалити вміст файлу lengths.txt.

Що означає >>?

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

$ echo Команда echo виводить текст
Команда echo виводить текст

Тепер протестуйте наведені нижче команди, щоб виявити різницю між цими двома операторами:

$ echo hello > testfile01.txt

та:

$ echo hello >> testfile02.txt

Підказка: Спробуйте виконати кожну команду двічі поспіль, а потім переглянути вихідні файли.

Розв’язання

У першому прикладі з > рядок ‘hello’ записується до файлу testfile01.txt, але файл перезаписується кожного разу, коли ми запускаємо команду.

З другого прикладу ми бачимо, що оператор >> також записує рядок ‘hello’ у файл (у цьому випадку testfile02.txt), але додає рядок до файлу, якщо останній вже існує (тобто, коли ми запускаємо його вдруге).

Додавання даних

Ми вже зустрічалися з командою head, яка друкує рядки з початку файлу. Команда tail схожа на неї, але друкує рядки з кінця файлу.

Розглянемо файл shell-lesson-data/exercise-data/animal-counts/animals.csv. Після виконання цих команд виберіть відповідь, яка відповідає файлу animals-subset.csv:

$ head -n 3 animals.csv > animals-subset.csv
$ tail -n 2 animals.csv >> animals-subset.csv
  1. Перші три рядки файлу `animals.csv’
  2. Останні два рядки файлу animals.csv.
  3. Перші три рядки та останні два рядки файлу animals.csv.
  4. Другий і третій рядки файлу animals.csv.

Розв’язання Варіант 3 є правильним. Щоб варіант 1 був правильним, потрібно виконати лише команду head. Щоб варіант 2 був правильним, нам слід виконати лише команду tail. Щоб варіант 4 був коректним, нам слід передати вивід команди head у команду tail -n 2 виконавши head -n 3 animals.csv | tail -n 2 > animals-subset.csv.

Передача виводу іншій команді

У нашому прикладі ми шукаємо файл з найменшою кількістю рядків, ми використовуємо два проміжні файли lengths.txt і sorted-lengths.txt для зберігання результатів. Це заплутаний спосіб роботи, оскільки навіть після того, як ви зрозумієте, що роблять wc, sort і head, ці проміжні файли ускладнюють розуміння того, що відбувається. Ми можемо полегшити розуміння, запустивши sort і head разом:

$ sort -n lengths.txt | head -n 1
  9  methane.pdb

Вертикальна риска | між двома командами називається каналом. Він вказує терміналу, що ми хочемо використовувати вивід команди ліворуч як вхідні дані для команди праворуч.

Це усунуло необхідність у файлі sorted-lengths.txt.

Поєднання декількох команд

Ніщо не заважає нам з’єднувати канали послідовно. Ми можемо, наприклад, надсилати вивід wc безпосередньо до sort, а потім отриманий результат - до head. Це усуває необхідність у будь-яких проміжних файлах.

Ми почнемо з використання каналу для відправки виводу wc до sort:

$ wc -l *.pdb | sort -n
   9 methane.pdb
  12 ethane.pdb
  15 propane.pdb
  20 cubane.pdb
  21 pentane.pdb
  30 octane.pdb
 107 total

Потім ми можемо відправити цей вивід через інший канал в head, отже конвеєр стає наступним:

$ wc -l *.pdb | sort -n | head -n 1
   9  methane.pdb

Це точно так само, як математик вкладає функції на кшталт log(3x) і каже ‘логарифм трьох, помноженого на x’. У нашому випадку, обчислюється ‘head від sort від підрахунку кількості рядків у файлах *.pdb’.

Перенаправлення та канали, використані в останніх кількох командах, проілюстровано нижче:

Перенаправлення та канали різних команд: "wc -l *.pdb" перенаправить
виведення до терміналу. "wc -l *.pdb > lengths" спрямує вивід до файлу
"lengths". "wc -l *.pdb | sort -n | head -n 1" побудує конвеєр, де
вихід команди "wc" є входом для команди "sort", вихід команди
команди "sort" є входом для команди "head", а вихід команди
"head" буде спрямовано до терміналу

З’єднання команд у конвеєр

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

  1. wc -l * > sort -n > head -n 3.
  2. wc -l * | sort -n | head -n 1-3.
  3. wc -l * | head -n 3 | sort -n
  4. wc -l * | sort -n | head -n 3.

Розв’язання Варіант 4 є рішенням. Символ каналу | використовується для під’єднання виводу однієї команди до входу іншої. Символ > використовується для перенаправлення стандартного виводу до файлу. Спробуйте у каталозі shell-lesson-data/exercise-data/proteins!

Інструменти, розроблені для спільної роботи

Ця ідея зв’язування програм разом є причиною успіху Unix. Замість того, щоб створювати величезні програми, які намагаються робити багато різних речей, програмісти Unix зосереджуються на створенні великої кількості простих інструментів, кожен з яких добре виконує одну роботу, і які добре працюють один з іншим. Ця модель програмування називається “канали та фільтри”. Ми вже бачили канали; а фільтр - це програма на кшталт wc або sort. яка перетворює потік вхідних даних у потік вихідних даних. Майже всі стандартні інструменти Unix можуть працювати таким чином: якщо їм не наказано робити інакше, вони читають зі стандартного вводу, роблять щось з прочитаним, і записують у стандартний вивід.

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

Розуміння читання каналу

Файл з назвою animals.csv (у папці shell-lesson-data/exercise-data/animal-counts) містить наступні дані:

2012-11-05,deer,5
2012-11-05,rabbit,22
2012-11-05,raccoon,7
2012-11-06,rabbit,19
2012-11-06,deer,2
2012-11-06,fox,4
2012-11-07,rabbit,16
2012-11-07,bear,1

Який текст проходить через кожен з каналів і кінцеве перенаправлення у конвеєрі нижче? Зауважте, що команда sort -r сортує у зворотному порядку.

$ cat animals.csv | head -n 5 | tail -n 3 | sort -r > final.txt

Підказка: створюйте конвеєр по одній команді за раз, щоб перевірити своє розуміння

Розв’язання

Команда head витягує перші 5 рядків з файлу animals.csv. Потім останні 3 рядки витягуються з попередніх 5 за допомогою команди tail. За допомогою команди sort -r ці 3 рядки сортуються у зворотному порядку і нарешті, виведення перенаправляється до файлу final.txt. Вміст цього файлу можна перевірити, виконавши команду cat final.txt. Файл повинен містити наступні рядки:

2012-11-06,rabbit,19
2012-11-06,олень,2
2012-11-05,єнот,7

Конструювання каналу

Для файлу animals.csv з попередньої завдання розглянемо наступну команду:>

$ cut -d , -f 2 animals.csv

Команда cut використовується для видалення або ‘вирізання’ певних частин кожного рядка у файлі, і cut очікує, що рядки буде розділено на стовпчики символом Tab. Символ, який використовується таким чином, називається відокремлювальним символом. У наведеному вище прикладі ми використовуємо опцію -d, щоб вказати кому як відокремлювальний символ. Ми також використали опцію -f, щоб вказати, що ми хочемо вилучити друге поле (стовпчик). Це призведе до наступного результату:

deer
rabbit
raccoon
rabbit
deer
fox
rabbit
bear

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

Розв’язання

$ cut -d , -f 2 animals.csv | sort | uniq

Який канал?

Файл animals.csv містить 8 рядків даних, відформатованих наступним чином:

2012-11-05,deer,5
2012-11-05,rabbit,22
2012-11-05,raccoon,7
2012-11-06,rabbit,19
...

Команда uniq має опцію -c, яка дає кількість входжень рядка у вхідних даних. Припустимо, що ваш поточний каталог має назву shell-lesson-data/exercise-data/animal-counts, яку команду слід використати, щоб створити таблицю, яка показує загальну кількість тварин кожного типу у файлі?

  1. sort animals.csv | uniq -c.
  2. sort -t, -k2,2 animals.csv | uniq -c.
  3. cut -d, -f 2 animals.csv | uniq -c
  4. cut -d, -f 2 animals.csv | sort | uniq -c.
  5. cut -d, -f 2 animals.csv | sort | uniq -c | wc -l

Розв’язання

Варіант 4 є правильною відповіддю. Якщо вам важко зрозуміти, чому, спробуйте виконати команди або фрагменти конвеєрів (переконайтеся, що ви перебуваєте у каталозі shell-lesson-data/exercise-data/animal-counts).

Конвеєр Неллі: Перевірка файлів

Неллі пропустила свої зразки через аналізатори і створила 17 файлів у каталозі north-pacific-gyre, описаному раніше. Для швидкої перевірки, починаючи з каталогу shell-lesson-data, Неллі набирає:

$ cd north-pacific-gyre
$ wc -l *.txt

На виході вона отримує 18 рядків, які виглядають наступним чином:

300 NENE01729A.txt
300 NENE01729B.txt
300 NENE01736A.txt
300 NENE01751A.txt
300 NENE01751B.txt
300 NENE01812A.txt
... ...

Тепер вона набирає наступне:

$ wc -l *.txt | sort -n | head -n 5
 240 NENE02018B.txt
 300 NENE01729A.txt
 300 NENE01729B.txt
 300 NENE01736A.txt
 300 NENE01751A.txt

Упс: один з файлів на 60 рядків коротший за інші. Коли вона повертається і перевіряє його, вона бачить, що зробила цей аналіз о 8:00 ранку в понеділок — хтось, можливо, користувався машиною на вихідних, і вона забула її перезавантажити. Перед тим, як повторно проаналізувати цей зразок, вона перевіряє, чи є файли, що містять забагато даних:

$ wc -l *.txt | sort -n | tail -n 5
 300 NENE02040B.txt
 300 NENE02040Z.txt
 300 NENE02043A.txt
 300 NENE02043B.txt
5040 total

Ці цифри виглядають добре — але що робить ця ‘Z’ у передостанньому рядку? Всі її зразки мають бути позначені ‘A’ або ‘B’; за попередньою домовленістю її лабораторія використовує ‘Z’ для позначення зразків з недостатньою інформацією. Щоб знайти подібні зразки, вона робить наступне:

$ ls *Z.txt
NENE01971Z.txt    NENE02040Z.txt

Звісно, коли вона перевіряє журнал на своєму ноутбуці, глибина не записана для жодного з цих зразків. Оскільки вже занадто пізно отримати інформацію в інший спосіб, вона повинна виключити ці два файли з аналізу. Вона може видалити їх за допомогою rm, але насправді є деякі аналізи, які вона може зробити пізніше, де глибина не має значення, тож натомість, пізніше їй доведеться бути обережною і вибирати файли за допомогою підстановочних виразів NENE*A.txt NENE*B.txt.

Видалення непотрібних файлів

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

  1. rm ?.txt
  2. rm *.txt
  3. rm * .txt
  4. rm *.*

Розв’язання

  1. Це призведе до вилучення файлів .txt з односимвольними назвами
  2. Це правильна відповідь
  3. Термінал розширить підстановочний символ * так, щоб він відповідав усім файлам у поточному каталозі, таким чином, команда спробує видалити всі знайдені файли і додатковий файл з назвою `.txt’.
  4. Термінал розширить *.*, щоб знайти всі файли з будь-яким розширенням, таким чином, ця команда видалить усі файли.

[workshop-repo]: [yaml]: http://yaml.org/

Ключові моменти

  • wc підраховує рядки, слова та символи у вхідних даних.

  • cat` виводить вміст своїх вхідних даних.

  • sort сортує вхідні дані.

  • head виводить перші 10 рядків вхідних даних.

  • tail виводить останні 10 рядків вхідних даних.

  • command > [file] перенаправляє вивід команди у файл (перезаписуючи наявний вміст).

  • command >> [file] додає вивід команди до файлу.

  • [first] | [second] є конвеєром: вихід першої команди використовується як вхід для другої.

  • Найкращий спосіб використання терміналу - це використання каналів для об’єднання простих одноцільових програм (фільтрів).