Por que eu devo ler este artigo:Conhecer uma linguagem de script e internalizar uma cultura de automação permite que se delegue o trabalho repetitivo para quem faz isso melhor (o computador), e libera tempo precioso dos profissionais de TI para se dedicarem a projetos e outras atividades mais complexas. O objetivo deste artigo é apresentar a linguagem Python como um meio agradável e simples de automatizar tarefas do dia a dia e permitir que os sysadmins produzam mais com menos esforço.

Conhecendo mais sobre Python

Nos últimos anos acompanhamos o crescimento exponencial dos ambientes de TI, tanto em termos de complexidade quanto de quantidade de equipamentos e aplicações, os quais demandam gerenciamento constante. Existem dois caminhos para lidar com esse crescimento: contratar mais profissionais (ou como acontece muitas vezes, simplesmente sobrecarregar os atuais) ou investir em automação.

Automação de tarefas repetitivas não é nenhuma novidade, e é o motivo pelo qual os computadores foram criados. A questão é que cada vez mais encontramos profissionais que, muitas vezes por falta de conhecimento, se transformam em verdadeiros escravos das aplicações que cuidam, executando diariamente diversas tarefas repetitivas sem imaginar que pode existir uma solução melhor.

Com as iniciativas de Cloud Computing cada vez mais presentes, onde o provisionamento de servidores e aplicações é realizado com poucos cliques, a automação é ainda mais importante. É difícil imaginar como dar suporte a um ambiente com, por exemplo, 1000 servidores virtuais, sem rotinas automatizadas. O AWS (Amazon Web Services) e o OpenStack são dois exemplos de plataformas de cloud que fornecem diversos serviços de automação que permitem o uso de Python (esse é um assunto que provavelmente daria um artigo interessante no futuro).

Este artigo se propõe a apresentar uma breve introdução à programação utilizando a linguagem Python, e com base nos conceitos apresentados, expor alguns cenários de automação comuns no dia a dia dos profissionais de TI. A ideia é que estes cenários sirvam de building blocks para a construção de soluções específicas para os problemas de cada um. Assumimos como premissa que os leitores possuem ao menos uma noção básica de programação, do tipo que se aprende no colégio, faculdade ou cursos livres.

Os leitores podem se perguntar: por que Python? Existem outras ótimas linguagens de script que podem ser utilizadas para automação, como Perl, os diversos dialetos de shell-script, Power Shell, VBScript, para citar algumas. A opção por Python foi direcionada por alguns fatores, baseados em experiência pessoal:

  • Simplicidade de sintaxe: Perl é uma linguagem excelente e talvez mais poderosa em alguns aspectos, mas iniciantes têm alguma dificuldade em entender a sintaxe e se confundem com a quantidade de símbolos utilizados pela linguagem;
  • Disponibilidade em várias plataformas: Power Shell e VBScript são nativas do Windows, assim como shell-script é nativo do Unix. A ideia é aprender apenas uma linguagem que possa ser utilizada em diversas plataformas;
  • Disponibilidade de bibliotecas: Python possui uma coleção enorme de bibliotecas, muitas delas multiplataforma, que economizam muito tempo no desenvolvimento;
  • Uso futuro: Python é uma linguagem de uso muito abrangente, de automação de rotinas a frameworks completos de desenvolvimento web. Se o leitor quiser investir em outras áreas da programação, é possível aproveitar o conhecimento já adquirido.

Apresentados os fatores, que fique claro que esta não é uma discussão do tipo “linguagem X é melhor do que linguagem Y”. O objetivo aqui é, acima de tudo, apresentar uma linguagem aos que ainda não programam. Para os leitores que já têm a sua linguagem de script favorita, é sempre possível aprender uma coisa ou outra observando como as tarefas podem ser feitas em outra linguagem.

Introdução à linguagem Python

Python é uma linguagem de script interpretada e multiparadigma (ver BOX 1), que foi criada por Guido van Rossum no final da década de 1980, enquanto estava no CWI – Centrum Wiskunde & Informatica (Instituto Nacional de Pesquisas Matemáticas e de Ciências da Computação) em Amsterdam, na Holanda.

Desde o seu início, a linguagem foi concebida para ser simples de entender e possuir uma biblioteca padrão bastante abrangente, que possibilitasse alta produtividade por parte do programador. A documentação oficial (veja a seção Links) chama essa filosofia de “batteries included”, ou seja, a linguagem já vem com as bibliotecas que são mais comuns e mais utilizadas pela comunidade.

Linguagem Interpretada e Multiparadigma

Linguagem interpretada é aquela cujo programa não é compilado para um arquivo executável, mas sim executado linha a linha por um interpretador.

Multiparadigma é uma linguagem que suporta diversos paradigmas de desenvolvimento. Atualmente temos como paradigmas mais comuns os de programação imperativa, orientada a objetos e funcional.

Python vive um momento de transição, em que foi criada uma nova versão da linguagem, que possui diferenças que causaram uma ruptura em algumas funcionalidades da versão anterior. Na prática é possível utilizar qualquer uma das duas versões, e em termos de produtividade as duas versões são equiparáveis.

Instalação do interpretador e bibliotecas

Todos os exemplos deste artigo foram criados utilizando a versão 3.2 do ActivePython, que é distribuído pela ActiveState (veja a seção Links), para Windows, Linux e Mac OSX. É recomendado que se utilize a versão de 32 bits porque é a que possui a maior quantidade de bibliotecas adicionais disponíveis no repositório. Os scripts e exemplos mostrados aqui devem funcionar tanto na versão anterior como na última do Python sem maiores problemas, independentemente de distribuição.

Modo batch e modo interativo (REPL)

