Сильная сторона UNIX-оболочки заключается в том, что она позволяет комбинировать программы для создания конвейеров), способных обрабатывать большие объемы данных.
В этом уроке показано, как это сделать и как повторять команды для автоматической обработки любого количества файлов.
Мы продолжим работу в проекте zipf, который должен содержать следующие файлы:
zipf/
└── data
├── README.md
├── dracula.txt
├── frankenstein.txt
├── jane_eyre.txt
├── moby_dick.txt
├── sense_and_sensibility.txt
├── sherlock_holmes.txt
└── time_machine.txt
Создадим такую иерархию файлов с помощью Google Colab.
Напомню, что Google Colab - это облачный сервис, предоставляющий интерфейс Jupyter Notebook, который работает на базе операционной системы GNU/Debian и позволяет обращаться к командной оболочке этой операционной системы.
Рассмотрим возможности Google Colab для работы с командной оболочкой.
Детально про командную оболочку в GNU/Linux по см. ссылке.
!¶Перед командами ставится символ !:
!pwd
!ls
/content sample_data zipf
Магическая команда %%shell превращает ячейку блокнота в полноценный файл командной оболочки:
%%shell
pwd
ls
/content sample_data zipf
Существует магическая команда %shell, которая превращает строку в командную оболочку:
%shell pwd
/content
Теперь создадим структуру каталогов.
Удалим созданный ранее каталог zipf, если он был в системе:
%%shell
if [ -d zipf ]; then
rm -rfv zipf
fi
Формируем структуру каталогов:
%%shell
mkdir zipf
cd zipf
mkdir data
cd data
wget https://raw.githubusercontent.com/dm-fedorov/pandas_basic/master/data/data.zip
unzip data.zip
pwd
ls
--2023-03-26 14:27:44-- https://raw.githubusercontent.com/dm-fedorov/pandas_basic/master/data/data.zip Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.109.133, 185.199.108.133, 185.199.110.133, ... Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.109.133|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 1992058 (1.9M) [application/zip] Saving to: ‘data.zip’ data.zip 100%[===================>] 1.90M --.-KB/s in 0.02s 2023-03-26 14:27:44 (87.6 MB/s) - ‘data.zip’ saved [1992058/1992058] Archive: data.zip inflating: dracula.txt inflating: __MACOSX/._dracula.txt inflating: frankenstein.txt inflating: __MACOSX/._frankenstein.txt inflating: jane_eyre.txt inflating: __MACOSX/._jane_eyre.txt inflating: moby_dick.txt inflating: __MACOSX/._moby_dick.txt inflating: README.md inflating: __MACOSX/._README.md inflating: sense_and_sensibility.txt inflating: __MACOSX/._sense_and_sensibility.txt inflating: sherlock_holmes.txt inflating: __MACOSX/._sherlock_holmes.txt inflating: time_machine.txt inflating: __MACOSX/._time_machine.txt /content/zipf/data data.zip jane_eyre.txt README.md time_machine.txt dracula.txt __MACOSX sense_and_sensibility.txt frankenstein.txt moby_dick.txt sherlock_holmes.txt
Чтобы увидеть, как оболочка позволяет нам комбинировать команды, давайте перейдем в каталог zipf/data и посчитаем количество строк в каждом файле.
Команда wc (сокращение от word count) сообщает, сколько строк, слов и букв содержится в одном файле:
%shell wc zipf/data/moby_dick.txt
22331 215832 1253891 zipf/data/moby_dick.txt
Только количество строк (указываем ключ -l):
%shell wc -l zipf/data/moby_dick.txt
22331 zipf/data/moby_dick.txt
Мы можем использовать wildcard (подстановочные символы), чтобы сразу указать набор файлов. Чаще всего используется подстановочный символ * (одна звездочка). Он соответствует нулю или более символов, поэтому zipf/data/*.txt соответствует всем текстовым файлам в каталоге data:
%shell ls zipf/data/*.txt
zipf/data/dracula.txt zipf/data/sense_and_sensibility.txt zipf/data/frankenstein.txt zipf/data/sherlock_holmes.txt zipf/data/jane_eyre.txt zipf/data/time_machine.txt zipf/data/moby_dick.txt
В то время как zipf/data/s*.txt соответствует только двум файлам, имена которых начинаются с s:
%shell ls zipf/data/s*.txt
zipf/data/sense_and_sensibility.txt zipf/data/sherlock_holmes.txt
Подстановочные символы расширяются, чтобы соответствовать именам файлов перед запуском команд, поэтому они работают одинаково для каждой команды. Это означает, что мы можем использовать их с wc для подсчета количества слов в книгах с именами, которые содержат подчеркивание:
%shell wc zipf/data/*_*.txt
21054 188460 1049294 zipf/data/jane_eyre.txt 22331 215832 1253891 zipf/data/moby_dick.txt 13028 121593 693116 zipf/data/sense_and_sensibility.txt 13053 107536 581903 zipf/data/sherlock_holmes.txt 3582 35527 200928 zipf/data/time_machine.txt 73048 668948 3779132 total
Подсчет количества строк в каждом файле:
%shell wc -l zipf/data/*.txt
15975 zipf/data/dracula.txt 7832 zipf/data/frankenstein.txt 21054 zipf/data/jane_eyre.txt 22331 zipf/data/moby_dick.txt 13028 zipf/data/sense_and_sensibility.txt 13053 zipf/data/sherlock_holmes.txt 3582 zipf/data/time_machine.txt 96855 total
Какая из этих книг самая короткая?
Мы можем проверить на глаз, когда файлов всего семь, а если бы их было восемь тысяч?
Наш первый шаг к решению — запустить команду:
%%shell
cd zipf/data
wc -l *.txt > lengths.txt
Символ "больше" > указывает оболочке перенаправить вывод команды в файл, а не печатать его. На экране ничего не появляется; вместо этого все, что могло появиться, ушло в файл lengths.txt. Оболочка создает этот файл, если он не существует, или перезаписывает его, если он уже существует.
Мы можем распечатать содержимое файла lengths.txt, используя cat, что является сокращением от con cat enate (потому что, если мы дадим ему имена нескольких файлов, он напечатает их все по порядку):
%%shell
cat zipf/data/lengths.txt
15975 dracula.txt 7832 frankenstein.txt 21054 jane_eyre.txt 22331 moby_dick.txt 13028 sense_and_sensibility.txt 13053 sherlock_holmes.txt 3582 time_machine.txt 96855 total
Теперь мы можем использовать sort для сортировки строк в этом файле:
%%shell
sort -n zipf/data/lengths.txt
3582 time_machine.txt 7832 frankenstein.txt 13028 sense_and_sensibility.txt 13053 sherlock_holmes.txt 15975 dracula.txt 21054 jane_eyre.txt 22331 moby_dick.txt 96855 total
На всякий случай мы используем в sort опцию -n, чтобы указать, что мы хотим сортировать по числам.
sort не изменяет lengths.txt. Вместо этого она отправляет свой вывод на экран так же, как wc ранее. Поэтому мы можем поместить отсортированный список строк в другой временный файл sorted-lengths.txt с помощью >:
%%shell
cd zipf/data
sort -n lengths.txt > sorted-lengths.txt
Создание промежуточных файлов с именами типа lengths.txt и sorted-lengths.txt работает, но отслеживать эти файлы и очищать их, когда они больше не нужны, — утомительное занятие.
Давайте удалим два файла, которые мы только что создали:
%%shell
cd zipf/data
rm lengths.txt sorted-lengths.txt
Мы можем получить тот же результат с меньшим количеством ввода, используя канал (pipe):
%%shell
cd zipf/data
wc -l *.txt | sort -n
3582 time_machine.txt 7832 frankenstein.txt 13028 sense_and_sensibility.txt 13053 sherlock_holmes.txt 15975 dracula.txt 21054 jane_eyre.txt 22331 moby_dick.txt 96855 total
Вертикальная черта | между командами wc и sort сообщает оболочке, что мы хотим использовать выходные данные команды слева в качестве входных данных для команды справа.
Выполнение команды с файлом в качестве входных данных имеет четкий поток информации: команда выполняет задачу над этим файлом и выводит результат на экран (рис. 3.1 а).
Однако при использовании каналов (pipe) информация иначе передается после первой команды. Вышестоящая по течению данных команда не читает из файла. Вместо этого она считывает вывод команды, находящейся ниже по течению (рис. 3.1 б).

Рисунок 3.1: Команды, связанные каналом
Мы можем использовать | для сборки каналов любой длины. Например, мы можем использовать команду head, чтобы получить только первые три строки отсортированных данных, которые показывают нам три самые короткие книги:
%shell wc -l zipf/data/*.txt | sort -n | head -n 3
3582 zipf/data/time_machine.txt 7832 zipf/data/frankenstein.txt 13028 zipf/data/sense_and_sensibility.txt
Мы всегда можем перенаправить вывод в файл, добавив > shortest.txt в конец канала, тем самым сохранив ответ для дальнейшего использования.
На практике большинство Unix-пользователей создавали бы этот конвейер шаг за шагом, как и мы: начиная с одной команды и добавляя другие команды одну за другой, проверяя вывод после каждого изменения.
![]() |
Обсудить публикацию в [Telegram-канале] |