Однострочник месяца на Perl: Дело о совпадающих UID
Автор: Ben Okopnik
Перевод: Павел Соколов


Глава 1

Электронное письмо было кратким и переходило сразу к делу:

Вумерт,
Буду краток и сразу перейду к делу.
Слияние трёх компаний.
Нервный сисадмин.
3000+ пользователей.
/etc/passwd.
UIDы.
Всего лучшего,
Фринк Ублик

Вумерт Фунли, Реалистичный Компьютерный Детектив, ухмыльнулся: ответы клиента во время разговора по телефону были громкими и бессвязными, а сам разговор пестрел фразами вроде "Не работает!" и "Помогите!". Вумерт послал Фринка на место, чтобы осмотреться, и письмо стало успешным результатом этой рекогносцировки. Всё, что осталось, -- это найти решение, имея в запасе всего несколько часов, перед тем, как клиент завершит рабочий день. Вумерт решил использовать время продуктивно. Так, где его любимая подушка?

Глава 2

Освежённый и во всеоружии, Вумерт появился у клиента и сразу же столкнулся со взвинченным Фринком.

- Вумерт, это ужасно! Файл слишком длинный для ручного поиска, и это всё только множество UID. Сисадмин кается, бесится и паникует по очереди, а от его волос уже практически ничего не осталось. И что мы сможем здесь сделать?

- Нет проблем, дружище... ой, извини. Я вернулся из Канберры всего несколько часов назад и некоторое влияние всё ещё чувствуется. Могу сказать по своему ужасному опыту, что завтра будет ещё хуже: я должен быть утром в Далласе, после обеда в Нью-Йорке, а вечером - в Тель Авиве. Я посоветовал бы тебе надеть наушники или исчезнуть из моих окрестностей, пока акцент не выветрится. О-о-о, эти опасности путешествий...

Фринк заметно расстроился.

- Вумерт, ты не принимаешь ситуацию всерьёз. Разве ты не видишь, что это огромная проблема?

- А, это? Расслабься, не принимай к сердцу. Это совсем не так плохо, Фринк, как выглядит; на самом деле...

Вумерт проворно извлёк из кармана свои любимые перчатки для печати и натянул их.

- ...С Perl это просто тривиально. Что нам надо сделать - так это дать сисадмину пару инструментов для командной строки, которыми он может воспользоваться для решения этой проблемы, и - так как он использует bash - он сможет вытаскивать их клавишей "верх" каждый раз, как они ему понадобятся. Ну вот!


perl -F: -walne'$h{$F[2]}.="$F[0] ";END{$h{$_}=~/ ./&&print"$_: $h{$_}"for keys%h}' /etc/passwd

Список совпавших UID, вместе с соответствующими им именами пользователей пробежал по экрану после того, как Вумерт нажал "Enter". Оба, Вумерт и Фринк, отметили с интересом, что для UID0 было три записи -

0: root sashroot kill3r

- Так-так. Кажется кому-то удалось влезть и создать себе запись UID0 (root). "sashroot" - нормально, это "standalone shell" (обособленная оболочка) для восстановительных работ, но "kill3r"? Хорошо, мы сообщим клиенту; а тем временем продолжим с нашей проблемой. У сисадмина теперь есть список всех дубликатов - а их, кажется, не так много - но поиск следующего свободного UID может быть проблемным. Так что вот второй инструмент:


perl -wle'{getpwuid++$n&&redo;print$n}'

- Это должно дать ему хороший задел в решении этих проблем. Что до нас - мы направляемся домой!

Глава 3

Когда они вернулись в дом к Вумерту и расположились перед камином - ночь была холодной и ветреной - Фринк выжидающе посмотрел на Вумерта. Заметив взгляд, Вумерт рассмеялся:

- Знаю, знаю. Мне следует объясниться, не так ли? Ореол тайны - штука приятная, но это ничто перед удовольствием от обучения. Вот, начнём с первой строчки:


perl -F: -walne'$h{$F[2]}.="$F[0] ";END{$h{$_}=~/ ./&&print"$_: $h{$_}"for keys%h}' /etc/passwd

- Во-первых, взгляни на ключи командной строки, которые я использовал:

-w Включить предупреждения
-a Авторазделение (см. "-F")
-l Включить обработку конца строки
-n Неявный непечатаемый цикл
-e Выполнить следующие команды
-F: Использовать ":" в качестве разделителя для ключа авторазделения "-a"

Если ты помнишь наше последнее приключение, все перечисленные ключи, кроме "-a" и "-F" уже знакомы тебе. Авторазделение разбивает строчки, прочитанные "-n" или "-p", используя пробельные символы (whitespace) [1] в качестве разделителя по умолчанию и сохраняя результат в массив "@F". Ключ "-F" опционально переопределяет символ разделителя.

Так как мы читаем файл "/etc/passwd", давай взглянем на формат его отдельных строк:

borg:x:1026:127:All your base are belong to us!:/home/borg:/bin/bash

Здесь семь стандартных полей, расположенных в порядке "имя - пароль - UID - GID - информация о пользователе - домашняя директория - оболочка". Сейчас нас интересуют только имя и UID; что я собираюсь сделать - это построить хэш (hash) или словарный массив - это очень важная структура данных в Perl, одна из трёх базовых - который будет содержать UID (3е поле) как индекс (key), и имя (1-е поле), за которым следует пробел, как значение (value), для каждой записи в "/etc/passwd":

$h{$F[2]}.="$F[0] "

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

$h{$_}=~/ ./&&print"$_: $h{$_}"for keys%h}

Я смотрю, ты всё ещё выглядишь озадаченным. Давай я напишу это в более читабельной форме:

for ( keys %h ){                # Цикл по хэшу "%h" 
    if ( $h{$_} =~ / ./ ){      # В значении есть пробел, за которым есть ещё символ?
        print "$_: $h{$_}\n";   # Если да, распечатать UID, двоеточие, пробел и значение
    }
}

Если ты поразмыслишь, ты увидишь, что единственная вещь, которая может подойти под регулярное выражение - это значение, в котором содержится более одного имени, то есть в случае совпавшего UID.

- Всё правильно, теперь я могу видеть результат. А что там со второй командой, инструментом "следующий свободный UID"?

- А, ты имеешь в виду эту:


perl -wle'{getpwuid++$n&&redo;print$n}'

Это не более, чем короткий цикл, в котором я проверяю, существует ли UID, указанный в "$n". Если команда срабатывает нормально, что значит, что уже есть используемый UID, равный "$n" - вызывается "redo", "$n" увеличивается на единицу и тест снова выполняется. Если же команда выпадает с ошибкой, "$n" распечатывается в STDOUT и программа завершается. Полезно и несложно. Немного работы и они со всем разберутся. Вот взлом системы безопасности - дело другое, но, по крайней мере, они о нём знают...


[1] Прим. пер. Имеются в виду символы вроде табуляции и перевода каретки, которые не отображаются, но управляют выводом.


Copyright © 2002, Ben Okopnik. Copying license http://www.linuxgazette.com/copying.html
Published in Issue 85 of Linux Gazette, December 2002


Вернуться на главную страницу