Em Python existem dois modos de acionarmos o interpretador: o modo batch, que é o mais comum e é o que utilizaremos sempre que quisermos executar um programa, e o modo interativo, também chamado de REPL (acrônimo para “Read-Eval-Print Loop”).

No modo batch, para executar um programa, basta invocar o interpretador (assumindo que este esteja no PATH do sistema operacional) e passar o nome do programa:

python programa.py

Ao executar esse comando, o interpretador Python irá executar as instruções contidas no arquivo programa.py.

Se executarmos apenas o interpretador, sem passar nenhum programa, veremos uma saída no prompt de comando semelhante ao exemplo:

Python 2.7.5 (default, Aug 21 2013, 21:18:58)

Type "help", "copyright", "credits" or "license" for more information.

>>>

Neste prompt é possível digitar comandos Python e ver os seus resultados, sem precisar escrever um programa completo. Veja alguns exemplos na Listagem 1.

Listagem 1. Exemplo do REPL do Python.

  >>> 2+2
  4
  >>> print('Hello World')
  Hello World
  >>> 2+2
  4
  >>> 3/2
  1
  >>> 3.0/2
  1.5
>

No nosso exemplo é possível encontrar algumas operações aritméticas e um comando de exibição de texto na tela (print). O REPL é bastante útil quando queremos realizar um teste simples, com poucas linhas, e não queremos escrever um programa para isso, ou simplesmente quando queremos analisar algum dado de forma interativa, pouco a pouco.

Variáveis e tipos de dados

Variáveis são os elementos fundamentais de qualquer linguagem de programação. Representam uma forma de guardarmos valores em memória para serem utilizados posteriormente durante a execução do nosso programa. Outra forma de pensar no conceito de variáveis é imaginar que estamos nomeando um determinado valor.

Em Python não há um comando especial para declaração de variáveis. Para criarmos uma variável basta atribuirmos um valor a um nome, utilizando o sinal de igual (=). Veja o exemplo no REPL: >>> num = 10 >>> num 10

Nesse exemplo é possível verificar que foi criada uma variável de nome “num” com valor 10. Ao digitar o nome da variável no REPL, este nos retorna o valor atribuído a “num”.

Em se tratando de valores numéricos, podemos supor que é possível realizar operações aritméticas, como exposto na Listagem 2.

Listagem 2. Exemplos de operações aritméticas.

  >>> a = 2
  >>> b = 2
  >>> a + b
  4
  >>> c = a + b
  >>> c
  4
  >>> d = a * b
  >>> d
  4
  >>> e = a / b
  >>> e
  1
  >>> f = a ** b
  >>> f
  4
  >>> g = a - b
  >>> g

Neste exemplo criamos as variáveis “a” e “b”, realizamos algumas operações com os seus valores, e guardamos o resultado de cada operação em outra variável (na ordem, soma, multiplicação, divisão, exponenciação e subtração).

Todos esses exemplos possuem uma coisa em comum: utilizam números inteiros. Isso nos leva ao assunto dos tipos de dados em Python.

Existem dois grandes grupos de tipos de dados em Python, os escalares, que guardam um valor por vez, e as listas, que guardam vários valores de uma vez.

Os tipos de dados escalares em Python são quatro:

  • int: números inteiros (1, 10, 100);
  • float: números de ponto flutuante (1.5, 3.2);
  • str: string, texto, delimitado por aspas, simples ou duplas;
  • bool: verdadeiro ou falso (valores True ou False).

O tipo do dado é determinado dinamicamente pelo interpretador durante a execução, de modo a garantir que não seja realizada nenhuma operação inválida, como demonstra o código da Listagem 3.

Listagem 3. Operações inválidas.

  >>> nome='Mateus'
  >>> 10 / nome
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
  TypeError: unsupported operand type(s) for /: 'int' and 'str'

Recebemos um erro ao tentar dividir um número inteiro por uma string, porque são valores incompatíveis para essa operação.

No entanto, é válido operarmos valores inteiros com valores float, pelo fato dos dois serem numéricos:

>>> 10/2.75

3.6363636363636362

No caso de uma operação entre int e <>float, o resultado é sempre <>float.

Da mesma forma que não é preciso declarar variáveis, não é preciso indicar o tipo de dado, o interpretador faz esse trabalho.

Os tipos de dados escalares são muito utilizados e estão presentes na maioria das linguagens de programação, com suas particularidades. O que torna Python uma linguagem poderosa são as suas listas, que permitem a manipulação de conjuntos de dados.

Existem quatro tipos de listas em Python:

  • tuple: conjunto de valores imutável;
  • array: conjunto de valores mutável;
  • dict: tabela de chaves e valores
  • set: conjunto de valores distintos.

Os tipos tuple, array, set e str (que afinal de contas, é uma lista de caracteres) possuem em comum as formas de acesso que chamamos de indexing (acesso por índice) e slicing (fatiamento, em uma tradução livre).

Indexing é útil para extrairmos um elemento por vez de uma lista. Veja um exemplo na Listagem 4.

Listagem 4. Acesso a elementos de uma lista utilizando indexing.

  >>> x = ['a', 'b', 'c'] #lista com os elementos a, b e c
  >>> x[0]  #retorna o elemento na posicao 0
  'a'
  >>> x[1]  retorna o elemento na posicao 1
  'b'
  >>> x[2]  retorna o elemento na posicao 2
  'c'
  >>> s="Jose da Silva" #mesma coisa, mas utilizando uma string
  >>> s[0]
  'J'
  >>> s[1]
  'o'

Slicing é útil para extrairmos um conjunto de elementos de uma lista. O resultado é uma nova lista ou uma nova string, dependendo do tipo utilizado. Veja um exemplo na Listagem 5.

