Конвейеры в Linux

0
515
views

В этом руководстве мы рассмотрим общее определение философии Unix и изучим ключевые элементы хорошо написанного скрипта. Также мы познакомимся с такими составными частями скриптов, как оператор конвейера, работа с stdin и stdout. Наконец, мы рассмотрим, как применить эти элементы в наших скриптах на ruby/bash!

Содержание

Что такое философия Unix?

Философия Unix изначально была определена Кеном Томпсоном и представляет собой набор хороших практик, которые отличают минималистское и модульное программное обеспечение. Все основные утилиты Unix (такие как find или grep) следуют этим практикам, так что, наверное, практики действительно хорошие, верно?

Оригинальная цитата, задокументированная Дагом Макилроем, содержит следующие пункты:

  1. Заставьте каждую программу делать одну вещь хорошо. Для выполнения новой работы создавайте новую программу, а не усложняйте старые, добавляя новые фичи.
  2. Ожидайте, что вывод каждой программы станет входными данными для другой, пока неизвестной программы. Не загромождайте вывод лишней информацией. Избегайте строгих столбцовых или двоичных форматов ввода. Не настаивайте на интерактивном вводе.
  3. Проектируйте и создавайте программы, и даже операционные системы, которые можно опробовать на ранней стадии, в идеале — в течение нескольких недель. Не стесняйтесь выбрасывать неуклюжие части и собирать их заново.
  4. Для облегчения программирования используйте инструменты, а не неквалифицированную помощь, даже если для их создания придется сделать крюк и даже если знаете, что после окончания работы некоторые из них будут выброшены.

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

Что такое конвейер?

Конвейер — это непрерывный цикл программ, в котором stdout одной программы используется как stdin следующей. В оболочках bash/zsh/fish конвейер создается при помощи оператора |. Он часто используется для дальнейшего запроса информации. Например, если вы хотите подсчитать, сколько файлов формата markdown находится в вашем каталоге, вы можете сделать следующее:

find . -iname '*.md' | wc -l

Давайте разберем это по частям, чтобы вы могли понять, что к чему.

Сначала используется команда find, которая находит все файлы формата markdown в текущей директории. У меня это выглядит следующим образом:

$ find . -iname '*.md'
./posts/20230814T124722/README.md
./posts/20230703T214043/README.md
./posts/20230616T234323/README.md
./posts/20230625T223158/README.md
./posts/20230731T212528/README.md
./posts/20230807T203924/README.md
./posts/20230804T140043/README.md
./REPL Driven Development - For not so smart developers.md
./Como escrever uma CLI CRUD utilizando ScyllaDB + Ruby.md
./Linux filters - How to streamline text like a boss.md

Если вы хотите просто перечислить имена файлов без их подсчета, вы можете даже перенаправить их в команду sort, и тогда список будет составлен в алфавитном порядке:

$ find . -iname '*.md' | sort
./Como escrever uma CLI CRUD utilizando ScyllaDB + Ruby.md
./Linux filters - How to streamline text like a boss.md
./REPL Driven Development - For not so smart developers.md
./posts/20230616T234323/README.md
./posts/20230625T223158/README.md
./posts/20230703T214043/README.md
./posts/20230731T212528/README.md
./posts/20230804T140043/README.md
./posts/20230807T203924/README.md
./posts/20230814T124722/README.md

Видите, как вывод первой команды, find, был передан команде sort, которая вернула его отсортированным? В этом и заключается смысл написания небольших инструментов: их можно легко компоновать в различные конвейеры, получая отличные результаты.

В начальном примере мы использовали команду wc, которая считает то, что получает из stdin. Тут мы использовали флаг -l для подсчета строк. Собрав все это вместе, мы получили результат:

$ find . -iname '*.md' | wc -l
      10

Стандартные потоки ввода и вывода (stdin и stdout)

Stdin (стандартный ввод) и stdout (стандартный вывод) — это основные способы связи компьютера с внешним миром.

Стандартным потоком ввода, stdin, обычно называют все, что связано со взаимодействием с пользователем (например, ввод текста, выбор элемента из списка и т.д.), но это можно понимать и более обобщенно. В принципе, stdin можно считать поставщиком информации по умолчанию, независимо от того, поступает ли она от другой программы или от взаимодействующего с ней пользователя.

Стандартным потоком вывода, stdout, обычно называют вывод результатов работы на экран (это может быть терминал, браузер или что угодно). В конвейерах стандартный вывод подавляется, и результаты работы одной команды, вместо вывода на экран, перенаправляются как stdin в следующую команду.

Что определяет плохой скрипт и как превратить его в хороший?

