null

Заменяем *sh, grep, awk на Python в скриптах

Как и у любого *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

К списку статей

 

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

Основная сфера моей деятельности связана с поддержкой Solaris и оборудования Sun/Oracle, хотя в последнее время к ним прибавились технологии виртуализации (линейка Citrix Xen) и всякое разное от IBM - от xSeries до Power. Учусь на кафедре Вычислительной Техники НИУ ИТМО.

See you...out there!

http://www.facebook.com/profile.php?id=100001947776045
https://twitter.com/AnnoyingBugs

Ничего не найдено. n is 0