Listagem 5. Acesso a elementos de uma lista utilizando slicing.

  >>> x[0:2] #retorna elementos entre 0 e um antes de 2
  ['a', 'b']
  >>> x[1:]  #retorna elementos de 1 até o final
  ['b', 'c']
  >>> x[:-1] #retorna elementos do início até um antes do final
  ['a', 'b']
  >>> s[0:4] #retorna os caracteres entre 0 e um antes de 4
  'Jose'
  >>> s[5:7] #retorna os caracteres entre 5 e um antes de 7
  'da'

Temos ainda a função len, que nos mostra a quantidade de elementos de uma lista, ou a quantidade de caracteres de uma string, como no exemplo da Listagem 6.

Listagem 6. Exemplo da função len().

  >>> x
  ['a', 'b', 'c']
  >>> len(x)
  3
  >>> s
  'Jose da Silva'
  >>> len(s)
  13

Para as strings existem três operações importantes e que serão muito utilizadas nos nossos cenários, que são a concatenação, split e join.

Em Python, é possível concatenar strings de duas formas, como no exemplo da Listagem 7.

Listagem 7. Exemplo de concatenação de strings.

  #Utilizando o operador +     
  >> nome='Jose'
  >>> sobrenome='Silva'
  >>> nome_completo=nome + ' ' + sobrenome
  >>> nome_completo
  'Jose Silva' 
  #Utilizando o operador %:
  >>> nome_completo2='%s %s' % (nome, sobrenome)
  >>> nome_completo2
  'Jose Silva'

É possível perceber que o segundo exemplo, apesar de aparentemente mais complicado, tem uma vantagem: o formato da string desejada está todo contido dentro uma única string, e os dados dentro de uma tupla, após o sinal %. Os dois sinais “%s” são placeholders, que indicam que duas strings são esperadas, e com um espaço entre elas.

Agora imagine que queremos juntar cinco strings, em vez de duas, como no exemplo da Listagem 8.

Listagem 8. Concatenação de strings utilizando %.

  >>> s1='a'
  >>> s2='a'
  >>> s3='a'
  >>> s4='a'
  >>> s5='a'
  #utilizando +
  >>> s_all=s1 + ' ' + s2 + ' ' + s3 + ' ' + s4 + ' ' + s5
  >>> s_all
  'a a a a a'
  #utilizando %
  >>> s_all2='%s %s %s %s %s' % (s1, s2, s3, s4, s5)
  >>> s_all2
  'a a a a a'

A segunda forma normalmente é mais limpa, e é a que será utilizada nos exemplos.

Utilizamos a função split quando queremos dividir uma string com campos separados por algum caractere delimitador, e obter uma lista que permita acesso individual aos campos. Por exemplo, em ambientes Unix/Linux, temos o arquivo /etc/passwd que contém uma lista com os usuários do sistema. Essa lista possui linhas, que possuem campos separados pelo caractere “:”. Se quisermos manipular os campos de uma linha individualmente, podemos fazer como no exemplo da Listagem 9.

Listagem 9. Exemplo de uso da função split().

  >>> user='root:*:0:0:System Administrator:/var/root:/bin/sh'
  >>> campos_user=user.split(':')
  >>> campos_user
  ['root', '*', '0', '0', 'System Administrator', '/var/root', '/bin/sh']
  >>> campos_user[0]
  'root'
  >>> campos_user[4]
  'System Administrator'
  >>> campos_user[5]
  '/var/root'
  >>> campos_user[6]
  '/bin/sh'

Da mesma forma, se temos uma lista e queremos juntar os seus elementos em uma única string, podemos utilizar a função join:

>>> user2=':'.join(campos_user)

>>> user2

'root:*:0:0:System Administrator:/var/root:/bin/sh'

Uma particularidade da função join é que ela é aplicada na string que contém o delimitador, como podemos ver pelo exemplo.

Uma última consideração sobre strings é o uso do caractere r (“raw string”) para desabilitar o uso de caracteres de escape, como no exemplo da Listagem 10.

Listagem 10. Uso de caracteres especiais.

  >>> print('Teste\tTeste')
  Teste Teste
  >>> print(r'Teste\tTeste')
  Teste\tTeste

No primeiro comando print, o caractere de escape \t é interpretado como um <TAB>, ou seja, um caractere invisível que ocupa o espaço equivalente a quatro ou oito espaços em branco, dependendo do ambiente. No segundo comando, com o r no início, os caracteres \t são interpretados literalmente.

O tipo dict serve para armazenar pares de chaves e valores. É bastante útil quando desejamos criar uma coleção de valores relacionados e que podem ser nomeados para permitirem uma identificação mais clara, como no exemplo da Listagem 11.

Listagem 11. Uso do tipo dict().

  >>> d = dict()
  >>> d['chave1'] = 'valor 1'
  >>> d['chave2'] = 'valor 2'
  >>> d
  {'chave1': 'valor 1', 'chave2': 'valor 2'}
  >>>

Além dos tipos básicos, é possível criarmos os nossos próprios tipos, através do mecanismo de classes. Este assunto está fora do escopo deste artigo, mas é importante saber que uma classe é, a grosso modo, como um registro de uma tabela, que possui vários campos ou atributos, e pode possuir emmétodos que executam ações sobre os atributos. Podemos ter várias eminstâncias de uma classe, da mesma forma que podemos ter várias linhas em uma tabela, cada uma com as suas características.

Uma classe que utilizaremos frequentemente nos nossos cenários se chama datetime, e é parte da biblioteca de mesmo nome.

Quanto ao nome das variáveis, existem algumas regras que devem ser seguidas:

  • Devem iniciar por uma letra;
  • Podem conter letras, números e underline;
  • Não podem conter caracteres especiais;

Python diferencia maiúsculas de minúsculas, ou seja, os dois nomes no exemplo da Listagem 12 são diferentes para o interpretador:

