Как и у любого *nix-администратора, мои первые скрипты использовали *sh, sed, awk, cut, head -1 и другие утилиты. Я мучался, когда где-то попадался пробел, так как приходилось переопределять IFS, страдал от избыточного кода, например такого:
FIELD1=$(cat /etc/passwd | head -1 | cut -d: -f1)
FIELD2=$(cat /etc/passwd | head -1 | cut -d: -f2)
А потом я изучил Python, и оказалось, что программирование на английском языке (а не на закорючках, как в sh/perl) - это удобно:
field1,field2 = file('/etc/passwd').readline().split(':')[0:2]
Самые отчаянные поклонники Python могут заменить интерактивный *sh на IPython.
Текстовые утилиты
echo
Аналог команды echo - это инструкция print:
print 'Hello, world!'
Форматирование выполняется через оператор %:
import os
print 'Hello, %s' % os.environ['LOGNAME']
print 'Hello, %s on host %s' % (os.environ['LOGNAME'], os.environ['HOST'])
print 'Hello, %(LOGNAME)s on host %(HOST)s' % os.environ
who = 'myaut'
from_who = 'Python'
print 'Hello, %(who)s from %(from_who)s' % locals()
cat/head/tail
Единственная задача утилиты cat - это открыть некоторый файл, чтобы обработать его. В Python нам нужно будет вызвать встроенную функцию, а чтобы прочитать его построчно, применить цикл for к этому объекту.
import sys
inf = file('/etc/passwd')
for line in inf:
sys.stdout.write(line)
Обратите внимание, что новый блок отделяется отступами (предпочтительней всего использовать 4 пробела, как того требует PEP 8) и начинается с двоеточия, а заканчивается уменьшением отступов. Эта особенность Python давно стала объектом жарких споров, и приходится признать, что ее преимущество - жесткое требование к структурированию кода - не перекрывает недостатков: трудности с копированием/переносом кода, путаница при использовании переноса строки \.
Отмечу, что при чтении файла, Python помещает перенос строки \n в переменную line, а print как и echo добавляет его, так что этот код можно переписать так:
inf = file('/etc/passwd')
for line in inf:
line = line[:-1]
print line
Утилиты head и tail будут выглядеть еще проще. Эта конструкция выведет последние 10 строк (точнее, строки от -10й, то есть 10й с конца, до -1й, то есть последней):
for line in for line in inf.readlines()[-10:]:
...
grep/sed
Для самых простых вариантов использования grep можно использовать встроенные функции и операторы строки:
# '^myaut'
if line.startswith('myaut'):
print line
# '/bin/true$'
if line.endswith('/bin/true'):
print line
# 'daemon'
if 'daemon' in line:
print line
Если же потребуется более сложное регулярное выражение, то придется привлечь модуль re:
import re
...
if re.match('^\w+:x:\d{1,2}:', line):
print line
Для реализации sed нужно использовать метод re.sub:
print re.sub('^\w+:x:(\d+).*', r'\1', line)
awk/cut
Для того, чтобы разделить строку на соответствующие ей поля, как уже было показано в начале статьи, разделить строку на поля помогут методы split() и rsplit(). Они возвращают поля в виде списка, а элементы списка в свою очередь в Python можно присваивать одновременно множеству переменных:
user, _, uid, _, gecos, _, _ = line.split(':')
if int(uid) < 10:
print user, uid, gecos
С помощью анонимной переменной _ можно проигнорировать присвоение. Первый аргумент функции split - это строка-разделитель (не список разделителей, единственное исключение - значение None, обозначающее все whitespace-символы), а второй - максимальное количество разделений, которое можно сделать.
Для более сложных ситуаций можно использовать уже упомянутый модуль re.
user, _, uid, _, gecos, _, _ = re.findall('[^:]+', line)
Или так:
m = re.match('^(\w+):x:(\d+):', line)
if m:
user, uid = m.groups()
Кроме того, можно задействовать модуль csv.
Вызываем внешние команды
Часто скрипт взаимодействует с множеством административных утилит *nix, и чтобы вызвать внешнюю утилиту из Python нам потребуется модуль subprocess. Он содержит две функции: call() для синхронного вызова подпроцесса и Popen() для асинхронного и создания конвееров. Первая вернет код возврата, а вторая - специальный объект Popen, который позволит взаимодействовать с процессом:
import subprocess
ps = subprocess.Popen(['ps', '-ef'],
stdout = subprocess.PIPE)
for line in ps.stdout:
if line.startswith('UID'):
continue
line = line[:-1]
user, pid, ppid, c, stime, tty, time, name = line.split(None, 7)
if user == 'myaut':
print user, pid, name
ps.wait()
Преимущество языка Python заключается в удобной обработке данных - ведь это язык общего назначения. Давайте посчитаем, сколько процессов запустил каждый пользователь, используя defaultdict (словарь с значением по-умолчанию):
import subprocess
from collections import defaultdict
proc_count = defaultdict(int)
...
for line in ps.stdout:
...
user = line.split()[0]
proc_count[user] += 1
ps.wait()
for user, count in proc_count.items():
print user, count
Кто-то может заметить, что использование Python в связке с subprocess слишком громоздко (замечу, что в IPython для внешних процессов есть специальный синтаксис: lines = !ls -l), но ведь множество функций, требующих в обычном шелле вызова внешних утилит в Python уже реализованы в виде модулей стандартной библиотеки. Это и shutil, и tarfile/zipfile, работающие с архивами, и, кстати позволяющие не распаковывать архив во временную директорию, и xml.etree/beautifulsoup для парсинга XML и HTML соответственно. Последний я использовал, чтобы выдрать Solaris-пакеты из архива SunFreeware.
Кроме того, есть богатый набор модулей для работы с сетью: socket и urllib помогут вам определить доступность сетевого сервиса, а paramiko - выполнить удаленную команду через ssh. Многие программы предоставляют привязки (биндинги, bindings) к Python, ну а если и не предоставляют, то к вашим услугам библиотека ctypes - привязки с API на языке C. Поистине, в Python включены батарейки.
Хорошим справочником по модулям Python для задач системного администрирования является книга "Python в системном администрировании UNIX и Linux".

Проверяем свой веб-сервер
В качестве примера поделюсь скриптом, проверяющим работоспособность веб-сервера (в данном случае - Glassfish, приложение в котором слегка текло), а в случае сбоя - собирающего отчет и перезагрузающего его.
webchecker.py