Дать точное определение того, что мы считаем хорошим скриптом, довольно сложно из-за множества переменных. Лучше давайте пойдем от противного и определим, как выглядит плохой скрипт с точки зрения философии Unix.

Игнорирование коммуникации stdin и stdout

Вы когда-нибудь использовали CLI, которые имеют причудливый способ отображения информации (анимированный ввод, крутящийся спиннер)? Это выглядит очень круто, когда вы просто хотите использовать эти инструменты сами по себе. Но когда вы пытаетесь поместить их в конвейер, они ломают весь процесс. Поэтому, создавая собственные инструменты, всегда используйте стандартные коммуникации в качестве основного способа получения и извлечения информации. Также отдавайте предпочтение использованию опций, а не хардкодингу значений (например, --output=json или -o json).

В Ruby гораздо меньше шансов выстрелить себе в ногу, поскольку способ получения данных, который мы изучаем, уже достаточно хорошо работает со стандартным вводом. Но важно внимательно подойти к проектированию скрипта.

Например, рассмотрим простой скрипт printname.rb:

#!/usr/bin/env ruby

puts 'Type your name: '
name = gets.chomp

puts "Your name is: #{name}"

Этот скрипт, на первый взгляд, может работать как сам по себе, так и в конвейере. Но обратите внимание на вывод, производимый в обоих случаях:

$ echo "Cherry Ramatis" | ./printname.rb
Type your name:
Your name is: Cherry Ramatis
$ ./printname.rb
Type your name:
Cherry Ramatis
Your name is: Cherry Ramatis

Вы можете заметить, что мы всегда печатаем сообщение «Type your name». У нас есть два варианта:

  • Сделать так, чтобы имя можно было передать в параметрах флага (--name="Cherry Ramatis")
  • Вообще отбросить сообщение «Type your name», превратив скрипт в более «конвейерный».

Монолитные скрипты

Иногда вы пишете инструмент и быстро замечаете, что в процессе написания его объем увеличился. То, что должно было быть небольшим инструментом, превращается в большой проект с различными интерактивными шагами. Когда это достигает такой точки, очень соблазнительно просто продолжать развивать инструмент, добавляя множество подкоманд.

Но философия Unix говорит нам, что написать инструмент, который хорошо делает одну вещь, более ценно, чем написать гигантскую громадину.

Поэтому, вместо того, чтобы создавать большой CLI с целым набором команд, подумайте о написании отдельных небольших инструментов, которые будут взаимодействовать и дополнять друг друга.

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

$ list_songs | sort | update_song

list_songs и update_song — разные скрипты. Они взаимодействуют через stdin и stdout, позволяя пользователю добавлять любую команду и получать одинаковое поведение. В этом примере мы передаем команду sort, поскольку хотим просмотреть список в отсортированном виде.

Бонус: оператор ! в vim

Возрадуйтесь, пользователи Vim! Настал наш звездный час. Vim — это что-то вроде стандартного редактора в Unix-системах. И он обладает рядом преимуществ, таких как взаимодействие с двоичными файлами непосредственно в буфере с помощью оператора !.


От редакции Techrocks. У нас есть пара интересных статей о Vim:

И статья об использовании восклицательного знака: «8 потрясающих способов использования символа (!) в командах в Linux».


В Vim вы можете нажать !! в обычном режиме, чтобы ввести в ваш мини-буфер команду, подобную :.!. Команда, которую вы введете после оператора !, будет использована с текущей строкой как stdin. Потрясающе, правда? Давайте разберем пример.

Рассмотрим следующий скрипт на языке Ruby:

#!/usr/bin/env ruby

STDIN.each do |line|
 puts "- #{line}"
end

В этом скрипте мы перебираем данные, полученные через стандартный поток ввода, и распечатываем их в stdout со знаком — в начале. Теперь мы можем взаимодействовать с vim.

Другие варианты использования восклицательного знака:

  • !}: заполнение !-команды от текущей строки до конца абзаца.
  • !/pattern: заполнение !-команды от текущей строки до первого вхождения шаблона в буфере.
  • !G: заполнение !-команды от текущей строки до конца файла.
  • !4j: заполнение !-команды от текущей строки на 4 строки ниже.

Заключение

В этой небольшой статье я попытался привнести больше контекста в то, что я часто делаю в своей повседневной жизни: автоматизацию. Я надеюсь, что моя статья будет вам полезна! Да пребудет с вами сила.

Перевод статьи «Linux filters — How to streamline text like a boss».

ОСТАВЬТЕ ОТВЕТ

Please enter your comment!
Please enter your name here