Listagem 12. Diferenciação de maiúsculas e minúsculas.

  >>> variavel = 1
  >>> VARIAVEL = 10
  >>> variavel
  1
  >>> VARIAVEL
  10
  >>> Variavel
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
  NameError: name 'Variavel' is not defined

É recomendado que se nomeie as variáveis de forma significativa, para garantir boa legibilidade do código. É importante que o seu código seja facilmente entendido por outros que eventualmente precisem alterá-lo.

Controle de Fluxo

Instruções de controle de fluxo são aquelas que interferem na sequência de execução dos comandos de um programa, seja fazendo com que determinados comandos sejam executados e outros não (decisão), seja fazendo com que comandos sejam executados repetidamente (loop).

Uma instrução de decisão avalia uma ou mais condições e, com base no resultado, executa uma opção ou outra. Em Python, a instrução if/else é quem faz esse papel, como podemos ver na Listagem 13.

Listagem 13. Exemplo de instrução if/else.

  a = 1
  b = 2
  if a > b:
      print('a maior que b')
  elif a == b:
      print('a igual a b')
  else:
      print('a menor do que b')

Nesse exemplo, criamos duas variáveis, realizamos um teste, e executamos comandos baseados no resultado do teste. Se fossemos escrever a instrução apresentada na Listagem 13 em português, teríamos algo assim:

Se a for maior do que b (comando if)

Diga “a maior que b”

Senão, se a for igual a b (comando elif)

Diga “a igual a b”

Senão, se nenhuma das opções anteriores for verdadeira (comando else)

Diga “a menor do que b”

Os comandos if e elif sempre recebem uma condição, enquanto que o comando else é utilizado como o caso alternativo, se todos os outros testes foram falsos. O único comando obrigatório em uma decisão é o if, isto é, não é preciso existir nem elif nem else. Na prática existem casos onde a ação alternativa é simplesmente não fazer nada, como no exemplo:

if a > b:

print('a maior que b')

Nesse código, só mostraremos a mensagem caso a seja maior do que b. Caso a condição não seja verdadeira, o programa continua.

Qualquer expressão cuja resposta seja verdadeiro ou falso pode ser utilizada como condição. Tipicamente utilizamos operadores de comparação como > (maior), < (menor), >= (maior ou igual), <= (menor ou igual), == (igual) e != (diferente). Expressões podem ainda ser combinadas com o uso de operadores lógicos como and, or e not, mais o uso de parênteses, como podemos ver na Listagem 14.

Listagem 14. Uso de parênteses e operadores de comparação.

  if a > b and a > 0:
      print('a maior que b')
      print('E a maior que zero')
   
  if s == 'Meu Nome':
      print('s igual a Meu Nome')
   
  if (a > b and a > 0) or b == 2:
      print('a > b E a > 0, OU b igual a 2')

É importante notar dois aspectos da linguagem Python que foram mostrados nesses exemplos:

  • Comandos que contêm várias partes, como if, utilizam o sinal “:” ao final de cada parte, para delimitar o início de um bloco;
  • A indentação do código que está dentro do bloco é relevante e indica a qual bloco o código pertence.

Ou seja, quando temos um comando if, os comandos que serão executados dependendo da condição avaliada, devem estar indentados para “dentro” do bloco. Normalmente essa indentação é feita com quatro espaços, mas pode ser utilizado o caractere TAB. O importante é que o uso seja consistente, ou seja, ou utilizamos somente espaços ou somente TABs.

Para concluir a explicação sobre if, vale mencionar que é possível ter quantos comandos forem necessários dentro de um bloco if, até mesmo outro bloco if, como podemos ver na Listagem 15.

Listagem 15. Exemplo com comandos if encadeados.

  if (a > b and a > 0):
      print('a maior que b e a maior que 0')
      if a == 10:
          print('a maior que 10')

Repare a indentação do segundo bloco if, mais para dentro, para indicar que será executado dentro do bloco if mais acima.

Instruções de loop servem para executarmos um comando repetidamente, como por exemplo, para ler as diversas linhas de um arquivo texto, executamos um comando que busca a próxima linha de um arquivo dentro de um loop. Python possui algumas instruções de loop, mas nos ateremos à mais comum, que é a instrução for.

A instrução for serve para acessarmos os vários elementos de uma lista individualmente e em sequência, como demonstra o código:

l = (1,2,3,4,5)

for i in l:

print(i)

Nesse exemplo, o comando for é aplicado sobre a lista l, de modo que a cada iteração ou “volta” do loop, cada valor da lista é colocado na variável i, que então é mostrada na tela pelo comando print. Se fossemos escrever essa instrução em português, teríamos algo assim:

Coloque o próximo elemento de L em I

Mostre o valor de I

É possível perceber, como no if, o sinal de “:” ao final da linha do comando for, e a indentação do comando print para indicar que este pertence ao bloco do comando for.

Às vezes, desejamos utilizar uma sequência numérica no nosso loop. Nesse caso podemos utilizar a função range junto com o comando for, como no código:

for i in range(10):

print(i)

A função range nesse exemplo retorna uma lista com 10 valores, iniciando em 0, ou seja, os números de 0 a 9.

Essa é a forma geral do comando for. Exploraremos outros usos desse comando nos cenários que serão mostrados no decorrer desse artigo.

Funções

Funções são blocos de código reutilizáveis. Sempre que percebermos que estamos escrevendo um mesmo conjunto de instruções repetidamente, este conjunto é um bom candidato a se transformar em uma função.

Por exemplo, é muito comum colocarmos nos nossos scripts comandos que geram mensagens para mostrar o progresso da execução, e é útil colocar a data atual junto das mensagens, para podermos, por exemplo, identificarmos quanto tempo determinada ação levou.

Para mostrar uma mensagem com data atual, formatada como YYYY-MM-DD HH:MM:SS” podemos escrever um código assim:

>>> print('%s: %s' % (str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")), 'Erro na execucao'))

2013-10-26 17:22:19: Erro na execucao

Em vez de repetirmos diversas vezes essa linha inteira, podemos definir uma função que faça isso, como no exemplo:

>>> def show_msg(msg):

... print('%s: %s' % (str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")), msg))

De modo que, nas próximas vezes que quisermos mostrar uma mensagem, basta chamarmos a função passando como parâmetro a mensagem desejada:

>>> show_msg("Erro na execucao")

2013-10-26 17:24:31: Erro na execucao

Isso faz com que não tenhamos que repetir os comandos print e datetime.now(). Se um dia quisermos mudar o formato de data, basta mudar em um único local.

Bibliotecas e comandos internos

Os comandos que mostramos até aqui são internos da linguagem, como for, if e print. No entanto, boa parte do poder de Python vem do seu conjunto de bibliotecas, que são componentes que agrupam funcionalidades básicas e de uso geral, ou seja, que podem ser utilizadas por todos em qualquer programa.

Para utilizar uma biblioteca, é preciso indicar no programa que você deseja fazê-lo. Para isso, utilizamos o comando import:

import sys

Nesse exemplo, o comando import indica que pretendemos utilizar a biblioteca sys, que contém diversas funcionalidades que são de nosso interesse.

Por exemplo, os parâmetros de linha de comando passados para um programa ficam na lista sys.argv. Na Listagem 16 temos um programa que mostra todos os parâmetros que foram passados na sua linha de comando.

Listagem 16. Parâmetros de linha de comando.

  import sys
   
  for arg in sys.argv:
      print(arg)

Grave esse exemplo em um arquivo chamado argv.py, e o execute na linha de comando com alguns parâmetros, como podemos ver na Listagem 17.

Listagem 17. Teste do script argv.py.

  $ python argv.py 1 2 3 a b c "python" "script"
  argv.py
  1
  2
  3
  a
  b
  c
  python
  script

Podemos reparar que o primeiro valor retornado é o nome do programa, e os demais são os valores que passamos na linha de comando, na ordem em que os especificamos, independentemente de tipo de dados.

Em outro exemplo de uso de biblioteca, queremos testar se determinado arquivo existe. Para isso utilizamos a função os.path.exists, que testa se um arquivo ou diretório existe. Juntando com o exemplo anterior, passaremos um nome de arquivo como parâmetro na linha de comando, e testaremos se o mesmo existe, como podemos ver na Listagem 18.

Listagem 18. Exemplo do uso de bibliotecas.

  import sys
  import os
   
  if len(sys.argv) < 2:
      print('Parametro nao foi passado!')
      exit()
   
  file_name = sys.argv[1]
   
  if os.path.exists(file_name):
      print('Arquivo existe: ' + file_name)
  else:
      print('Arquivo nao existe: ' + file_name)

Grave esse exemplo em um arquivo com o nome exists.py, e o execute como nos exemplos da Listagem 19.

Listagem 19. Testes do script exists.py.

  $ python exists.py
  Parametro nao foi passado!
  $ python exists.py exists.py 
  Arquivo existe: exists.py 
  $ python exists.py teste.txt 
  Arquivo nao existe: teste.txt

Essa é a forma básica de uso de bibliotecas. Para identificar quais são as bibliotecas que fazem as coisas que desejamos, basta procurar na documentação, que é bastante abrangente. Nos nossos cenários utilizaremos diversas bibliotecas, o que deve ajudar a orientar esse processo de busca.

Acesso a arquivos

A automação de tarefas comumente envolve acesso a um ou mais arquivos. O acesso a arquivos em Python é feito através da função open, como no código:

arquivo = open('argv.py', 'r')

Nesse exemplo executamos a função open e passamos como parâmetro o nome do arquivo a ser aberto (argv.py) e o modo como o arquivo deve ser aberto (“r” para leitura, “w” para escrita, “a” para escrita com append). Na variável “arquivo” agora existe uma referência para o arquivo em disco, que pode ser utilizada para ler os dados do arquivo, linha a linha, com um loop for, por exemplo:

for linha in arquivo:

print(linha.rstrip())

A função rstrip utilizada no exemplo serve para remover o caractere <ENTER> (\n) do final de cada linha. Se não fizéssemos isso, teríamos uma linha em branco a mais para cada linha, porque o comando print adiciona um <ENTER> ao final de cada linha.

Para abrir um arquivo de escrita, basta trocar o modo de abertura.

Na Listagem 20 mostraremos um script simples de cópia de arquivos texto, que exemplifica o processo de validação de parâmetros de entrada. Muitas vezes nossos programas precisam receber parâmetros externos que servem para alterar o seu comportamento a uma condição ou outra. Por exemplo, quando utilizamos o comando dir no Windows, podemos passar as opções /s para fazer uma listagem recursiva, /b para mostrar uma listagem simples, entre outras opções. A ideia é que sejamos capazes de implementar esse tipo de funcionalidade nos nossos programas em Python.

Listagem 20. Validação de parâmetros de entrada.

  import sys
  import os
   
  if len(sys.argv) < 3:
      print('Parametro nao foi passado!')
      print('Uso: python copy.py <arquivo de origem> 
      <arquivo de destino>')
      exit()
   
  source_file_name = sys.argv[1]
  dest_file_name = sys.argv[2]
   
  overwrite = False
   
  if len(sys.argv) == 4:
      overwrite = sys.argv[3]
   
  if source_file_name == dest_file_name:
      print('Arquivo de origem igual arquivo de destino. 
       Copia nao executada')
      exit()
   
  if not overwrite and os.path.exists(dest_file_name):
      print('Arquivo de destino existe. Copia nao executada')
      exit()
   
  if not os.path.exists(source_file_name):
      print('Arquivo de origem nao existe. Copia nao executada')
      exit()
   
  source_file = open(source_file_name, 'r')
  dest_file = open(dest_file_name, 'w')
   
  for source_line in source_file:
      dest_file.write(source_line)
   
  source_file.close()
  dest_file.close()
   
  print('Copia executada')

Manipulação de arquivos e diretórios

Boa parte do que fazemos com linguagens de script é manipulação de arquivos. A maior parte das funções de manipulação de arquivos nativas do Python é equivalente ao comando do sistema operacional que realiza a mesma ação, e não deve ser difícil inferir o seu uso. Na Listagem 21 relacionamos as principais funções para manipulação de arquivos, com uma breve descrição.

Listagem 21. Funções de manipulação de arquivos e diretórios.

  os.remove('nome do arquivo') #remove arquivo
  os.removedirs('caminho do diretorio') #remove estrutura de diretorios
  os.rename('nome antigo','nome novo') #renomeia arquivo
  os.getcwd() #retorna diretorio atual
  os.rmdir('nome do diretorio') #remove diretorio
  os.mkdir('nome do diretorio') #cria diretorio
  os.chdir('nome do diretorio') #muda diretorio (cd)
  os.listdir('diretorio base') #lista diretorio
  os.walk('diretorio base') #navega pela arvore de diretorios
  os.path.exists('nome_do_arquivo') #verifica se arquivo existe
  os.path.basename('nome do arquivo') #retorna o nome do arquivo, sem diretorio
  os.path.dirname('nome do arquivo') #retorna o caminho do arquivo, sem o nome
  os.path.getatime('nome do arquivo') #retorna a data do ultimo acesso
  os.path.getctime('nome do arquivo') #retorna a data de criacao
  os.path.getmtime('nome do arquivo') #retorna a data da ultima alteracao
  os.path.getsize('nome do arquivo') #retorna o tamanho do arquivo em bytes
  os.path.isdir('nome do arquivo') #verifica se o arquivo passado é um diretório
  os.path.isfile('nome do arquivo') #verifica se o arquivo passado é um arquivo
  os.path.join('dir1', 'dir2') #concatena dois elementos para formar um caminho
  os.path.split('dir1', 'dir2') #retorna lista com todas as partes de um caminho
  shutil.copy('origem','destino') #copia arquivo
  shutil.move('origem','destino') #move arquivo

Veremos na prática como utilizar algumas dessas funções nos cenários mais adiante.

Tratamento de exceções

Python fornece um mecanismo de tratamento de exceções para os casos onde algum erro não esperado é retornado durante a execução. Por exemplo, se tentarmos acessar um arquivo que não existe, será gerada uma exceção, como podemos ver na Listagem 22.

Listagem 22. Exemplo de exceção não tratada.

  >>> f=open('xxx','r')
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
  IOError: [Errno 2] No such file or directory: 'xxx'

Se imaginarmos uma situação onde não sabemos se o arquivo existe, podemos colocar o nosso comando de abertura dentro de um bloco try/except, como podemos ver na Listagem 23.

Listagem 23. Exemplo de tratamento de exceção com try/except.

  >>> try:
  ...     f=open('xxx','r')
  ... except Exception as e:
  ...     print('Erro: %s' % e)

Dessa forma, se ocorrer um erro, é possível executarmos comandos para contornar a situação.

Existe ainda mais uma opção que é o bloco finally, que é adicionado ao final do bloco try, para garantir que o que estiver ali seja executado, independentemente do que ocorrer nos blocos try ou except, como podemos ver na Listagem 24.

Listagem 24. Exemplo de uso da instrução finally.

  >>> try:
  ...     f=open('xxx','r')
  ... except Exception as e:
  ...     print('Erro: %s' % e)
  ... finally:
  ...     print('Nao sei se deu erro, mas vou executar')
  ... 
  Erro: [Errno 2] No such file or directory: 'xxx'
  Nao sei se deu erro, mas vou executar

O bloco finally é útil quando precisamos liberar recursos, como fechar arquivos ou conexões com serviços externos.

Cenários de Exemplo

Aqui são apresentados alguns cenários comuns no dia a dia de um sysadmin e que pretendem mostrar uma forma diferente de executar tarefas repetitivas. Partindo desses exemplos, que com base no material apresentado até aqui não devem ser de difícil compreensão, e com alguma pesquisa, a ideia é que o leitor esteja apto a escrever os seus próprios scripts em pouco tempo, e com isso perceber como pode ser simples a automação de algumas atividades que muitas vezes são realizadas manualmente.

Todos os scripts para Windows assumem que o usuário logado que está executando os scripts possui permissão de Domain Admin.

Cenário 1: Cópia de arquivos para múltiplos servidores

Você precisa atualizar semanalmente os binários de um sistema legado que está instalado em 50 servidores Windows espalhados pelo Brasil. A relação de binários a serem atualizados pode mudar a cada semana, e os desenvolvedores disponibilizam em um diretório do file server somente os arquivos que foram modificados.

Para automatizar essa rotina nós precisamos de duas informações:

  • O diretório onde foram disponibilizados os arquivos;
  • Um arquivo contendo a relação dos servidores e diretórios de destino.

Nossa relação de servidores será gravada em um arquivo com o nome cenario1.txt, com o seguinte formato:

SERVER0|APP\SERVER0

Onde:

  • SERVER0 é o nome do servidor;
  • APP\SERVER0 é o nome do compartilhamento e diretório para onde os arquivos devem ser copiados.

Na Listagem 25 podemos ver como ficaria um script para realizar essa tarefa (lembre-se que a indentação é relevante em Python). As linhas que são iniciadas pelo caractere "#" são comentários dentro do código, e servem para explicar o funcionamento de alguns comandos.

Listagem 25. Script do cenário 1.

  import os
  import shutil
  import datetime
   
  #arquivo que contem a lista 
  server_list = 'cenario1.txt'
   
  #diretorio que contem os arquivos a serem copiados
  bin_dir = r'C:\APP'
   
  #funcao padrao para mostrar mensagens com data e hora
  def show_msg(msg):
        print('%s: %s' % (str(datetime.datetime.now().strftime
        ("%Y-%m-%d %H:%M:%S")), msg))
   
  #verifica se o arquivo com a lista de servidores existe
  if not os.path.exists(server_list):
        show_msg('Arquivo %s nao encontrado' % (server_list))
        exit(1)
   
  #verifica se o diretorio com os arquivos a serem copiados existe
  if not os.path.exists(bin_dir):
        show_msg('Diretorio %s nao encontrado' % (bin_dir))
        exit(1)
   
  #lista os arquivos a serem copiados
  bin_files = os.listdir(bin_dir)
   
  #verifica se existem arquivos a serem copiados
  if len(bin_files) == 0:
        show_msg('Nenhum arquivo encontrado em %s para ser copiado' 
         % (bin_dir))
        exit(1)
   
  #abre o arquivo que contem a lista de servidores
  servers = open(server_list, 'r')
   
  #monta loop que executara os comandos para cada servidor
  for entry in servers:
        (server, dir) = entry.rstrip().split('|')
        path = r'\\%s\%s' % (server, dir)
        
        #se o diretorio de destino nao existir, crie
        if not os.path.exists(path):
              os.mkdir(path)
        #monta loop que copiara cada arquivo para o servidor de destino
        for file in bin_files:
              shutil.copy(os.path.join(bin_dir, file), os.path.join(path, file))
              show_msg('Arquivo %s copiado para o servidor %s' % (file, server))
   
  servers.close()

Se tudo correr bem na execução, devemos ver uma saída parecida com a mostrada na Listagem 26.

Listagem 26. Resultado da execução do script do cenário 1.

  2013-10-26 20:32:04: Arquivo bin1.dll copiado para o servidor SERVER0
  2013-10-26 20:32:04: Arquivo bin2.dll copiado para o servidor SERVER0
  2013-10-26 20:32:04: Arquivo bin3.dll copiado para o servidor SERVER0
  2013-10-26 20:32:04: Arquivo bin4.dll copiado para o servidor SERVER0

Cenário 2: Execução de comandos remotamente no Linux

A sua empresa definiu que cada analista terá um usuário nomeado nos servidores, com permissão de root, para garantir que os logs de segurança reflitam quem realizou qual ação no ambiente. No entanto, como não existe configurado um mecanismo de administração de usuários centralizado, não é possível garantir que não será criado nenhum usuário com permissão de root além dos permitidos pela empresa. Você precisa montar um script para auditar diariamente o seu parque de 100 servidores Linux, para garantir que os únicos usuários com permissão de root (id=0) são os usuários autorizados pela empresa.

Para automatizar essa rotina nós precisamos de duas informações:

  • Um arquivo contendo a relação dos usuários autorizados;
  • Um arquivo contendo a lista dos servidores a serem verificados.

Podemos ver como ficaria um script para executar esta tarefa na Listagem 27.

Listagem 27. Script do cenário 2.

  import paramiko
   
  #inicia o cliente SSH
  ssh = paramiko.SSHClient()
  #define que as chaves publicas serao aceitas automaticamente
  ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
   
  #usuario e senha de conexao
  user='root'
  password='senha do root'
   
  #lista de servidores a serem verificados
  server_list = 'cenario2.txt'
   
  #lista de usuarios que podem ser root
  root_list = 'root.txt'
   
  #cria lista vazia para receber os usuarios que podem ser root
  root_allowed = []
   
  #abre arquivo com relacao de usuarios que podem ser root
  root_users = open(root_list, 'r')
   
  #le a lista de usuarios que podem ser root e adiciona a uma lista em memoria
  for root in root_users:
        root = root.rstrip()
        if len(root) > 0:
              root_allowed.append(root)
   
  root_users.close()
   
  #abre arquivo com relacao de servidores que devem ser verificados
  servers = open(server_list, 'r')
   
  #monta loop que executara os comandos para cada servidor 
  for server in servers:
        #se o nome do servidor estiver em branco, pare o loop
        if len(server) == 0:
              break
        #remove caractere \n do final do nome do servidor
        server = server.rstrip()
   
        #se conecta com o servidor
        ssh.connect(server, username=user, password=password)
   
        #executa o comando no servidor     
        (stdin, stdout, stderr) = ssh.exec_command('cat /etc/passwd')
        user_list = stdout.readlines()
   
        #monta loop para ler cada linha retornada do arquivo /etc/passwd
        for user in user_list:
              #faz o split da linha utilizando o separador ":"
              user_fields = user.split(':')
              if len(user_fields) < 5:
                    continue
   
              #obtem nome e id de usuario
              user_name = user_fields[0]
              user_id = int(user_fields[2])
              
              #se id igual a 0 e nome de usuario nao estiver 
              #na lista de permitidos
              if user_id == 0 and user_name not in root_allowed:
                    print('Servidor %s: Usuario %s possui permissao 
                    de root indevidamente' % (server, user_name)) 
  servers.close()

Cenário 3: Upload e Download de arquivos via FTP e HTTP

Quinzenalmente você precisa fazer o download de um arquivo com dados de empregados que o sistema de RH disponibiliza (via HTTP) e fazer o upload deste arquivo para o servidor FTP da empresa externa que faz o processamento da folha de pagamento. O nome do arquivo deve conter o nome da empresa e a data do arquivo no formato YYYYMMDD.

Para este cenário, precisamos saber:

  • Servidor HTTP de origem;
  • Servidor FTP de destino e credenciais;
  • Nome do arquivo a ser baixado;
  • Nome que o arquivo deve ter no servidor FTP.

Podemos ver como ficaria um script para executar essa tarefa na Listagem 28.

Listagem 28. Script do cenário 3.

  import datetime
  import ftplib
  import urllib.request
  import urllib.parse
   
  http_url='http://servidor.intranet/arquivo_rh.zip'
  ftp_server='ftp.empresa.com'
  ftp_user='nome do usuario'
  ftp_passwd='senha do usuario'
  ftp_file_name='EMPRESA_%s.zip' % datetime.datetime.now().strftime('%Y%m%d')
   
  #abre conexao com o servidor web
  request=urllib.request.Request(http_url)
  #envia request para fazer o download do arquivo
  response=urllib.request.urlopen(request)
   
  print('Inicio do download do arquivo')
  #abre arquivo local em modo binario para escrita
  local_file=open(ftp_file_name, 'wb')
  #grava dados recebidos via HTTP
  local_file.write(response.read())
  local_file.close()
  print('Download do arquivo %s concluido com sucesso' % ftp_file_name)
   
  #abre o arquivo gravado em modo binario para leitura
  local_file=open(ftp_file_name, 'rb')
   
  print('Inicio do upload do arquivo para %s' % ftp_server)
  ftp_client=ftplib.FTP(ftp_server)
  #faz login com o servidor FTP
  ftp_client.login(ftp_user, ftp_passwd)
   
  #envia arquivo em modo binario
  ftp_client.storbinary('STOR %s' % ftp_file_name, local_file)
  print('Upload do arquivo %s concluido com sucesso' % ftp_file_name)
  local_file.close()

Cenário 4: Listagem de usuários em grupos locais (Windows)

Você precisa auditar todos os 500 desktops da empresa para garantir que não existam outros usuários no grupo local Administrators que não seja o usuário Administrator e do grupo Domain Admins.

Neste cenário temos um arquivo de nome cenario4.txt que contém a lista de desktops a serem verificados. Podemos ver um exemplo na Listagem 29.

Listagem 29. Exemplo de arquivo de configuração.

  PC0
  PC1
  PC2
  ...

Um exemplo de script para executar essa tarefa é apresentado na Listagem 30.

Listagem 30. Script do cenário 4.

  import win32net
   
  #arquivo que contem a lista de desktops 
  desktop_list = 'cenario4.txt'
   
  #lista de membros permitidos
  allowed_members = []
  allowed_members.append('Administrator')
  allowed_members.append('Domain Admins')
   
  #parametros da funcao NetLocalGroupGetMembers
  level=2
  resume=0
   
  desktops = open(desktop_list, 'r')
   
  #nonta loop para executar comandos para cada desktop
  for desktop in desktops:
        desktop = desktop.rstrip()
        #chama a funcao do Windows que retorna a lista 
        #de membros de um grupo
        group_info=win32net.NetLocalGroupGetMembers
        (desktop, 'Administrators',level, resume)
        members=group_info[0]
   
        #monta loop para executar comandos para cada membro 
        #do grupo Administrators listado
        for member in members:
              if member['domainandname'].split('\\')[1] 
               not in allowed_members:
                    print('Desktop %s: usuario %s membro do 
                    grupo Administrators' % (desktop, 
                    member['domainandname']))
   
  desktops.close()

Cenário 5: Configuração de serviços (Windows)

Você precisa listar todas as contas de serviço que são utilizadas em todos os serviços dos servidores Windows, para verificar se há algum serviço configurado com outra conta que não sejam as autorizadas.

Para este cenário é preciso instalar a biblioteca WMI, que não vem na instalação default do ActivePython. Veja o endereço para download na seção Links.

Neste cenário temos um arquivo de nome cenario5.txt que contém a lista de servidores a serem verificados. Podemos ver um exemplo deste arquivo na Listagem 31.

Listagem 31. Exemplo de arquivo de configuração.

  SERVER0
  SERVER1
  SERVER2
  ...

No script listaremos os serviços de cada servidor, com as respectivas contas. Um exemplo de como ficaria um script para realizar essa tarefa é apresentado na Listagem 32.

Listagem 32. Script do cenário 5.

  import wmi
   
  server_list = 'cenario5.txt'
  servers = open(server_list, 'r')
   
  for server in servers:
        server = server.rstrip()
        wmi_client = wmi.WMI(server)
        for service in wmi_client.Win32_Service():
              print('%s\t%s' % (service.Caption, service.StartName))
   
  servers.close()

No exemplo da Listagem 32 utilizamos o caractere <TAB> (\t) no comando print para separar os campos que serão mostrados na saída do script. Esse formato de texto, com colunas separadas por <TAB>, é simples de importar para uma ferramenta como o Excel, por exemplo, para analisar os dados posteriormente, sem necessidade de conversão, somente realizando copy/paste. É possível utilizar outro separador, como vírgula ou ponto e vírgula, mas nesse caso seria necessário realizar um processo de importação de dados.

Conclusão

Neste artigo foi apresentada a linguagem Python como uma ferramenta poderosa para a automação de processos do dia a dia. Foram mostrados os tipos de dados comumente utilizados e suas particularidades, principais instruções de controle de fluxo, e recursos para manipulação de arquivos. Além disso, foram explorados alguns cenários comuns de uso, que podem servir de exemplo para a construção de soluções mais abrangentes de automação.