======= Aula 1 (01 Set 2008) - Visão geral do GNU/Linux e comentários breves sobre diversos programas ======= - Algumas formas de obter informações estáticas sobre o computador em que o GNU/Linux está instalado é olhar o conteúdo (usando o comando cat) de arquivos no diretório /proc. - Algumas informações estáticas são mais fáceis de serem obtidas através de programas específicos como o free, lsusb e lspci. O primeiro lista como está o uso da memória RAM do computador. O segundo lista os dispositivos USB conectados no computador e o terceiro lista todos os dispositivos ligados no barramento PCI interno do computador (todas as placas, por exemplo). - O diretório /proc é uma cópia "legível" do conteúdo da memória em um dado momento. Todos os processos em execução estão lá dentro. - Tudo no GNU/Linux é arquivo, assim como os dispositivos de hardware. Todos eles ficam embaixo de um diretório especial chamado /dev. - Os conteúdos das variáveis de ambiente do bash podem ser vistos usando o comando `export` sem parâmetros. Para modificar uma variável de ambiente específica basta rodar `export VARIAVEL=novo_conteudo` - Sempre que um comando é executado no bash, ele primeiro verifica se o comando é um alias e, caso não seja, procura o mesmo nos diretórios que estão listados na variável de ambiente PATH. - Informações dinâmicas sobre o computador podem ser obtidas com os comandos `top`, `ps` e `lsof`. O primeiro lista várias informações sobre os processos e os ordena de acordo com algum campo específico como uso de CPU, uso de memória, etc... O segundo lista todos os processos que estão em execução e o terceiro lista todos os arquivos que estão abertos em um dado momento por cada processo. - Sempre que um processo é executado, a sua saída padrão e a sua saída de erro são enviadas para a tela do shell. Uma forma de modificar esse comportamento é utilizando os caracteres de redirecionamento '>' ou '>>'. Por exemplo: `ls 1>/tmp/saida` vai escrever a saída padrão do ls no arquivo "/tmp/saida". `ls 2>/tmp/erro` vai escrever a saída de erro do ls no arquivo "/tmp/saida". Quanto se utiliza '>' o arquivo para onde a saída vai ser enviada é apagada e um novo arquivo é criado. Quando se utiliza '>>', o conteúdo é anexado ao que já está lá no arquivo. O /dev/null é uma exceção a esse caso. Ele nunca é apagado e funciona como uma espécie de "buraco negro". Sempre que alguma informação não é necessária, ela deve ser redirecionada para o "/dev/null". - Uma ferramenta muito utilizada no shell é o pipe (|). Ele permite que a saída padrão de um programa seja enviada para a entrada padrão de outro. Por exemplo: `ls | grep teste` vai listar somente as linhas da saída do comando `ls` que tiverem a string "teste" nelas. É importante observar que para o pipe funcionar, os programas utilizados do lado direito do pipe tem que ser programas que aceitem, por padrão, dados pela entrada padrão. A maioria dos programas no GNU/Linux já funciona dessa forma justamente para que os usuários possam combiná-los das mais diversas maneiras possíveis. ======= Aula 2 (08 Set 2008) - Início de programação em bash script ======= - Para exibir o conteúdo de uma variável específica no shell pode-se utilizar o comando echo $VARIAVEL. É bom colocar VARIAVEL sempre entre {}: echo ${VARIAVEL} para que o shell expanda o conteúdo de forma correta. - Os comandos da linguagem do bash podem ser interpretados diretamente na linha de comando ou podem ser incluídos em um arquivo texto (geralmente com extensão .sh). Este arquivo texto deve começar com uma linha: #!/bin/bash E deve possuir permissão de execução. As permissões podem ser gerenciadas usando o programa chmod. Por exemplo: chmod a+x dá permissão de execução para todos os usuários executarem o script. Pode-se utilizar números para definir as permissões também: `chmod UGO script`, onde UGO e são números de 0 a 7. U define a permissão do dono do arquivo, G define a permissão do grupo do arquivo e O define a permissão para os demais usuários. Os números definem as permissões de forma binária. O 1 implica a presença da permissão e o 0 implica a ausência: 4 2 1 r w x 0 0 0 = 0 0 0 1 = 1 0 1 0 = 2 0 1 1 = 3 1 0 0 = 4 1 0 1 = 5 1 1 0 = 6 1 1 1 = 7 r = leitura, w = escrita e x = execução. Por exemplo, para dar permissão de execução, leitura e escrita para o dono do arquivo e somente permissão de execução e leitua para o grupo e para os outros, tem-se que: U=7, G=5 e O=5. - Para depurar scripts em bash pode-se adicionar o argumento "-x" depois do #!/bin/bash na primeira linha do arquivo. - Se o script não estiver no PATH, ele deve ser executado com o seu caminho completo. Por exemplo: ./script.sh Vai executar o script `script.sh` que está no diretório local (./ é um sinônimo de diretório local). - Algumas variáveis importantes do shell são a $? e a $IFS. A $? contém o retorno do último comando que foi executado. Isso é útil para saber se o comando anterior foi executado com sucesso (por padrão, quando isso acontece, o retorno deve ser 0). O $IFS é útil em casos onde há iteração em uma lista como a mantida pelo comando `for`. Por exemplo: for I in `ls -1`;do echo $I done pode dar problema caso os arquivos da saída do ls tenham espaço entre o nome já que o IFS, por padrão possui os caracteres espaço e ENTER. Para que o for possa iterar de forma correta nos nomes dos arquivos deve-se antes de executá-lo rodar um: export IFS='PRESSIONAR ENTER' Para evitar problemas posteriores é bom salvar o conteúdo antigo do IFS e recuperá-lo depois. - Para armazenar em uma variável a saída de algum programa devem ser usados os acentos grave. Por exemplo: export VARIAVEL=`ls -1` Vai jogar em VARIAVEL a saída do ls. Se fosse feito: export VARIAVEL="ls -1" em VARIAVEL teria a string "ls -1". Uma terceira forma de armazenar conteúdo em variável é utilizando as aspas simples. Por exemplo: export VARIAVEL='$PATH' vai armazenar em VARIAVEL a string $PATH, ou seja, o conteúdo da variável não vai ser expandido. Essa é a diferença entre armazenar conteúdo com ' e armazenar conteúdo com ". No segundo caso a variável é expandida, ou seja: export VARIAVEL="$PATH" vai armazenar em VARIAVEL o conteúdo da variável PATH. - Para fazer operações aritméticas podem ser utilizados os programas let e o bc. O primeiro é ideal para operações com números inteiros e opera diretamente sobre as variáveis: let I=I+1 Soma 1 ao conteúdo de I. Já o bc é ideal para operações com números de ponto flutuante e pode ser usado com o pipe. Por exemplo: I=`echo $I+1 | bc -l` Soma 1 ao conteúdo de I, sendo que I pode ser um número real. Se I for um número real, o let I=I+1 trunca o resultado. - Para colar linhas de arquivos pode ser usado o paste. Se as linhas de um único arquivo devem ser coladas, o paste deve ser usado com a opção '-s'. Para mudar o separador entre as linhas coladas pode-se usar a opção '-d <DELIMITADOR>'. Por exemplo: paste -s -d '+' arquivo Vai pegar todas as linhas do arquivo, colocar em uma única linha (cola as linhas) separadas por um sinal de '+'. - Uma vez trabalhando com scripts em arquivos, os argumentos para o arquivo podem ser acessados via as variáveis $1, $2, $3, etc... $0 armazena o nome do script que foi invocado. $# pode ser usado para testar a quantidade de argumentos que foram passados para o script. Por exemplo, se o script for executado da seguinte forma: ./script.sh bla ble bli blo e dentro dele as variáveis de argumentos forem acessadas, o conteúdo será o seguinte: $#=4 $0=./script.sh $1=bla $2=ble $3=bli $4=bo - Para "cortar" linhas, que podem ser saída de outro comando, pode-se usar o cut. Por exemplo, seja uma linha contendo várias palavras separadas por espaço. Para imprimir somente a terceira palavra pode-se fazer: echo $LINHA | cut -f 3 -d ' ' O '-d' diz que o delimitador entre os campos vai ser espaço e o '-f 3' diz que deve ser impresso o campo (field) 3. - Para imprimir a quantidade de linhas de um arquivo pode ser usado o `wc -l`. wc executado sem argumentos dá informações de quantidade de linhas, palavras e caracteres do arquivo. ======= Aula 3 (15 Set 2008) - Mais sobre o 'if' no bash, expressão regular e início dos comandos mágicos do 'vim' ======= - O if do bash pode fazer vários testes relacionados com arquivos que facilitam a vida do programador. Por exemplo: if [ -f arquivo.txt ]; then echo "arquivo existe" fi Vai imprimir "arquivo existe" caso o "arquivo.txt" exista e seja um arquivo regular, ou seja, não seja um link ou um diretório por exemplo. Vários outros testes existem como testar se o arquivo existe e é um diretório (-d), testar se uma variável tem conteúdo vazio (-z), entre outros. `man bash` lista todos os testes existentes. - Vários testes no if podem ser ligados com operadores booleanos iguais ao da linguagem C. Por exemplo: if [ expressão ] && [ expressão ]; then equivale ao AND if [ expressão ] || [ expressão ]; then equivale ao OR - O && também pode ser usado diretamente no shell para fazer com que uma seqüência de comandos seja executada mas interrompida caso um dos comandos dê erro. Por exemplo: comando 1 && comando 2 && comando 3 vai executar os comandos na seqüencia 1, 2 e 3. Se o comando 2 falhar (se seu retorno for diferente de zero) o comando 3 não é executado - Os principais comandos relacionados com expressões regulares são o grep, o tr e o sed. O grep é útil para buscar linhas que casem com um dado padrão. O tr é útil para mapear um conjunto de caracteres em outro e o sed é útil para subsituir uma expressão regular por um conjunto de caracteres. As formas como esses 3 comandos são executados são as seguintes: grep expressao arquivo cat arquivo | tr [conjunto 1] [conjunto 2] sed -e 's/expressao/novo conteudo/g' arquivo Algumas opções muito usadas no grep são: -i para ele não ser case sensitive -v para ele funcionar de forma inversa (imprime tudo que não se encaixar na expressão) -r funciona de forma recursiva nos arquivos Opção muito usada no tr é: -d para apagar o conjunto 1 da entrada padrão. Neste caso não é necessário colocar o conjunto 2 na linha de comando. Opções muito usadas no sed são: -i para ele funcionar inline, ou seja, o arquivo é modificado. O resultado do sed não é impresso na saída padrão mas sim no próprio arquivo passado como entrada. -r para o sed interpretar as expressões regulares de acordo com o padrão que aprendemos na graduação (*, +, ?, () tem significados especiais) - Algumas observações importantes quando trabalhando com expressão regular são: . Cuidado ao usar o '.' pois ele significa qualquer caracter. Se você quiser fazer algo com o caracter '.' use '\.' no lugar. . '^' significa o início da linha e '$' significa o final da linha. . Para substituir uma string por outra o ideal é utilizar o sed. O tr às vezes tem uns comportamentos estranhos de serem inferidos. - O vim aceita expressões regulares em muitos dos seus comandos, por exemplo na hora de fazer a busca usando '/' - Alguns comandos básicos do vim são: y: Copia o bloco de texto p: Cola o bloco de texto x: Recorta o bloco de texto d: Deleta o bloco de texto Para rodar esses comandos o primeiro passo é entrar no modo visual: Pressiona ESC, pressiona v e usa as teclas de direção para selecionar as linhas desejadas. - O vim também tem uma substituição de strings como o 'sed'. Para ativá-la basta rodar a seqüência de comandos: Pressiona ESC, pressiona v e usa as teclas de direção para selecionar as linhas desejadas, pressiona : e escreve tudo como se fosse o sed a partir do '. Por exemplo: sed -e 's/bla/ble/g' no vim viraria somente 's/bla/ble/g'. ======= Aula 4 (22 Set 2008) - Mais sobre o 'sed', o 'grep', como utilizar sockets no bash corretamente e continuação dos comandos mágicos do 'vim' ======= - O sed pode ser utilizado para os mais diversos objetivos. Alguns deles estão listados em http://www.lrc.ic.unicamp.br/~daniel/sed_ptBR.html. - Um método muito útil que não tem no link acima é como substituir o padrão X da enésima ocorrência até o final. sed -e 's/X/NOVO/ng' Por exemplo, para substituir a terceira, quarta, ... ocorrência de daniel por luciano na linha abaixo: /home/daniel/teste /home/daniel/teste1 /home/daniel/teste2 /home/daniel/teste3 Basta rodar: sed -e 's/daniel/luciano/4g' Se quiser substituir somente a quarta ocorrência é só não colocar o 'g' no final. - Se os padrões utilizados no sed possuem o caracter '/', ele deve ser "escapado", ou seja, no lugar deve ser utilizado "\/". - Duas opções muito úteis no grep são: -n: imprime a linha onde o padrão foi encontrado -o: ao invés de imprimir a linha onde o padrão foi encontrado, imprime apenas o padrão - É possível utilizar sockets em bash. Caso o serviço a ser acessado seja somente leitura, basta rodar: cat < /dev/(tcp|udp)/hostname/porta Por exemplo, para ler a hora pelo serviço daytime na máquina time.nist.gov, basta rodar: cat < /dev/tcp/time.nist.gov/13 - Caso o serviço a ser acessado seja para leitura e escrita (por exemplo, o http, que envia uma requisição do cliente e devolve uma resposta), deve em primeiro lugar ser criado um descritor de arquivo para isso. Por exemplo, para acessar a página principal do google: exec 3<>/dev/tcp/www.google.com/80 echo -e "GET / HTTP/1.1\n\n" >&3 cat <&3 (TODO: O acesso http da forma como feito acima costuma dar erro de codificação nos servidores. É preciso investigar melhor porque isso acontece) - Mais comandos do 'vim': ESC, CTRL+v -> entra no modo de seleção de bloco. Dessa forma é possível modificar a mesma quantidade de colunas em todas as linhas. Por exemplo, é possível inserir um dado caracter no início de todas as linhas. Para inserir alguma informação em todas as linhas é preciso usar o 'I' e depois terminar com dois ESCs seguidos. ESC, u -> Desfaz a última ação realizada ESC, CTR+r -> Refaz a última ação desfeita ESC, g -> Vai para a primeira linha do arquivo ESC, GG -> Vai para a última linha do arquivo ESC, w -> "Anda" pelo arquivo pulando de palavra por palavra ESC, :new arquivo.txt -> Abre uma nova janela horizontal com o arquivo.txt ESC, :vnew arquivo.txt -> Abre uma nova janela vertical com o arquivo.txt ESC, CTRL+w <setas> -> "Navega" pelas janelas. É possível navegar de forma cíclica clicando CTRL+w w w w... ESC, :set number -> Exibe as linhas do arquivo no canto esquerdo ESC, :número -> Vai para a linha 'número' ESC, :qa -> Fecha todas as janelas ESC, CTRL+w o -> Fecha todas as janelas menos aquela em que o cursor está - Para colar um conteúdo do buffer no vim n vezes basta, antes de pressionar o 'p', digitar a quantidade de vezes que o buffer deve ser colado. - Para abrir vários arquivos no vim basta rodar o programa com a opção '-o' seguida de todos os nomes dos arquivos - Para abrir um arquivo na linha x, basta rodar o vim com a opção '+x' - Dois programas que usam o "vim" mas tem objetivos específicos são o vimdiff e o view. O primeiro deve ser rodado com dois nomes de arquivos como parâmetro. Ele mostra o diff dos dois arquivos de forma legível. O segundo abre o arquivo para somente leitura. ======= Aula 5 (29 Set 2008) - Mais sobre o 'vim', || e 'sort' no shell, gnuplot e screen ======= - O 'vim' tem "infinitos" comandos e seria muito complicado colocar os mais importantes aqui. O ideal é ter este guia de referência impresso: http://tnerual.eriogerg.free.fr/vimqrc.pdf - No shell é possível definir que um comando só deve rodar caso o anterior tenha dado erro (ou seja, sua saída tenha sido diferente de zero). Isso é feito assim: comando1 || comando2 Neste caso, `comando2` só vai ser executado se comando1 der erro. - Uma forma de ordenar texto no shell é utilizando o comando `sort`. Opções interessantes: -n: Ordena números pelo seu valor "real" e não pelo ASCII dos caracteres -r: Inverte a ordenação. Põe os maiores primeiro - Para plotar gráficos de dados que estejam em arquivos texto no formato: x1 y1 x2 y2 x3 y3 x4 y4 pode ser usado o gnuplot. O gnuplot precisa de um arquivo com a configuração do gráfico que será gerado (Costuma-se nomear este arquivo como .gpi). Os dados que serão plotados costumam ser preenchidos em arquivos .dat. Um exemplo de arquivo .gpi detalhado segue abaixo: ############################################################################## set term postscript enhanced eps monochrome # Defino que o gráfico vai ser um .eps set encoding iso_8859_1 # Codificação set xlabel "\nBandas" # O título do eixo x set xtics ("Blind Guardian" 0, "Dream Theater" 1, "Deep Purple" 2, "Jack Johnson" 3, "Guns and Roses" 4, "Ben Harper" 5) # Os valores marcados no eixo x e strings que aparecerão no lugar dos valores set ylabel 'Nota' # O título do eixo y set yrange [0:50] # O intervalo do eixo y set xrange [-1:6] # O intervalo do eixo x set output "bandas.eps" # Nome do arquivo .eps que será gerado plot 'bandas.dat' with linespoints # Arquivo que será plotado e o estilo da linha ############################################################################## Com este arquivo salvo, e com o arquivo bandas.dat corretamente preenchido, basta executar: gnuplot arquivo.gpi que o arquivo bandas.eps será gerado. - Para tirar dúvidas sobre algum comando do gnuplot pode-se executar: gnuplot e no prompt que aparecer: help <comando> - Duas deficiências do gnuplot são para plotar gráficos em barra e gráficos em pizza. Para o primeiro caso recomenda-se a utilização do script bargraph.pl http://www.burningcutlery.com/derek/bargraph/. Para o segundo caso recomenda-se a utilização da função "pie" presente no módulo "mathplotlib" do python http://matplotlib.sf.net/matplotlib.pylab.html#-pie - O screen é um programa muito útil para deixar programas rodando em background por muito tempo. As três opções mais utilizadas são: -S <nome>: Cria um terminal virtual cujo nome da sessão é <nome> -r <nome>: Recupera o terminal virtual associado à sessão <nome> -list: Lista todas as sessões existentes - Uma vez dentro do screen, comandos especiais podem ser executados sempre começando com CTRL+a. Por exemplo, para "desconectar" de um terminal, pode-se executar CTRL+a d. Mais combinações de comandos podem ser vistas com CTRL+a ? ======= Aula 6 (6 Out 2008) - Revisão de alguns problemas comuns a serem resolvidos com comandos no bash, novos comandos, dia e graphviz ======= - Para "transformar" arquivo1 em arquivo2 pode-se usar os comandos diff e patch da seguinte forma: diff arquivo1 arquivo2 > patch.patch patch < patch.patch arquivo1 - Caso um arquivo texto esteja compactado pelo gzip (extensão .gz), ele pode ser lido sem descompactar antes usando o comando zcat: zcat arquivo.txt.gz - Quando uma saída do comando 'sort' possui várias entradas para o mesmo número, as entradas repetidas podem ser removidas usando o comando 'uniq'. Por exemplo. Se a saída de um 'sort' foi: 1 1 1 2 3 3 3 4 4 5 6 após passar o uniq via um pipe, a saída será: 1 2 3 4 5 6 - O grep pode ser usado para exibir *apenas* o trecho das linhas que batem com o padrão buscado. Por exemplo, supondo que haja um arquivo com várias linhas do tipo "Blablabla ID: 45-32123 blebleble", sendo que o "ID: 45-32123" pode aparecer em qualquer lugar da linha. Se quisermos imprimir somente o trecho "ID: xx-xxxxx" pode-se usar o grep assim: grep -oE "ID: [0-9]*-[0-9]*" Sem o '-o' toda a linha seria impressa. - Para fazer correção ortográfica no shell pode-se usar o comando aspell. Para fazer a correção de um texto em inglês: aspell -c -l en arquivo.txt Para fazer a correção de um arquivo em português: aspell -c -l pt_BR arquivo.txt Os comandos são sempre explicados na tela do aspell e um arquivo .back é criado com o conteúdo original, antes das correções serem feitas. - No vim, as buscas com '/' podem ser feitas ignorando o case usando a opção: :set ignorecase Para voltar a considerar o case, usa-se: :set noignorecase - No vim, a saída de qualquer comando pode ser incluída dentro do texto editado utilizando "! comando". Isso é útil para, por exemplo, numerar as linhas dentro do arquivo. Pode-se utilizar o 'seq' para gerar os números de 1 até o tamanho do arquivo com o '!'. - Uma forma de avisar ao usuário quando um dado comando terminou de executar é colocar no final de um script o comando 'mail'. Isso é útil para processos demorados, principalmente aqueles executados de dentro do screen. A sintaxe básica do mail é: mail email@email.com -s assunto < conteudo_do_email.txt É importante ter cuidado na hora de usar este comando pois, caso o servidor smtp da máquina não esteja corretamente configurado, o domínio onde a máquina está localizada pode ser acusado de fazer spam. - Dois programas muito úteis para desenhar figuras no linux são o dia e o graphviz. O dia é um programa usado via GUI. O importante a ser comentado sobre ele é que uma figura desenhada por ele pode ser exportada para .eps, o que é útil quando se escreve textos em latex, como artigos. Para gerar um arquivo .eps a partir de um .dia pode-se utilizar: dia --nosplash --export=arquivo.eps arquivo.dia O graphviz na verdade é uma suíte de programas para desenhar grafos. Existem 5 programas que geram os gráficos de formas diferentes. Alguns tentam desenhar os grafos de forma circular, outros desenham grafos direcionados, outros desenham grafos não-direcionados, entre outros. O graphviz funciona de forma semelhante ao gnuplot. Escreve-se um arquivo texto que descreve o grafo e executa-se algum dos programas do graphviz para que seja gerado um arquivo .ps a partir do arquivo texto. Por exemplo: dot -Tps -o grafo.ps grafo.dot Vai gerar um arquivo .ps (grafo.ps) com o desenho de um grafo direcionado descrito pelo arquivo grafo.dot. O site do graphviz tem muitos exemplos que podem ser consultados: http://www.graphviz.org/Gallery.php Obs.: Na realidade o tipo da saída do graphviz pode ser diversa. O exemplo acima gerou um arquivo .ps, mas pode ser gerado por exemplo um arquivo .png usando a opção -Tpng e colocando um arquivo.png depois da opção '-o'. ======= Aula 7 (15 Out 2008) - Detalhes do vim, detalhes do sed e visão geral do awk ======= - O vim permite que comandos específicos sejam executados para arquivos diferentes através da utilização do modeline. Para habilitar, basta escrever no seu ~/.vimrc: set modeline Com esta opção ativada é possível embutir comandos do vim dentro de um arquivo entre os delimitadores vim: e : . Por exemplo, suponha que vc queira habilitar a numeração das linhas no arquivo teste.c sempre. Mas vc não quer que essa opção seja válida para outros arquivos (se vc quisesse isso bastaria adicionar "set number" no seu .vimrc). Para que a opção seja ativada somente quando editando o arquivo teste.c, basta adicionar, no final do arquivo teste.c: // vim: set number : o '//' não é uma necessidade do vim, mas sim do C. Se vc estivesse escrevendo um script em bash, deveria colocar # no lugar do // - O sed permite que as subexpressões e os padrões que forem encontrados sejam utilizados no seu modo de substituição. Por exemplo, digamos que vc deseje buscar as linhas que contenham strings do tipo: 5 bla 6. Você quer mudar o conteúdo do 5 para X mas quer manter o valor 6 do jeito que está (Obs.: Pode ser qualquer número no lugar do 5 e do 6) sed -e 's/\([0-9]\) \(bla [0-9]\)/X \2/' Desse jeito a saída será: X bla 6 O que foi feito? Cada conjunto entre parênteses é considerado uma subexpressão pelo sed. Os escape \1, \2, \3, até \9 são uma forma de acessada cada uma dessas subexpressões. No exemplo acima a primeira subexpressão é o [0-9] que casa com o primeiro 5. A segunda subexpressão é o bla [0-9] que casa com o 6. Na hora de imprimir o conteúdo do 5 é trocado pelo X e o restante é colocado da mesma forma que foi lido pelo \2. Também é possível imprimir toda a expressão regular utilizando o '&'. Por exemplo, considerando a mesma linha acima, suponha que vc deseja colocar todos os números entre colchetes. Isso pode ser feito da seguinte forma: sed -e 's/[0-9]/\[&\]/g' A saída será: [5] bla [6] - Um utilitário indicado quando se deseja programar orientado a dados é o awk (o ideal é sempre rodar o comando gawk para garantir que vai ser utilizado o gnu awk). O awk é um programa que recebe como entrada uma seqüência de ações que devem ser realizadas para diversos padrões em cima de um arquivo texto. De forma resumida seria algo do tipo: "Faça tal coisa quando a linha tiver isso e faça outra tal coisa quando a linha tiver aquilo". O poder do awk está no fato dele permitir que cada linha seja manipulada através dos seus campos, ou seja, cada linha (um registro) é formado por várias palavras (os campos). Um script em awk pode ser passado diretamente na linha de comando da seguinte forma: gawk 'BEGIN {ACAO PARA SER FEITA ANTES DO ARQUIVO SER LIDO}; PADRAO1 {ACAO1}; PADRAO2 {ACAO2}; PADDRAO3 {ACAO3}; END {ACAO PARA SER FEITA QUANDO O ARQUIVO CHEGAR AO FIM}' ARQUIVO.TXT Algumas informações importantes: . As ações do BEGIN e do END podem ser omitidas em situações onde não haja necessidade de utilizá-las. . É possível definir ações sem definir PADRAO. Neste caso a ação é realizada para cada linha Um exemplo simples: Suponha que vc tenha um arquivo da forma: 1 2 3 4 5 3 4 5 6 7 E você deseje imprimir somente o primeiro número de cada linha. Em awk isso seria feito assim: gawk '{print $1}' arquivo.txt Se fosse necessário imprimir o segundo número: gawk '{print $2}' arquivo.txt Se fosse necessário imprimir a linha inteira, somente caso a linha começasse com o núemro 1: gawk '/^1/ {print $0}' arquivo.txt Por padrão o awk considera que o separador de registros é a quebra de linha (\n) e que o separados de campos é qualquer tipo de espaço. Para modificar esses valores basta definir os valores desejados nas variáveis RS e FS na ação do BEGIN. O gawk possui muitas construções interessantes e funções embutidas como printf, if, rand(), etc... Mais informações podem ser vistas em: http://www.gnu.org/software/gawk/manual/gawk.html Um exemplo de script que utiliza bastante o awk pode ser visto em http://www.lrc.ic.unicamp.br/~daniel/surpresas/anima-001.sh. O resultado deste script pode ser visto em http://www.youtube.com/watch?v=YqfDvkPwTVs ======= Aula 8 (20 e 22 Out 2008) - Como fazer busca reversa no shell, Makefile e início de LaTeX ======= - De um modo geral, sempre que desejamos listar ou buscar algum conteúdo no linux, utilizamos programas como o `ls` ou o `find` seguidos do padrão que estamoso buscando. Entretanto, muitas vezes é útil fazer uma busca reversa. Ou seja, listar e buscar arquivos que *não* casem com o padrão desejado. No caso do `ls` isso pode ser feito da seguinte forma: ls -I D* # Listará todos os arquivos e diretórios que *não* comecem com 'D' No caso do `find` isso pode ser feito da seguinte forma: find . -not -name "D*" # Encontrará todos os arquivos e diretórios a partir # do diretório atual que não comecem com D Obs.: É possível limitar até quando o find vai "descer" utilizando a opção -maxdepth. Passando -maxdepth 1 o find não buscará os arquivos em nenhum diretório abaixo do local atual. - O make é um utilitário ideal para compilar programas em diferentes linguagens. Suas principais vantagens são: i) facilitar a compilação quando o número de arquivos é muito grande e; ii) recompilar somente os arquivos necessários quando nem todos são modificados. Para o make funcionar corretamente é preciso que seja criado um arquivo chamado Makefile. Um exemplo de Makefile pode ser visto logo abaixo: %%%%%%%%%%%%%%%%%%%%INÍCIO DO Makefile%%%%%%%%%%%%%%%%%%%%%%%%%%%%% TEX=/usr/bin/latex BIB=/usr/bin/bibtex GCC=/usr/bin/gcc DIA=/usr/bin/dia GNUPLOT=/usr/bin/gnuplot SOURCE_ARTIGOX=artigox FIGURAS_ARTIGOX=grafico1.eps ################################################### .SUFFIXES: .dia .eps .ps .gpi .dia.eps: $(DIA) --nosplash --export=$@ $< .gpi.eps: gnuplot $< .tex.dvi: ($(TEX) $< && $(BIB) $(patsubst %.tex,%,$<) && $(TEX) $< && $(TEX) $<) || (rm -f $@ && false) all: artigox artigox: $(FIGURAS_ARTIGOX) $(SOURCE_ARTIGOX).dvi dvips -t letter $(SOURCE_ARTIGOX).dvi -o $(SOURCE_ARTIGOX).ps ps2pdf $(SOURCE_ARTIGOX).ps $(SOURCE_ARTIGOX).pdf acroread $(SOURCE_ARTIGOX).pdf clean: @rm -f *.aux *.dvi *.log *.pdf *.bbl *.blg *.ps *.lof *.lot *.toc *.eps %%%%%%%%%%%%%%%%%%%%FIM DO Makefile%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - Informações importantes: . A tabulação é importante no Makefile. Todas as linhas com exceção da primeira linha de cada regra devem ser identadas com 1 tabulação. . As regras .dia.eps, .gpi.eps e .tex.dvi ensinam ao Makefile como compilar cada um dos códigos fonte .dia, .gpi e .tex para que eles sejam transformados em .eps, .eps e .dvi respectivamente. . O ideal é colocar todas as extensões envolvidas no seu projeto dentro da seção .SUFFIXES. . Nas regras de compilação, o arquivo de entrada é interpretado como $< e o arquivo gerado é interpretado como $@ . O Makefile possui algumas funções embutidas. Por exemplo, a patsubst que tem a seguinte sintaxe: patsubst PADRAO,SUBSTITUI,TEXTO. O que a patsubst faz é buscar por PADRAO no texto TEXTO e subsituir por SUBSTITUI. No exemplo acima isso foi necessário porque a execução do bibtex deve ser feita sobre o nome do arquivo sem a extensão. . Um @ antes de um comando faz com que sua saída não seja impressa na tela . As regras de compilação do Makefile sempre tem, na primeira linha, os arquivos que serão gerados. As linhas seguinte devem ser utilizadas para listar outros programas que devem ser executados para se chegar ao resultado final. . Uma vez tendo o Makefile pronto, para que alguma das regras seja executada, basta rodar: make regra A regra definida na regra 'all' é a regra que será executada caso o make seja invocado sem argumentos: make No exemplo acima, a regra padrão é 'artigox'. - Textos em LaTeX podem ser considerados um padrão no meio acadêmico. Um texto em latex é contido em um arquivo texto.tex, utiliza figuras geralmente em formato .ps ou .eps e referências bibliográficas contidas em arquivos .bib. Abaixo segue um exemplo de arquivo .tex e, na seqüência, um arquivo .bib: %%%%%%%%%%%%%%%%%%%%INÍCIO DO arquivox.tex%%%%%%%%%%%%%%%%%%%%%%%% \documentclass[letter.12pt]{article} \usepackage[brazilian]{babel} \usepackage[utf8]{inputenc} \usepackage{graphicx} \title{Um Artigo Muito Loco} \author{Neila Arouca and Daniel Batista and Jader Moura} \begin{document} \maketitle \begin{abstract} \end{abstract} \section{Introduçäo} Esse artigo eh mto loco porque so tem introducao e conclusao!! \cite{referencia} \begin{center} \includegraphics[width=0.5\textwidth]{grafico1.eps} \end{center} \section{Conclusäo} To be or not to be :P \bibliographystyle{acm} \bibliography{artigox} \end{document} %%%%%%%%%%%%%%%%%%%%FIM DO arquivox.tex%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%INÍCIO DO arquivox.bib%%%%%%%%%%%%%%%%%%%%%%%%% @misc{referencia, title="{Uma referencia um tanto vaga}", author={Mr Smith and Mrs Smith}, note="{Disponivel nas asas da imaginacao}" } %%%%%%%%%%%%%%%%%%%%FIM DO arquivox.bib%%%%%%%%%%%%%%%%%%%%%%%%%%%% - Informações importantes: . Figuras em LaTeX sempre são inseridas com o comando \includegraphics . O idioma do texto que está sendo escrito sempre é definido entre [] antes do {babel} no início do documento. É importante definir o idioma correto para que algumas seções tenham os títulos corretos e para que a separação silábica também ocorra de forma correta. . Existem diversos estilos de referências. O estilo a ser usado deve ser definido no comando bibliographystyle. Em http://www.cs.stir.ac.uk/~kjt/software/latex/showbst.html há uma lista com vários estilos de referências. . A compilação de um arquivo em LaTeX segue a seqüência: latex arquivo.tex bibtex arquivo latex arquivo.tex latex arquivo.tex dvips -t letter -o arquivo.ps arquivo.dvi ps2pdf arquivo.ps arquivo.pdf O exemplo de Makefile mostrado acima executa justamente essa seqüência de comandos. ======= Aula 9 (29 Out e 3 Nov 2008) - Mais sobre Makefile e LaTeX, getopts e comando automático no vim ======= - Mais sobre o Makefile . As regras no Makefile são constituídas sempre por duas partes. A primeira parte (que fica na primeira linha da regra) define os arquivos que devem ser gerados. Por exemplo: artigo: artigo.dvi diz que deverá ser gerado um arquivo artigo.dvi. A segunda parte do Makefile (que ficam nas linhas seguintes) definem comandos que devem ser executados. Por exemplo: artigo: artigo.dvi dvips -tletter -o artigo.ps artigo.dvi define que o comando 'dvips' deve ser executado depois que o artigo.dvi for gerado. De um modo geral, as regras no Makefile sempre deveriam ser assim. Caso uma regra só seja composta por comandos, ou seja, caso a regra não tenha nada definido na primeira linha, o ideal é "dizer" isso para que o make otimize suas estruturas interna na hora de interpretar o Makefile. Isso é feito colocando as regras que só executam comandos dentro da variável .PHONY. Por exemplo, a regra 'clean' sempre contém somente comandos para apagar arquivos temporários. Essa é uma regra que deveria ser colocada dentro da variável .PHONY. Assim: .PHONY: clean Na prática, se o Makefile é pequeno, não será percebida diferença ao se colocar ou não o clean dentro do .PHONY mas é bom manter a prática de colocar as regras que só executam comandos dentro desta variável para o caso do Makefile crescer muito. . É legal colocar na regra clean para que os arquivos *.*~ sejam apagados. Arquivos que terminam com ~ costumam ser criados por alguns programas como uma forma de backup. O dia é um exemplo de programa que cria arquivos .dia~ sempre que o .dia original é modificado. . Na criação de arquivos .pdf a partir de .tex no Makefile, a regra é definida como .tex.dvi e não com o.tex.pdf para evitar que mudanças nas figuras não sejam detectadas. Por exemplo, supondo que haja regra para gerar o .pdf a partir do .tex e que exista a regra: artigo: figura1.eps artigo.pdf acroread artigo.pdf Suponha que figura1.eps é gerada a partir de figura1.dia. Do jeito que a regra acima está definida, uma modificação na figura1.dia vai fazer o figura1.eps ser compilado de novo. Entretanto, como o artigo.tex não foi modificado, o Makefile não "sabe" que ele precisa compilar o artigo.pdf de novo. Como resultado, o artigo.pdf vai continuar sendo o antigo, ou seja, com a figura antiga. No caso em que o .dvi é gerado, a regra é definida dessa forma: artigo: figura1.eps artigo.dvi dvips -tletter -o artigo.ps artigo.pdf Desse jeito, o comando dvips é executado independente de ter havido ou não modificação no artigo.tex. Assim, qualquer mudança nas figuras vai aparecer no novo .ps e assim, as mudanças nas figuras são detectadas no arquivo final . É possível passar curingas no Makefile utilizando a função wildcard. Por exemplo, caso haja num diretório arquivos com os nomes artigo1-figura*.dia e todos eles gerarão .eps necessários para um artigo, a definição da lista das figuras pode ser assim: FIGURAS=$(patsubst %.dia,%.eps,$(wildcard artigo1-figura*.dia) . É possível incluir figuras no latex que não sejam somente .eps ou .ps. Figuras como .png podem ser incluídas (continua sendo usado o comando \includegraphics) só que, na hora de compilar o .pdf, deve ser usado o programa pdflatex e não o programa latex. Nesse caso deve-se lembrar de colocar o .png e o .pdf na lista de sufixos do Makefile. Por exemplo: .SUFFIXES: .dia .eps .ps .gpi .pdf .png E também deve-se lembrar de construir uma regra para gerar .pdf a partir do .tex: PDFTEX=/usr/bin/pdflatex .tex.pdf: ($(PDFTEX) $< && $(PDFTEX) $<) || (rm -f $@ && false) . Caso seja usada a regra anterior, volta-se a ter o problema de que mudanças nas figuras não são detectadas e por causa disso um novo .pdf não é gerado. Esse problema é o mesmo que acontece com arquivos .bib. Para resolver isso é preciso deixar explícito no Makefile que mudanças nos arquivos a, b e c devem gerar uma nova compilação do .pdf final. Isso é feito da seguinte forma: artigo.pdf: artigo.bib $(wildcard artigo1-figuras*.dia) - Mais sobre o latex . É possível evitar que o latex formate os nomes dos autores no bibtex definindo eles dentro de "{}". Por exemplo: author = "{Daniel M. Batista}" vai gerar nas referências o nome do autor "Daniel M. Batista" independente do estilo adotado para a bibliografia. . Um documento latex pode ter conteúdo espalhado em vários arquivos .tex. É preciso definir um arquivo principal, por exemplo "tese.tex" e dentro desse tese.tex inclui-se os vários arquivos com o comando \input. Por exemplo: \input{capitulo1.tex} \input{capitulo2.tex} Lembrando que, nesse caso, os arquivos capitulo1.tex e capitulo2.tex tem que ser definidos no Makefile como dependências para o tese.pdf. Por exemplo: tese.pdf: capitulo1.tex capitulo2.tex Sem isso, modificações nos arquivos capitulo?.tex não serão detectadas. - getopts . Um exemplo de como utilizar o getopts pode ser visto abaixo (o script é auto-explicativo). Relembrando: o getopts permite que sejam escritos scripts que aceitem argumentos na linha de comando do tipo "./script -l -a -b" independente das ordens dos argumentos. %%%%%%%%%%%%%%%%%%%%INÍCIO DO SCRIPT COM getopts%%%%%%%%%%%%%%%%%%%%%%%%% #!/bin/bash # Script besta: # Tem opções opcionais: -a e -b com argumento e -c sem argumento # Tem opções obrigatórias: -d e -e sem argumento e -f com argumento # Todas opções tem que ser definidas e se elas tiverem argumentos tem # que ter ':' depois. Não entendi porque precisa dos ':' iniciais na lista. Li # que é para que o getops não gere uns erros desnecessários. Vi aqui. Sem o ':' # inicial o getopts não diferencia argumento faltando de opção inexistente. # Tudo para ele vai ser tratado como -? setC=0 setA=0 setB=0 while getopts ":a:b:chd:e:f" opcoes; do case $opcoes in a ) setA=1; argumentoA=$OPTARG;; b ) setB=1; argumentoB=$OPTARG;; c ) setC=1;; d ) setD=1; argumentoD=$OPTARG;; e ) setE=1; argumentoE=$OPTARG;; f ) setF=1;; h ) echo "vc quer ajuda?! HUAHUAHUA" echo "$0 [-a argumentoA] [-b argumentoB] [-c] -d argumentoD -e argumentoE -f " exit 0;; \? ) echo "vc precisa de ajuda! Esse eh o ? HUAHUAHUA" exit 1;; * ) echo "Vc precisa de ajuda! Esse eh o * HUAHUAHUA" exit 1;; esac done # No caso das opções obrigatórias, basta fazer uns ifs depois: if [ -z $setD ] || [ -z $setE ] || [ -z $setF ]; then echo "vc precisa de ajuda! Tem argumento obrigatório faltando!" exit 1 fi # No caso das que precisam de argumentos, no final das contas, eu que preciso # fazer o teste: # if ([ ! -z $setA ] && [ -z $argumentoA ]) || ([ ! -z $setB ] && [ -z $argumentoB ]) || ([ ! -z $setE ] && [ -z $argumentoE ]) || ([ ! -z $setF ] && [ -z $argumentoF ]) ; then # echo "vc precisa de ajuda! Tem opção que precisa de argumento sem argumento!" # exit 1 # fi echo $setA--$argumentoA echo $setB--$argumentoB echo $setC echo $setD--$argumentoD echo $setE--$argumentoE echo $setF # Se a opcao passada nao estah na lista, o getopts considera que foi -? e roda # o que estiver definido para rodar no \?. Se a opcao estah na lista, precisa # de argumento mas nenhum argumento foi passado, o getops considera que foi -* # e roda o que estiver definido para rodar no * exit 0 %%%%%%%%%%%%%%%%%%%%FIM DO SCRIPT COM getopts%%%%%%%%%%%%%%%%%%%%%%%%%%%% - Fazendo o vim automaticamente colocar a data de última modificação . O código abaixo pode ser colocado no seu .vimrc para que sempre que um arquivo for modificado, seja adicionada a data de última modificação. Para que isso funcione, o arquivo em questão precisa ter nas suas 3 primeiras linhas a string "ultima modificação". %%%%%%%%%%%%%%%%%%%%INÍCIO DO CÓDIGO PARA O .vimrc%%%%%%%%%%%%%%%%%%%%%%%%% "Importante: Para aprender a fazer scripts em vim: "http://vimdoc.sourceforge.net/htmldoc/usr_41.html#script "e http://vimdoc.sourceforge.net/htmldoc/eval.html#functions " " ===== DATA AUTOMÁTICA ================== " insira em seus arquivos = "ultima modificação:" " em qualquer das três primeiras linhas fun! SetDate() mark z if getline(1) =~ ".*ultima modificação:" || \ getline(2) =~ ".*ultima modificação:" || \ getline(3) =~ ".*ultima modificação:" exec "1,5s/\s*ultima modificação: .*$/ultima modificação: " . strftime("%c") . "/" endif exec "'z" endfun " abaixo a chamada a função de data que é chamada toda vez que você " salva um arquivo preexistente fun! SetaData() mark z if getline(1) =~ ".*ultima alteração:" || \ getline(2) =~ ".*ultima alteração:" || \ getline(3) =~ ".*ultima alteração:" exec "1,5s/\s*ultima alteração: .*$/ultima alteração: " . strftime("%c") . "/" endif exec "'z" endfun " coloquei duas opções (alteração e modificação), assim " não tem perigo de você esquecer e o sistema " não atualizar a data do salvamento, outra melhoria na função " é que agora é válida para qualquer tipo de arquivo. se usar " num html por exemplo insira um começo de comentário na linha " da data e feche o comentário na próxima linha " abaixo a chamada a função de data que é chamada toda vez que você " salva um arquivo preexistente au BufWritePre * call SetDate() au BufWritePre * call SetaData() "==== Fim da Data Automática =============== %%%%%%%%%%%%%%%%%%%%FIM DO CÓDIGO PARA O .vimrc%%%%%%%%%%%%%%%%%%%%%%%%%%%% ======= Aula 10 (5 Nov 2008) - Mais sobre bash scripts e processamento dos pipes no shell ======= - Um exemplo de bash script para definir tags MP3 de um arquivo: %%%%%%%%%%%%%%%%%%%%FIM DO CÓDIGO PARA O .vimrc%%%%%%%%%%%%%%%%%%%%%%%%%%%% #!/bin/bash if [ $# -ne 6 ]; then echo "Uso: $0 artista album ano trilha titulo" exit 1 fi ARTISTA=$1 ALBUM=$2 ANO=$3 TRILHA=$4 TITULO=$5 ARQUIVO=$6 eyeD3 -1 -a "$ARTISTA" -A "$ALBUM" -Y $ANO -n"$TRILHA" -t"$TITULO" "$ARQUIVO" eyeD3 -2 -a "$ARTISTA" -A "$ALBUM" -Y $ANO -n"$TRILHA" -t"$TITULO" "$ARQUIVO" exit 0 %%%%%%%%%%%%%%%%%%%%FIM DO CÓDIGO PARA O .vimrc%%%%%%%%%%%%%%%%%%%%%%%%%%%% . Todo o segredo do script está no programa que define as tags (no exemplo acima é o eyeD3). O importante para ter em mente diz respeito à passagem de argumento no shell. Quando um argumento possui espaço no nome, ele deve ser passado entre aspas ou com "\ " no lugar do espaço. Por exemplo, supondo que exista um arquivo chamado "Teste Daniel.txt" no diretório atual, para dar um ls neste arquivo podem ser usadas as duas abordagens abaixo: ls -1 "Teste Daniel.txt" ls -1 Teste\ Daniel.txt Quando o comando é escrito no shell utilizando o TAB para auto-completar, o shell substitui automaticamente os espaços por "\ ". . Para que este script acima seja utilizado para atribuir tags para vários arquivos, de um diretório por exemplo, ele deve ser chamado dentro de outro script que vai fazer um loop em todos os arquivos .mp3. Por exemplo: for I in `ls -1 *.mp3`;do ./poe_tag.mp3 "ARTISTA" "ALBUM" etc...; done . Caso os nomes dos arquivos sigam algum padrão como "01-titulo.mp3", podem ser usados os comandos cut, sed e paste para definir os argumentos automaticamente a partir dos nomes dos arquivos. - Sempre que forem utilizados comandos ligados por pipe no shell é importante ter em mente que eles executam da direita para a esquerda. Por exemplo: ls -1 | sed -e 's/^/BLA/' No comando acima, o sed é o primeiro comando a ser executado. Quando o sed é executado sem um arquivo como argumento, como no exemplo acima, a sua ação é esperar por algum dado vindo da entrada padrão. O pipe tem o papel de fazer a saída padrão do comando da esquerda ir para a entrada padrão do comando da direita. Por conta disso, programas que não operem com dados vindos da entrada padrão dificilmente tirarão proveito do pipe. Se o objetivo é executar vários comandos em seqüência, o ideal é executá-los separados por ; ou por && ======= Aula 11 (12 Nov 2008) - Mais sobre programas no console e processamento dos pipes no shell ======= - Uma forma de comprovar que de fato os programas ligados por pipe são executados da direita para a esquerda é executar um comando como este: cat arquivo3 | cat arquivo2 - | cat arquivo1 - A saída que será exibida terá inicialmente o conteúdo do arquivo1, depois do arquivo2 e por último do arquivo3. Os '-' utilizados no segundo e no terceiro 'cat' tem o objetivo de fazê-los ler o conteúdo enviado para a saída padrão do comando localizado à esquerda. - Para contar quantas palavras uma string (ou um arquivo) possui basta usar o `wc` com a opção -w: # echo "1 2 3" | wc -w 3 - A quantidade de casas decimais das operações realizadas no `bc` são definidas pela variável 'scale' interna ao programa. Por exemplo, para imprimir o resultado de 2/3 com 3 casas decimais basta fazer: # echo "scale=3;2/3" | bc -l .666 - É possível utilizar o `seq` para gerar seqüências de números diferentes de 1, 2, 3, 4, etc... Por exemplo, para gerar uma seqüência de 0 a 50 de valores múltiplos de 5 basta fazer: # seq 0 5 50 0 5 10 15 20 25 30 35 40 45 50 - Uma forma de gerar o md5 de uma string no shell é utilizando o programa md5sum. Por exemplo: # echo -n "abc" | md5sum 900150983cd24fb0d6963f7d28e17f72 - É importante observar o '-n' no echo. Sem o '-n' o md5 gerado vai considerar que o ENTER também faz parte da string. ======= Aula 12 (26 Nov 2008) - funcoeszz, mplayer, scripts e rede e mais sobre shell ======= - Um conjunto de funções (comandos) muito útil para quem trabalha no terminal do linux está disponível no pacote funcoeszz. O pacote pode ser copiado de http://funcoeszz.net/download.html O pacote é um arquivo .sh contendo várias funções implementadas em shell script e que usam comandos como o 'bc', 'sed', 'cat', etc... O legal das funcoeszz é que já existe muita coisa pronta para atividades do dia-a-dia como por exemplo, somar dois números ou converter valores de uma moeda para outra, ou ainda, procurar a tradução e a pronúncia de uma palavra via web. A instalação das funcoezz é bem simples. Uma vez tendo copiado o código do site, basta rodar: funcoeszz zzzz --bashrc Este comando acima vai instalar as funcoeszz no arquivo ~/.bashrc do seu usuário e a partir do próximo login todos os comandos estarão disponíveis. Para conhecer todos os comandos disponíveis, o ideal é digitar 'zz' no shell e logo depois pressionar o <TAB>. Caso haja dúvida na utilização de alguma função basta rodar o nome do comando seguido de --help. Abaixo segue o exemplo da execução do comando zzpronuncia ############################################################################ lidenbrook@sun:~$ zzpronuncia parallelepiped URL: http://cougar.eb.com/soundc11/p/parall04.wav Gravado o arquivo '/tmp/zz.parallelepiped.wav' Input File : '/tmp/zz.parallelepiped.wav' Sample Size : 8-bit (1 byte) Sample Encoding: unsigned Channels : 1 Sample Rate : 11025 Time: 00:01.18 [00:00.00] of 00:01.18 (100% ) Samples out: 56.9k Clips: 0 Done. ############################################################################ O comando baixou um arquivo .wav e logo depois reproduziu o arquivo. Os programas utilizados foram o lynx e o play. É legal dar uma olhada no código das funcoeszz (/usr/bin/funcoeszz) porque tem muito código interessante que pode ser utilizado para outras coisas. - Dois programas muito utilizados para trabalhar com multimídia no linux são o mplayer e o mencoder. O primeiro é utilizado para tocar vídeos e áudios e o segundo é utilizado para converter ambos os tipos de arquivos em diversos formatos. Um exemplo de utilização comum do mencoder é mostrada abaixo. O comando é utilizado para converter um vídeo (.mkv) em um divx (.avi): mencoder -ovc lavc -oac lavc inf-walle.mkv -ss 00:01:30 -endpos 00:02:00 -o /tmp/video.avi Os parâmetros 'ovc' e 'oac' com as opções 'lavc' definem que o arquivo gerado será um divx. O parâmetro 'ss' informa a partir de que instante (hh:mm:ss) o arquivo começará a ser codificado e o parâmetro 'endpos' informa até quanto tempo o arquivo será codificado. O 'inf-walle.mkv' é o arquivo de entrada e o '-o /tmp/video.avi' define o arquivo que será gerado. O mplayer é mais simples de ser utilizado. A sua utilização mais comum é simplesmente: mplayer /tmp/video.avi as manpages de ambos os comandos são muito extensas e existe opção e suporte para praticamente todos os codecs de áudio e de vídeo existentes. Obs.: Uma sugestão de argumento para o mplayer é '-vo aa' :) O resultado é, no mínimo, interessante. - Um programa muito útil para simular interações do usuário com algum site via web é o curl. Ele tem várias opções para acessar conteúdos utilizando cookies, ssl, formulários, etc... A melhor forma de entender o curl é vê-lo em ação nestes scripts: http://www.lrc.ic.unicamp.br/~daniel/IniciaTorrentIS.txt http://www.lrc.ic.unicamp.br/~daniel/SubstituiTorrentsIS.txt http://www.lrc.ic.unicamp.br/~daniel/StatusTorrentIS.txt - Uma forma de utilizar listas de argumentos com tamanho variável em shell script é utilizando o comando shift. O que o shift faz é deslocar a lista de argumentos de uma unidade para a esquerda. Ou seja, se um script é executado recebendo 4 parâmetros como argumento: script a b c d e o 'shift' é chamado de dentro do 'script', depois da execução do 'shift', o script passará a ter os seguintes argumentos: b c d. O 'a' é removido da lista. Isso pode ser utilizado da seguinte forma em um script para que todos os argumentos sejam processados sem que se tenha conhecimento inicial da quantidade deles: ######################################################## while [ $# -ne 0 ]; do echo $1 shift done ######################################################## A cada passo do loop, o $1 vai mudando até que não exista mais nenhum argumento a ser processado. - Para copiar arquivos de uma máquina para outra via rede pode ser utilizado o programa netcat. Primeiro deve ser executado no servidor (a máquina que enviará os dados): cat arquivo | nc -l -p 1234 -q 1 e no lado do cliente: nc ip_do_servidor 1234 > arquivo o parâmetro 1234 é a porta onde o servidor irá escutar e onde o cliente vai conectar. O '-l' é o que informa ao 'nc' que ele está do lado do servidor ('l' vem de "listen"). O '-q 1' informa ao nc que ele deve sair 1 segundo depois do arquivo ser transferido. - Muitas vezes é necessário rodar um mesmo programa carregando opções diferentes. Por exemplo, um navegador acessando uma página costuma salvar cookies específicos para o usuário que está rodando o navegador. Se este usuário quiser abrir o navegador para uma mesma página que está aberta mas utilizando outros cookies, ele não vai conseguir. Uma forma de "enganar" o navegador é fazendo ele pensar que o home do usuário está em outro lugar. Com o home em outro lugar, o navegador vai criar outros arquivos ".*" no home e os cookies da nova sessão não vão se confundir com os cookies da sessão antiga. Como mudar o home? mkdir /tmp/novo_home EXPORT HOME=/tmp/novo_home Uma observação importante é que isso pode não funcionar direito com programas gráficos. Isso é ideal para ser utilizado com programas que rodam no terminal. - Para renomear vários arquivos pode ser usado o programa 'rename'. A sintaxe dele é a mesma do sed (Obs.: O 'rename' do fedora é diferente do 'rename' do debian. Abaixo segue a explicação do 'rename' no debian) Dada uma lista de arquivos teste*.txt, para substituir a extensão de todos para .sh, basta rodar: rename 's/txt$/sh/' teste*.txt ======= Aula 13 (05 Mar 2009) - revisão do semestre passado ======= Durante a revisão nós aprendemos mais coisas. - No bc é possível recuperar o resultado da última operação utilizando o comando 'last'. Por exemplo, ao fazer a seguinte conta no bc: 4/3 o resultado impresso é: 1.3333333333333 Se for digitado last seguido de ENTER, este mesmo valor é impresso: last 1.3333333333333 - Dado um arquivo com duas colunas, pode ser necessário inverter o conteúdo das colunas. Para inverter a linha por inteiro pode ser usado o 'rev' mas para inverter a ordem das colunas sem inverter os conteúdos individuais, pode ser usado o gawk: gawk '{print $2 " " $1}' arquivo.txt ======= Aula 14 (18 Mar 2009) - aplicando os vários comandos para resolver problemas e mais de 2GB de RAM no linux ======= - Como descobrir qual o comando mais executado pelo seu usuário no linux? É possível fazer um script utilizando o comando history para isso. O comando history lista todos os últimos comandos executados. Por exemplo, em uma máquina recém instalada: lidenbrook@sun:/tmp$ cd /tmp lidenbrook@sun:/tmp$ mkdir bla lidenbrook@sun:/tmp$ cd bla lidenbrook@sun:/tmp/bla$ ls lidenbrook@sun:/tmp/bla$ history 1 cd /tmp 2 mkdir bla 3 cd bla 4 ls 5 history A idéia portanto é pegar todos os comandos e contar quantas vezes cada um deles aparece. Abaixo estão 4 formas diferentes de fazer isso. A principal diferença entre os scripts está na forma que é utilizada para juntar a quantidade de vezes que cada comando é executado com o nome do comando. Obs.: para quem estava na aula, eu coloquei um primeiro cut cortando a partir da oitava coluna porque pode acontecer de ter somente 1 espaço antes dos números (caso o history liste mais de 1000 de entradas. De forma semelhante, o history pode colocar menos colunas). Usando o sort direto mas ordenando pela segunda coluna: for I in `history | cut -c 8- | cut -f 1 -d ' ' | sort | uniq`;do echo -n "$I "; history | cut -c 8- | cut -f 1 -d ' ' | grep ^$I$ | wc -l; done | sort -n -r -k 2 | less Usando o sort sem ordenar pela segunda coluna com o awk invertendo as colunas: for I in `history | cut -c 8- | cut -f 1 -d ' ' | sort | uniq`;do echo -n "$I "; history | cut -c 8- | cut -f 1 -d ' ' | grep ^$I$ | wc -l; done | gawk '{print $2 " " $1}' | sort -n -r | less Usando um único echo com a saída do comando dentro: for I in `history | cut -c 8- | cut -f 1 -d ' ' | sort | uniq`;do echo "`history | cut -c 8- | cut -f 1 -d ' ' | grep ^$I$ | wc -l` $I"; done | sort -n -r | less Usando o paste: for I in `history | cut -c 8- | cut -f 1 -d ' ' | sort | uniq`;do ( history | cut -c 8- | cut -f 1 -d ' ' | grep ^$I$ | wc -l; echo $I ) | paste -s -d ' ' ; done | sort -n -r | less Uma observação interessante é que se o history for utilizado dentro de um script com #!/bin/bash no início, ele não funciona! O problema é que o history fica "auto-contido" neste bash recém criado e para ele nenhum comando foi executado. O ideal, sempre que o history for utilizado, é fazer um script sem o #!/bin/bash no início (ou descobrir como o history faz para ler o arquivo que armazena todos os últimos comandos executados. Geralmente este arquivo é o ~/.bash_history). Outra observação é que em algumas distribiuções, execuções em seqüência do mesmo comando podem gerar uma única entrada no history! Por exemplo, no Debian. Então, neste caso, a contagem dos comandos mais executados pode não dar valores precisos. - Como fazer para o linux reconhecer mais de 2GB de memória? Ou mais de 4GB? Para isso o kernel tem que ser recompilado e a opção "HIGHMEM" deve ser habilitada. Para fazer isso, baixe o fonte de www.kernel.org, descompacte no diretório /usr/src, entre no diretório 'linux' que for criado e rode: make menuconfig Seguindo os menus, vá até a opção "Processor type and features" -> "High Memory Support" e habilite a opção desejada. Se vc tem 2GB, habilite a opção 4GB. Se vc tem 4GB, habilite a opção 64GB. Depois é só recompilar (o modo de fazer isso varia de distribuição para distribuição) e dar boot no kernel novo. Para saber quanto de memória vc tem de fato na sua máquina, rode o comando `lshw`. Para saber quanto o linux está reconhecendo, rode o comando `free` ou o comando `cat /proc/meminfo`. - Um comportamento estranho do comando 'ln' acontece quanto existe um diretório com o nome do link que vc quer criar. Por exemplo: lidenbrook@sun:/tmp$ mkdir 2 lidenbrook@sun:/tmp$ cd 2 lidenbrook@sun:/tmp/2$ touch 3 lidenbrook@sun:/tmp/2$ mkdir 4 lidenbrook@sun:/tmp/2$ ls -alFh total 4,0K drwx------ 3 lidenbrook lidenbrook 22 Mar 18 22:38 ./ drwxrwxrwt 12 root root 4,0K Mar 18 22:38 ../ -rw------- 1 lidenbrook lidenbrook 0 Mar 18 22:38 3 drwx------ 2 lidenbrook lidenbrook 6 Mar 18 22:38 4/ lidenbrook@sun:/tmp/2$ ln -s 3 4 lidenbrook@sun:/tmp/2$ ls -alFh total 4,0K drwx------ 3 lidenbrook lidenbrook 22 Mar 18 22:38 ./ drwxrwxrwt 12 root root 4,0K Mar 18 22:38 ../ -rw------- 1 lidenbrook lidenbrook 0 Mar 18 22:38 3 drwx------ 2 lidenbrook lidenbrook 14 Mar 18 22:38 4/ lidenbrook@sun:/tmp/2$ ls -alFh 4 total 0 drwx------ 2 lidenbrook lidenbrook 14 Mar 18 22:38 ./ drwx------ 3 lidenbrook lidenbrook 22 Mar 18 22:38 ../ lrwxrwxrwx 1 lidenbrook lidenbrook 1 Mar 18 22:38 3 -> 3 A intenção era criar um link chamado '4' apontando para o arquivo '3' mas como existe um diretório chamado '4', o resultado é que é criado um link dentro do diretório '4' chamado '3' apontando para o arquivo '3'. O problema é que o arquivo '3' está no diretório "de baixo" e por isso este link criado não aponta para o arquivo correto!! - Como comparar números no shell? O bc pode ser usado para isto. Por exemplo: echo "4>5" | bc devolve como saída o valor 0. Se fosse "4<5", a saída seria 1. ======= Aula 15 (25 Mar 2009) - segurança (simulação do hackerslab), permissões ======= - Um site muito legal para testar os conhecimentos de shell e de segurança era o www.hackerslab.org. Infelizmente muita coisa no site não está mais funcionando e não é possível brincar mais lá :( Os desafios consistiam em tentar explorar vulnerabilidades em programas e dessa forma conseguir "virar" um outro usuário. Era necessário fazer telnet em uma máquina remota rodando um shell bash e nesta máquina os comandos eram executados. As vulnerabilidades dos desafios exploravam programas com o suid bit habilitado. O suid bit é um dos três bits de permissão especiais que podem ser habilitados com o comando chmod: # chmod abcd arquivo a = as permissões especiais b = permissões para o dono do arquivo c = permissões para o grupo do arquivo d = permissões para os outros Quando o 'a' vale 4, significa que toda vez que o script (ou programa) arquivo for executado, ele será executado como se fosse o dono do programa. Então, se vc tem um programa chamado "level0" que pertence ao usuário level1 e tem o suid bit habilitado, significa que, se vc conseguir fazer o programa desviar sua execução do caminho padrão, vc será o usuário level1 neste momento! :) Esta mesma idéia vale para programas executados com o comando sudo. Resumindo com um exemplo simples, este abaixo era o enunciado do desafio no level 1 do hackerslab: "A computer student named Matthew is doing his C-programming homework. His teacher wanted him to create a program/script that if he types in a path name the program gives him what type of file/drectory it is. He was able to get it easily by using the `file` utility in the Unix-based commands. However, the flaw lies in this solution. Use this flaw and go on to the next level. HINT-One of 12 books known as the Minor prophets" A dica no final do enunciado era uma dica para descobrir o nome do programa, que era "amos". Sobre os minor prophets: http://en.wikipedia.org/wiki/Minor_prophets Uma vez encontrado o programa, o usuário executava ele e a saída era algo semelhante a isto: [level1@drill bin]$ ./amos path? / /: directory [level1@drill bin]$ Ou seja, o programa só rodava o `file`. O segredo para passar para o próximo nível estava em entender como o programa é executado. Lembrando das aulas passadas, num único "comando" no shell é possível passar vários programas separados por ';', '|', '&&' ou '||'. O conteúdo digitado após o "path?" era passado para o file e executado. Então, se o usuário escrever: /;/bin/sh, o que acontecerá será a execução deste comando: file /; /bin/sh No momento do ';', o usuário que está rodando é o level2 por causa do suid bit habilitado. Portanto, quem roda o '/bin/sh'? É o level2!! E assim viramos o level2 :) Abaixo o que acontecia: [level1@drill bin]$ ./amos path? /;/bin/sh /: directory bash$ whoami level2 bash$ Os desafios iniciais do hackerslab em sua maioria baseiavam-se em técnicas parecidas. Eles são importantes para que os programadores fiquem atentos na hora de escrever seus programas. Um simples scanf() pode trazer muita dor de cabeça, como no exemplo acima. - Para aqueles que não confiam em seus usuários, ou ainda para aqueles que gostariam de ter um resumo de todos os comandos e saídas que foram executados durante uma sessão, um programa legal é o `script`. Ele salva todos os comandos e saída em um arquivo chamado "typescript" no home do usuário. - A permissão padrão de arquivos é definida pelo valor atribuído com o comando umask. O umask recebe como padrão 3 números (Sim, são 3. Mesmo se colocar um monte de zero antes, ele só interpreta os 3 últimos). Esses valores são usados para fazer um XOR com 777 e o resultado é a permissão padrão que será usada para todos os arquivos binários que serão criados pelo usuário (por exemplo, a saída do gcc). Se for um arquivo não binário, basta tirar o bit de execução. Por exemplo: lidenbrook@sun:/tmp$ umask 022 lidenbrook@sun:/tmp$ gcc -o teste teste.c lidenbrook@sun:/tmp$ ls -alFh teste -rwxr-xr-x 1 lidenbrook lidenbrook 6,1K Mar 25 23:14 teste* lidenbrook@sun:/tmp$ umask 077 lidenbrook@sun:/tmp$ gcc -o teste teste.c lidenbrook@sun:/tmp$ ls -alFh teste -rwx------ 1 lidenbrook lidenbrook 6,1K Mar 25 23:14 teste* - Ainda sobre o chmod, o primeiro número utilizado pode assumir outros valores. Relembrando: chmod abcd arquivo Se 'a' for 4, significa que sempre que o arquivo for executado, o usuário que esterá executando será o dono do arquivo Se 'a' for 2, significa que sempre que o arquivo for executado, o grupo que estará executando será o grupo do arquivo Se 'a' for 1, significa que no diretório que tiver esse bit os usuários podem colocar arquivos lá mas somente os donos de cada arquivo podem modificá-los ou apagá-los. ======= Aula 16 (1 Abr 2009) .htaccess e um monte de comandos utéis para shell scripts e vim ======= - Muitas vezes não queremos que um certo conteúdo das nossas páginas fiquem públicas na web. Para evitar isso, podemos definir que uma determinada pasta de nosso public_html só pode ser acessada com senha. Para isso pode ser usado um arquivo .htaccess. Crie o .htaccess (é um arquivo texto) com o conteúdo abaixo e coloque no diretório que vc deseja proteger por senha: AuthName "Acesso Restrito" AuthType Basic AuthUserFile /home/daniel/senhas require valid-user Com o arquivo criado, rode o programa htpasswd para criar sua senha: htpasswd -c /home/daniel/senhas daniel O comando abaixo vai criar o arquivo /home/daniel/senhas e a senha definida será mantida criptografada lá. Após isso defina as permissões para os arquivos de modo que o usuário que está rodando o apache possa acessá-los. Na maioria das vezes, as opções são: . Dar permissão para o grupo ler os arquivos e mudar o grupo para www-data (Isso só é possível se vc fizer parte deste grupo) . Dar permissão para todos os outros usuários lerem o arquivo É importante *NÃO* manter o arquivo de senha em algum diretório dentro do public_html porque alguém pode tentar acessar já que geralmente nós não temos muita criatividade para escolher o nome deste arquivo :) - Às vezes é útil em um script pegarmos somente o nome do arquivo excluindo todo o seu caminho completo. Por exemplo em /home/daniel/sun.txt podemos estar interessados somente no sun.txt. Como fazer isso? Abaixo tem um script que realiza a ação de 3 formas diferentes. A segunda forma é bem legal para ver uma utilidade para o comando `rev`. A primeira forma é sem dúvida a mais exagerada e a terceira, a mais elegante :) O script recebe como argumento o caminho completo do arquivo. ########################################################################### #!/bin/bash # Primeiro método: Conta quantas '/' tem no caminho e soma mais um para usar # o resultado como entrada para o cut cortar no lugar certo # let I=`echo $1 | grep -o '/' | wc -l`+1 # echo $1 | cut -f $I -d '/' # Segundo método: Inverte tudo e pega o primeiro campo separado pela '/'. # Depois inverte de novo para pegar o nome do caminho correto # echo $1 | rev | cut -f -1 -d '/' | rev # Terceiro método: Faz o mesmo que os dois métodos acima fazem com um único # comando! basename $1 exit 0 ########################################################################### Dois comandos semelhantes ao `basename` são o `dirname` e o `readlink`. O primeiro é um complemento do basename. Ele retorna o diretório completo onde o arquivo está. O segundo lista para onde um link está apontando. Se o argumento do `readlink` não for um link, ele não retorna nada. - Uma forma de fazer o shell reagir a sinais enviados (por exemplo, pelos comandos kill ou killall) é usando o comando `trap`. Por exemplo: trap "echo alguém quer me matar :\( > SINAL15" 15 Dessa forma, toda vez que o shell do usuário receber o sinal 15 via kill, o arquivo SINAL15 será escrito no home com o conteúdo "alguém quem me matar :(". Abaixo segue a sequência dos comandos funcionando: lidenbrook@sun:~$ ls SINAL15 ls: impossível acessar SINAL15: Arquivo ou diretório não encontrado lidenbrook@sun:~$ trap "echo alguém quer me matar :\( > SINAL15" 15 lidenbrook@sun:~$ tty /dev/pts/2 lidenbrook@sun:~$ ps auwwwx | grep pts/2 1001 4130 0.2 0.0 6668 3984 pts/2 Ss 21:50 0:00 bash 1001 4183 0.0 0.0 3716 1024 pts/2 R+ 21:52 0:00 ps auwwwx 1001 4184 0.0 0.0 3140 772 pts/2 S+ 21:52 0:00 grep pts/2 lidenbrook@sun:~$ kill -15 4130 lidenbrook@sun:~$ ls SINAL15 SINAL15 lidenbrook@sun:~$ cat SINAL15 alguém quer me matar :( Uma utilidade legal para o trap seria fazer ele capturar o sinal 9 que é enviado para matar um processo e dessa forma evitar que o shell seja morto. Só que o sinal 9 é um caso a parte. Ele sempre é capturado pelo processo, que termina morto. - Duas descobertas úteis no vim: . Se o seu teclado não tem as teclas HOME nem END (o que é comum para os usuários de MacOS), vc pode ir para o fim e para o início das linhas no vim usando as teclas $ e 0. O $ vai para o fim da linha e o 0 vai para o início. Outra tecla útil e relacionada com estas ações é a tecla ^. Quando ele é pressionada o cursor vai para o início da primeira palavra da linha. Isso é muito útil para código! Já que na maioria das vezes o início da linha contém tabulações ou espaços por conta da identação. (lembrando que tem que teclar ^ e depois o espaço e que para todos esses comandos funcionarem é preciso sair do modo de inserção pressionando ESC antes.) . Como fazer busca por uma frase no vim considerando que pode ser que esta frase esteja por várias linhas? Por exemplo, como buscar a frase "aa bb cc" se o bb está no fim da linha e o cc no início da seguinte? Se buscar por "aa bb cc" não funciona. Uma forma é utilizando expressão regular e buscar por "aa[ |\n]\+bb[ |\n]\+cc". Assim estamos dizendo pro vim que ele tem que procurar um "aa" depois um ou mais espaços ou quebra de linhas, depois o "bb" , depois um ou mais espaços ou de quebras de linhas, e por último o "cc". Fazer uma macro no vim para essa busca deve ser uma tarefa divertida :) - Para lidar com a criação de arquivos ou diretórios temporários em shell scripts, o ideal é usar o comando `mktemp`. O comando sem argumentos cria um arquivo com nome aleatório no /tmp. O comando com o argumento '-d' cria um diretório. Isso é útil para evitar a escrita acidental de arquivos importantes. Abaixo um exemplo da execução: lidenbrook@sun:~$ mktemp /tmp/tmp.xnkKTukXnH lidenbrook@sun:~$ ls -lFh /tmp/tmp.xnkKTukXnH -rw------- 1 lidenbrook lidenbrook 0 Abr 1 22:05 /tmp/tmp.xnkKTukXnH lidenbrook@sun:~$ mktemp -d /tmp/tmp.TCLeBTaSws lidenbrook@sun:~$ ls -lFh /tmp/tmp.TCLeBTaSws/ total 4,0K drwx------ 2 lidenbrook lidenbrook 6 Abr 1 22:05 ./ drwxrwxrwt 11 root root 4,0K Abr 1 22:05 ../ lidenbrook@sun:~$ - Muitas vezes é necessário enviar mensagens para o usuário que está conectado no X. Como fazer isso se o usuário não tem nenhum terminal aberto? Pode ser usado o comando `zenity` para isso. Este comando abre uma janela na sessão de X do usuário. Por exemplo, suponha que há um script no cron que roda todo dia. É possível fazer este script enviar uma janela para o usuário caso ele esteja logado no X com o seguinte trecho de código: ########################################################################### USER=`/usr/bin/who | grep :0\) | cut -d\ -f1` export DISPLAY=:0 export XAUTHORITY=/home/$USER/.Xauthority MENSAGEMINICIO="O script está rodando..." /usr/bin/zenity --info --text="${MENSAGEMINICIO}" & ########################################################################### A primeira linha descobre qual é o usuário que está no X. Para melhorar o script pode ser colocado um 'if' logo abaixo para testar se este usuário é o seu usuário. Em seguida as variáveis DISPLAY e XAUTHORITY são definidas. A primeira variável diz em qual display está o usuário. É possível ter um display diferente de ":0" mas isso é um caso muito específico. A terceira variável é o que permite de fato que as janelas sejam enviadas para o usuário. Sem o XAUTHORITY somente o usuário na sua sessão atual consegue enviar janelas para o X. Depois disso basta rodar o zenity passando como parâmetro a mensagem desejada. Como resultado, uma janela aparecerá para o usuário com um botão OK para ele pressionar :) - É possível incluir um script dentro de outro usando o comando "source" ou simplesmente ".". Por exemplo, no script1.sh se tiver a linha: source script2.sh ou: . script2.sh é o mesmo que o conteúdo do script2.sh ser copiado por inteiro dentro do script1.sh. ======= Aula 17 (15 Abr 2009) nohup, chat via shell e mkfifo ======= . Quando rodamos um comando no shell via ssh e colocamos ele em background (com o & no final) pode ser que o ssh não "deixe" a gente deslogar. O logout trava e muitas vezes perdemos o shell. Na verdade há uma forma de forçar o logout, que é através da combinação de teclas ~. que faz com que o ssh morra do lado do cliente. Para evitar este travamento quando um programa vai para background ele deve ser executado com o nohup antes. Por exemplo: nohup script.sh & - O GNU/Linux já vem com dois comandos que permitem a gerência de um chat bem simples via console. O primeiro comando é o `mesg`. Para permitir que outras pessoas falem com vc no shell, rode: mesg y para evitar que as pessoas te incomodem, rode: mesg n Quando o `mesg` é executado sem parâmetros ele informa se vc está aceitando (y) ou não (n) as mensagens. Com o mesg habilitado, o próximo passo é descobrir onde o usuário está. Isso pode ser feito com o comando `w`. Um exemplo de saída está abaixo: daniel@pillars:~$ w 22:10:55 up 10 days, 4:16, 4 users, load average: 0,00, 0,00, 0,00 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT teste pts/0 c951a9c8.virtua. 17:44 55:41m 0.03s 0.03s -bash daniel pts/2 201.82.93.137 21:24 2.00s 0.54s 0.40s vim -X index.html teste2 pts/3 200-158-190-88.d 21:26 44:24m 0.00s 0.00s -bash daniel pts/4 201.82.93.137 22:03 0.00s 0.13s 0.00s w Se eu for o usuário teste e quiser falar com o usuário daniel eu escolheria algum dos dois terminais em que ele está. O ideal sempre é escolher o terminal em que a pessoa esteja rodando o bash e esteja muito tempo em 'idle'. Terminais em que a pessoa não está a muito tempo idle são terminais em que ela está trabalhando. Ah, claro, evitem mandar mensagem para o terminal em que alguém está editando um arquivo porque isso vai atrapalhar muito a pessoa. As mensagens vão aparecer por cima do editor de texto e vai ser a maior confusão :P. Ok, escohi falar com daniel no terminal pts/4. Então eu, teste, rodo no meu shell: write daniel pts/4 e começo a escrever. Daniel vai receber minhas mensagens e antes um aviso explicando o que está acontecendo. Um exemplo de como seria a saída no shell do usuário daniel está abaixo: ######################################################################### daniel@pillars:~$ Message from teste@pillars on pts/0 at 22:15 ... teste Ei, posso te pertubar? :P EOF ######################################################################### Daniel agora pode rodar um write para teste assim: write daniel pts/0 e os dois começam a conversar. Um problema do write é que não há campos separados para as mensagens de cada um. É possível que alguma mensagem chegue no meio de uma escrita sua e isso é chato. Mas para passar uma mensagem rápida para alguém, é uma boa solução. Um programa para chat bem mais completo que o write é o talk. O problema do talk é que ele depende de um daemon para funcionar e a maioria dos administradoers não habilita este daemon nas suas instalações. - Mas a vida não teria graça se só usássemos as coisas que já estão prontas. Abaixo seguem a sequência de alguns comandos que podem ser utilizados para fazer um chat bem simples entre dois usuários usando somente o cat e o tail. Vamos batizá-lo de chatFnac pois eu fiz isso enquanto estava na FNAC um dia desses brincando nos MacOS que ficam disponíveis pro povo mexer :) A idéia do chatFnac é utilizar arquivos para trocar as mensagens entre os usuários. Digamos que o usuario1 quer conversar com o usuario2. Ambos estão logados via ssh na mesma máquina. A primeira coisa que eles precisam fazer é criar um arquivo no /tmp com permissão para o outro ler. Por exemplo, usuario1 roda: touch /tmp/usuario1; chmod o+r /tmp/usuario1 e usuario2 roda: touch /tmp/usuario2; chmod o+r /tmp/usuario2 Agora o usuario1 roda um processo do tail em background lendo o arquivo do usuario2. Para não haver confusão sobre quais mensagens foram de fato enviadas pelo usuario2, o usuario1 pode passar um sed no arquivo para adicionar 'usuario2: ' antes das mensagens. Então usuario1 roda: tail -f /tmp/usuario2 | sed -e 's/^/usuario2: /' & e usuario2 roda: tail -f /tmp/usuario1 | sed -e 's/^/usuario1: /' & Agora cada um dos usuários começa a escrever suas mensagens para o outro. O usuario1 roda: cat - > /tmp/usuario1 e o usuario2 roda: cat - > /tmp/usuario2 e pronto :) Essa é uma boa forma de burlar as políticas de organizações que não permitem a execução de messenger no trabalho :P. Mas há um problema. O root da máquina pode desconfiar do que estão fazendo e bisbilhotar os arquivos /tmp/usuario1 e /tmp/usuario2. Mesmo que os arquivos sejam apagados depois, durante a conversa o root pode ficar de olho. Será que é possível passar as mensagens de um processo cat para o outro sem que elas fiquem em um arquivo no disco? A resposta é sim e isso é feito com named pipes que são criados com o comando mkfifo. - A idéia dos named pipes é a mesma dos pipes convencionais. O objetivo é interligar processos. Só que ao invés de usar o '|' no shell, a conexão dos dois processos é feita por um arquivo virtual, que é o named pipe criado pelo comando mkfifo. Dados novos em um named pipe só podem ser escritos depois que os dados antigos são lidos. Ou seja, se um processo escreve duas linhas para um named pipe mas nenhum processo lê nem a primeira, o primeiro processo fica bloqueado. No caso do chat acima, a vantagem do named pipe é que nada fica escrito no disco. O named pipe não é um arquivo real. Tudo que é escrito ali vai direto pro outro processo sem deixar rastros. Assim, o root bisbilhoteiro não vai conseguir ver nada. Na verdade ele até conseguiria mas ele teria que ser muito rápido e como consequência os usuarios saberiam que alguém está lendo as mensagens deles pois as mensagens vão parar de chegar já que elas passarão a ir para outro processo. Uma vantagem real é que depois da conversa os usuários não precisam apagar os arquivos pois nada fica lá. Como ficaria o chatFnac com named pipe? Basta mudar o comando touch por mkfifo e depois o `tail -f` por cat. Pronto :) Poderia ser incluída criptografia com o comando gpg no chatFnac. Apesar da tentativa na aula não ter dado certo (o gpg "segura" os dados até receber um CTRL+D), acho que é possível. - A principal utilidade do mkfifo é paralelizar processos demorados quando eles lidam com arquivos grandes. Por exemplo, suponha que há o arquivo um.gz e dois.gz e que vc quer ordenar ambos. Uma opção seria: zcat um.gz dois.gz | sort -n > tres Um problema dos comandos acima, caso vc tenha uma máquina multi-processada, é que eles usam um único processador. A saída do primeiro zcat é enviada para o sort que escreve no arquivo. Com named pipe isso pode ser paralelizado da seguinte forma: mkfifo a b zcat um.gz | sort > a & zcat dois.gz | sort > b & sort -m a b > tres o `sort -m` permite a ordenação de dois arquivos já ordenados. A diferença de tempo desta segunda sequência de comandos compensa quando os arquivos são grandes. Na máquina da sala 316 o primeiro comando levou 2 minutos e o segundo levou 1 minuto. Mais informações e exemplos da utilidade do mkfifo pode ser lidas no link http://slacy.com/blog/2008/12/on-mkfifo-and-doing-the-impossible/ - Outra utilidade do mkfifo é quando um arquivo grande deve ser lido várias vezes. Neste caso o mkfifo deve ser usado em conjunto com o comando `tee`. O comando `tee` escreve dados na saída padrão e em arquivos ao mesmo tempo. Por exemplo: ls -1 | tee a b c Vai escrever a saída do `ls -1` na tela e ao mesmo tempo nos arquivos a, b e c. Para que a saída não saia na tela: ls -1 | tee a b c 1>/dev/null Mas e se a, b e c forem named pipes? Por exemplo: mkfifo a b c cat /tmp/bla | tee a b c 1>/dev/null Desse jeito o arquivo /tmp/bla será lido uma única vez pelo cat e o seu contéudo será colocado em sequência nos named pipes a, b e c. Se três processos rodarem sobre o arquivo /tmp/bla, com esses comandos acima, os processos podem rodar em paralelo! Quanto maiores os arquivos, maior a vantagem em utilizar named pipes. - Obs.1: para gerar os arquivos durante os testes na aula, geramos arquivos aleatórios com a variável de ambiente $RANDOM do shell. Como o próprio nome sugere, esta variável mantém numeros que variam aleatoriamente. - Obs.2: acabamos descobrindo que o uniq tem uma opção '-c' que já conta quantas vezes ocorreu a ocorrência de uma dada string!!! Com isso aqueles scripts que olham quantas vezes um comando foi executado no history (veja a aula 14) pode ser diminuído para somente isto: history | cut -c 8- | cut -f 1 -d ' ' | sort | uniq -c | sort -n -r | less Não precisa daqueles `for` malucos e nem de rodar o history um monte de vez! Medindo o tempo aqui com o comando `time`, esta sequência de comandos rodou 5 vezes mais rápido do que a solução que tínhamos visto a algumas aulas atrás. Quanto maior o history, maior vai ser o ganho de tempo. ======= Aula 18 (29 Abr 2009) bargraph.pl, gnuplot e sed ======= - O gnuplot não é uma opção muito boa para fazer gráficos em barra. Muitos ajustes devem ser feitos para definir as cores das colunas, a posição delas no eixo x e os rótulos no eixo x. O ideal quando se quser fazer gráficos em barra é utilizar o script em perl bargraph.pl que pode ser copiado de http://www.burningcutlery.com/derek/bargraph/ O bargraph.pl recebe como entrada um único arquivo com as configurações do gráfico que vai ser gerado e com os dados também embutidos no mesmo arquivo. Abaixo segue um exemplo de arquivo de configuração do bargraph que vai gerar uma coluna para cada linha, todas as colunas com a mesma cor. A última coluna do gráfico vai ser uma média de todos os outros valores. ######################################################################### # basic bar graph example from Derek Bruening's PhD defense =harmean meanlabel=average =sortbmarks yformat=%gx xlabel=Benchmark ylabel=Memory usage versus code size ammp 15.07 applu 5.66 apsi 6.90 art 28.67 bzip2 24.00 crafty 10.99 eon 5.94 equake 19.14 gap 13.18 gcc 11.35 gzip 24.00 mcf 26.37 mesa 14.45 mgrid 12.94 parser 15.74 perlbmk 9.34 sixtrack 5.95 swim 17.62 twolf 10.36 vortex 8.04 vpr 14.51 wupwise 11.84 excel 9.25 photoshp 5.84 powerpnt 12.42 winword 8.79 ######################################################################### Há várias opções no bargraph.pl que podem ser consultadas na página onde o programa é disponibilizado. Uma informação importante é que o bargraph na realidade usa o gnuplot para gerar o gráfico e depois trabalha em cima do gráfico gerado para lidar com as cores de cada uma das barras. Portanto, é possível utilizar o bargraph em um primeiro passo (já que é muito mais fácil definir os dados nele do que no gnuplot), editar o arquivo do gnuplot gerado, fazer as modificações necessárias e rodar o gnuplot para gerar um .ps final. Para gerar um arquivo .eps direto do bargraph deve ser utilizada essa sintaxe: $ bargraph.pl arquivo.conf > arquivo.eps Paga gerar o arquivo do gnuplot, a sintaxe é esta: $ bargraph.pl -gnuplot arquivo.conf > arquivo.gpi - O gnuplot tem um milhão de opções. Algumas delas são exibidas abaixo: . Os rótulos do eixo x de um gráfico podem ser colocados na vertical. Na verdade podem ser rotacionados por qualquer ângulo usando a opção "rotate by" na definição do xtics: set xtics rotate by -45 ("Blind Guardian" 0, "Dream Theater" 1, "Deep Purple" 2, "Jack Johnson" 3, "Ben Harper" 5) Neste exemplo os nomes das bandas ficarão rotacionados por 45 graus negativos (eles ficarão apontando para o canto inferior direito do gráfico). Para rotacionar por 90 graus não é necessário adicionar o "by 45". . Os rótulos também podem ser deslocados para a direita ou para a esquerda com a opção "offset": set xtics offset -6 ("Blind Guardian" 0, "Dream Theater" 1, "Deep Purple" 2, "Jack Johnson" 3, "Ben Harper" 5) . Outra ação que pode ser realizada é mover para cima ou para baixo também com a opção offset mas passando os dados do deslocamento vertical após uma vírgula: set xtics offset 0,graph -0.35 ("Blind Guardian" 0, "Dream Theater" 1, "Deep Purple" 2, "Jack Johnson" 3, "Ben Harper" 5) . Para o caso de ser decidido construir um gráfico em barras no gnuplot é importante conhecer algumas opções do estilo boxes. As linhas abaixo definem que a largura de cada barra vai ser 90% do máximo (boxwidth 0.9) e que as barras devem ser preenchidas e ter uma linha nas bordas (style fill solid 1.0 border -1). O valor após o "solid" define o quanto cada cor vai ser "clareada" no gráfico. Um valor de 0.5 vai fazer o preto parecer cinza. set boxwidth 0.9 set style fill solid 1.0 border -1 . Para definir a cor de cada barra separadamente pode ser usada a opção "lc" do gnuplot e definir a cor utilizando os valores rgb como em html: plot 'bandas.dat' with boxes lc rgb "#ff0000", \ 'bandas2.dat' with boxes lc rgb "#000000" . É possível ainda preencher as barras com padrões monocromáticos. Para isso define-se os padrões de preenchimento das barras como "pattern" e não como "solid". Isso é útil para artigos que serão publicados em locais onde não há cores na versão final. plot 'bandas.dat' with boxes fill pattern 1, \ 'bandas2.dat' with boxes fill pattern 4 - O sed com a opção '-i' pode ser utilizada em diversos arquivos. Por exemplo: sed -i 's/Daniel/Batista/g' *.tex Vai substituir Daniel por Batista em todos os arquivos .tex do diretório. Os arquivos já serão modificados. Não há necessidade de enviar a saída do sed para outro lugar e sobrescrever o artigo anterior. Também é possível realizar mais de uma substituição com o sed ao mesmo tempo. Por exemplo: sed -i 's/Daniel/Batista/g;s/Macedo/M./g' *.tex Vai substituir tant Daniel por Batista quanto Macedo por M. . - Abaixo segue um script para adicionar \textit{ no início da primeira linha de todos os parágrafos e } no final da última linha de todos os parágrafos do arquivo teste.txt: ################################################################## #!/bin/bash export IFS=' ' #export IFS='' I=`head -n 1 teste.txt` sed -i "s/$I/\\\textit{$I/" teste.txt let J=1 for I in `grep -A1 -B1 ^$ teste.txt | grep -v "\-\-"`; do echo ================================== let K=J%2 if [ "$K" -eq "0" ]; then sed -i "s/$I/\\\textit{$I/" teste.txt else sed -i "s/$I/$I}/" teste.txt fi let J=J+1 echo ================================== done I=`tail -n 1 teste.txt` sed -i "s/$I/$I}/" teste.txt exit 0 ################################################################## Este script funciona mas algumas coisas precisam ser compreendidas melhor: . Por que o export IFS com ENTER faz o for não imprimir linhas em branco? . Por que \\\textit e \\\\textit produzem o mesmo resultado dentro do sed? Com apenas uma ou duas '\' o sed coloca uma tabulação no início da linha seguido de extit. Isso acontece porque o shell expande o \t. ======= Aula 19 (13 Mai 2009) tar, rsync, cvs, um exemplo de uso do curl e clafoutis :P ======= - O tar é um programa muito utilizado por sistemas de backup em fita. A utilidade do tar está em empacotar vários arquivos em um único arquivo. Assim fica fácil arquivar arquivos ou enviá-los como anexo por email. Para criar um arquivo .tar contendo vários arquivos do diretório "dir", basta executar no shell: tar cf dir.tar dir Para verificar o conteúdo do dir.tar, basta rodar: tar tf dir.tar Para extrair o conteúdo do dir.tar inteiro: tar xf dir.tar E para extrair um único arquivo que esteja dentro do dir.tar: tar xf dir.tar caminho/nome_do_arquivo_dentro_do_tar Para mudar o diretório onde o .tar será extraído utiliza-se a opção "-C": tar xf dir.tar -C /caminho/onde/o/conteudo/serah/extraido O tar não usa compressão por padrão. Para comprimir o conteúdo dos dados pode-se utilizar o gzip ou o bzip2. O bzip2 comprime melhor do que o gzip mas consome mais ciclos de CPU para comprimir/descomprimir. Para gerar um .tar comprimido com gzip, adicione a opção z no comando: tar zcf dir.tar.gz dir Para gerar um .tar comprimir com bzip2, adicione a opção j no comando: tar jcf dir.tar.bz2 dir Para extrair não há necessidade de colocar as opções 'z' ou 'j'. O tar sozinho descobre se o arquivo está compactado e descompacta da forma correta. É possível utilizar o tar de modo a copiar somente arquivos modificados a partir de uma dada data. Para isso utiliza-se a opção --newer DATA, onde DATA deve ser escrita no formato ano-mês-dia. Por exemplo, 2009-05-13. - Uma forma de manter dois diretórios iguais é através do programa rsync. Por exemplo, para manter o diretório DIR2 exatamente igual ao DIR1, utiliza-se: rsync -avz --progress DIR1/ DIR2 Esse comando deve ser executado sempre que arquivos em DIR1 forem modificados. Assim a cópia em DIR2 será mantida exatamente como DIR1. Da forma como o comando acima é executado, se um arquivo for apagado em DIR1, ele não será apagado em DIR2 na próxima execução do rsync. Para forçar que o arquivo seja apagado em DIR2 deve-se adicionar a opção "--delete-excluded". A grande vantagem do rsync com relação a uma cópia comum feita com o 'cp' é que o rsync copia de DIR1 para DIR2 somente o que tem de diferente entre os diretórios. O ganho no tempo de execução é óbvio quando se tem diretórios muito grandes mas que sofrem modificações em poucos arquivos com freqüência. O rsync pode ser utilizado via rede através de ssh. Para isso deve-se definir a variável de ambiente RSYNC_RSH com /usr/bin/ssh e o DIR1 (ou o DIR2) deve estar no formato usuario@maquina:DIR1. Por exemplo: export RSYNC_RSH="/usr/bin/ssh" rsync -avz --progress daniel@maquina.google.com:/home/daniel/DIR1/ DIR2 Esse comando vai copiar o conteúdo de /home/daniel/DIR1/ em maquina.google.com via SSH e colocar em DIR2. Somente as diferenças serão copiadas. Vc precisa ter uma conta em maquina.google.com para poder utilizar o rsync via ssh. - Outra opção de manter diretórios atualizados é utilizando o cvs. O cvs funciona de forma semelhante ao rsync. Ele copia somente as mudanças que tiverem sido feitas. A vantagem do cvs sobre o rsync é que ele mantém controles de versões nos arquivos. Assim é possível voltar a ter um arquivo exatamente como ele estava a algumas versões atrás. Antes de usar o cvs é preciso definir onde os repositórios serão mantidos. Por exemplo, se vc escolher por manter todos os seus repositórios em /home/daniel/repositoriosCVS, rode: export CVSROOT=/home/daniel/repositoriosCVS cvs init Após esses dois comandos vc pode começar a criar seus repositórios. Suponha que vc já tem dois diretórios locais na sua máquina chamados projeto1 e projeto2 e vc quer criar repositórios para cada um deles. Rode: export CVSROOT=/home/daniel/repositoriosCVS cd projeto1 cvs import projeto1 seu_nome inicio cd projeto2 cvs import projeto2 seu_nome inicio A execução dos `cvs import` vai chamar um editor de texto. Vc tem que colocar um resumo dos projetos que vc está incluindo. A string "inicio" pode ser modificada. Ela é só um marco que faça vc lembrar que neste instante vc estava começando o repositório. Os dois repositórios foram criados. A partir daqui vc pode trabalhar em qualquer lugar usando o CVS assim: export CVSROOT=/home/daniel/repositoriosCVS cvs checkout projeto1 Estes comandos vão enviar uma cópia do projeto1 como está no repositório para a sua máquina local. Vc pode trabalhar normalmente e ao final do trabalho rode, de dentro do diretório projeto1: cvs commit Este comando vai enviar para o servidor somente as mudanças que vc realizou. Assim como o cvs import, um editor de textos será aberto para vc resumir as modificações. Se vc estiver trabalhando com mais de uma pessoa no repositório, *SEMPRE* antes de rodar o commit rode: cvs update Este comando vai garantir que, antes de rodar um commit, vc está com a versão mais recente do código incluindo mudanças que outras pessoas possam ter feito enquanto vc estava trabalhando. Para adicionar novos arquivos num repositório do cvs rode: cvs add nome_do_arquivo Para remover: cvs remove nome_do_arquivo Para o `cvs remove` funcionar vc precisa apagar o arquivo com `rm` antes. Outros comandos úteis do cvs são: . cvs status - mostra informações de todos os arquivos do repositório. Informa se eles estão atualizados e se vc fez alguma modificação local neles . cvs diff - mostra diferenças de uma dada versão para a versão local . cvs log - exibe as informações que foram adicionadas após as execuções do cvs commit. Informa quem modificou os arquivos e quando. Assim como o rsync, o cvs também pode ser usado via ssh. Para isso, defina o CVSROOT no formato usuario@maquina:/caminho/dentro/da/maquina e define a variável CVS_RSH para /usr/bin/ssh. - Um exemplo de utilidade do curl, o navegador em linha de comando, é a atualização de status do twitter via shell. Basta rodar: curl -u usuario:senha -d status="Testando o curl para atualizar o twitter via shell :)" http://twitter.com/statuses/update.xml - Por último, mas não menos importante, segue a receita do clafoutis que eu levei para a aula de hoje :) Eu achei todos os ingredientes no super barão. . 8 ovos . 1 punhado de amêndoas . 3/4 copo de farinha de trigo (após algumas receitas anteriores mal sucedidas eu descobri que tem que peneirar a farinha antes) . 2+1/2 copo de açucar . 1/2 colher de chá de extrato de baunilha . 1 copo de "heavy cream" = 3/4 copo de creme de leite + 1/4 copo de manteiga sem sal derretida. Misture bem de modo a ficar homogêneo . 2 copos de leite . 350 gramas de cerejas Unte um pirex com manteiga e povilhe com um pouco de farinha de trigo. Pré-aqueça o forno a 350 graus. Com exceção das cerejas, misture todos os ingredientes no liquidificador. Ponha as cerejas no pirex e despeje a mistura sobre elas. Ponha no forno por 1 hora e meia. O resultado vai ser parecido com um pudim. Serve umas 12 pessoas fácil! Apesar do livro recomendar servir quente, eu recomendo servir frio :) ======= Aula 20 (27 Mai 2009) scripts com rm, sort, planilha eletrônica no shell e manipulação de pdfs ======= - Como fazer com que um 'rm' mova os dados para uma "lixeira" ao invés de apagar os arquivos? Pode-se criar o script abaixo e apontar o 'rm' para ele pelo comando 'alias': ====================================================================== #!/bin/bash # Autores: um monte de gente! # Data: 27 Mai 2009 # Bugs? Não nos mande e-mail. Conserte! # # Script rmlixeira # Este script move o argumento passado na linha de comando para o diretório # $RECYCLE.BIN$ dentro do home do usuário # # TODO: # 1-) Mover para a lixeira que esteja na partição onde os arquivos apagados se # encontram (usando dirname isso fica fácil). Se não fizer isso pode ficar # muito lento quando os dados apagados não estiverem no home mas sim em outra # partição; # 2-) Aceitar argumentos variáveis; # 3-) Interpretar todas as opções do 'rm' e traduzir para as opções do 'mv'. # 4-) Criar o RECYCLE.BIN caso ele não exista # 5-) Armazenar o caminho completo do arquivo em algum local dentro do # RECYCLE.BIN para possibilitar a recuperação. # 6-) Melhorar a mensagem de erro if [ $# -ne 1 ]; then echo "erro" exit 1 fi mv $1 \$RECYCLE.BIN\$ exit 0 ====================================================================== Uma vez salvo o script em, digamos, /home/daniel/bin/rmlixeira, basta rodar o alias: alias rm='/home/daniel/bin/rmlixeira' - Dado o arquivo abaixo: 1|5|0|2|1 6|1|4|2|3 Como pegar o segundo menor número de cada linha? Pode ser utilizado o script abaixo (Tentamos fazer usando o IFS igual a '|' para evitar o tr mas o sort não gostou não). ====================================================================== for I in `cat teste.txt`;do echo $I | tr '|' '\n' | sort -n | head -n 2 | tail -n 1; done ====================================================================== O script está considerando que o arquivo com os dados foi salvo com o nome teste.txt. O head e o tail podem ser substituídos por um sed: `sed -n '2p'` - O sort funciona muito estranho quando tem um zero no arquivo e quando há tanto letras quanto números. Por exemplo, o arquivo: 1 2 a 0 11 Se passado por um sort sem arguemnto, sairia: 0 1 11 2 a Ou seja, parece que ele compara o primeiro caracter pelo código ascii. Já se rodar com o sort -n: 0 a 1 2 11 Ele ordenou os números pelo valor numérico (11 maior do que 2) mas as letras ficam depois do zero e antes dos outros números! Já rodando com a opção -g: a 0 1 2 11 Dá um resultado semelhante ao do -n mas as letras ficam antes. - Como imprimir o conteúdo de uma variável sendo que o nome da variável está dentro de outra variável?! Tem que referenciar assim: ${!PRIMEIRAVARIAVEL}. Por exemplo: ============================================================ export A=B export B=2 echo ${A} ; # Este echo devolve 'B' echo ${!A} ; # Este echo devolve '2' ============================================================ - Uma opção à planilhas eletrônicas que usam o X é a 'sc'. Ela é uma planilha que roda no shell! Bem semelhante ao lotus123 :) Para inserir números na planilha deve-se digitar ENTER sobre a célula e escrever let A1=2 por exemplo Para inserir uma string, troque o 'let' por 'label'. Ele aceita funções matemáticas, financeiras, etc... Para salvar o arquivo basta pressionar 'P', escrever o nome do arquivo e dar ENTER. O arquivo é salvo em texto plano. Abaixo um exemplo de arquivo: ============================================================ # This data file was generated by the Spreadsheet Calculator. # You almost certainly shouldn't edit it. let A0 = 33 let B0 = 44 let C0 = 55 let B1 = A0+C0 label C1 = "teste" let D1 = @min(A0:C0) label C2 = "blablabla" label B3 = "blablakjs" let D4 = @ceil(10.3) let D5 = @ceil(10.4) goto A5 A0 ============================================================ - Muitas vezes é útil ter um indicador de progresso para termos uma noção que nosso script ainda está rodando. Como fazer isso? Abaixo está uma opção. Quando este trecho de código estiver em execução tem-se a impressão de que um traço está rodando :) ============================================================ #!/bin/bash echo while [ 1 ]; do echo -e -n "\b-" sleep 0.1 echo -e -n "\b\\" sleep 0.1 echo -e -n "\b|" sleep 0.1 echo -e -n "\b/" sleep 0.1 done echo "\nTerminado :)" exit 0 ============================================================ Abaixo segue mais um exemplo, desta vez com um . virando um O. Dando impressão de que o ponto está crescendo e depois diminuindo :) ============================================================ #!/bin/bash echo while [ 1 ]; do echo -e -n "\b." sleep 0.1 echo -e -n "\bo" sleep 0.1 echo -e -n "\b0" sleep 0.1 echo -e -n "\bO" sleep 0.1 echo -e -n "\b0" sleep 0.1 echo -e -n "\bo" sleep 0.1 echo -e -n "\b." sleep 0.1 done echo "\nTerminado :)" exit 0 ============================================================ Acho que dá para fazer algo legal usando emoticons também :). Por exemplo a sequência: :P :) :| :( Estes trechos de códigos podem ser utilizados como base para um script que detecta a quantidade de núcleos da máquina e inicia um processo para cada núcleo. Para fazer isso deve-se trocar o "while [ 1 ];" por um while que verifique quantos processos estão rodando (pode usar o ps junto com o wc para isso). - Abaixo segue uma lista de programas que podem ser utilizados para gerar/manipular/exibir pdf's e ps's: pdftk - permite a concatenação de vários .pdfs em um único .pdf. Permite também que somente uma parte de um .pdf seja extraída em outro .pdf pdfimages - Extrai as figuras de dentro de um .pdf a2ps - gera um arquivo .ps a partir de um código fonte. Pode ser passados códigos em diversas linguagens. O a2ps descobre a linguagem e gera um .ps bem organizado. Podem ser passados vários arquivos como argumento e um único .ps é gerado (ideal quando o código possui vários arquivos que são incluídos uns nos outros). lylipond - gera um arquivo .pdf para uma partitura escrita em uma linguagem própria. Útil para quem é músico. Um tutorial legal pode ser encontrado aqui: http://erasmo.info/lilypond/tutorial/ impressive - Exibe um .pdf e fornece diversas opções ideais para apresentações. Por exemplo ele permite que um trecho do slide seja destacado com um retângulo ou com um círculo, ele aceita zoom, as transições entre os slides são cheias de efeitos, ele permite uma visão global de todos os slides (útil no final da apresentação quando alguém pede: eu quero ver o slide número x). Tem muitas opções. Recomendo: http://impressive.sourceforge.net/ ou man impressive. Ele usa a aceleração da placa de vídeo então é ideal ter uma placa boa com suporte 3D funcionando. psnup - coloca várias páginas de um .pdf em uma única página. Ideal para imprimir vários slides em 1 única página ps2pdf, pdf2ps, pdftotext - Converte os formatos entre si. Os nomes já deixam claro o que cada um faz :) ======= Aula 21 (3 Jun 2009) Como combinar a quantidade de processos em execução com a quantidade de núcleos da máquina ======= - Em muitos casos têm-se scripts que executam vários processos ``CPU-intensive'' que são independentes uns dos outros. Em máquinas com mais de um núcleo é um desperdício de hardware rodar os processos de forma sequencial. Por exemplo, suponha que o programa `simulador` seja um programa que demore para rodar simulações e que ele receba como parâmetros as configurações das simulações contidas nos arquivos simulacaoXX.txt, onde XX varia de 00 a 99. A primeira opção para rodar todos os experimentos seria: for I in `ls -1 simulacao??.txt`; do simulador $I done Se isso for executado em uma máquina com um único núcleo não há problema. Se isso for executado em uma máquina com mais de um núcleo há desperdício porque cada execução do simulador só será iniciada após a anterior ter finalizado, mesmo que hajam núcleos ociosos durante a execução de um processo do simulador. Outra opção seria fazer assim: for I in `ls -1 simulacao??.txt`; do simulador $I & done O problema de fazer assim é que todos os processos do simulador vão executar em paralelo e isso pode deixar a máquina muito lenta já que haverá mais processos do que núcleos e a quantidade de mudanças de contexto feita pelo escalonador do SO fará os processos demorarem mais do que se cada um rodasse exclusivamente em um único núcleo. Então o ideal seria encontrar a quantidade de núcleos que a máquina possui e manter somente essa quantidade de processos pesados rodando (No caso do LRC o correto seria encontrar a quantidade de núcleos e dividir por 2 por conta das regras de utilizaçõa das máquinas do laboratório) Como encontrar a quantidade de núcleos? Existem pelo menos 3 formas: grep ^processor /proc/cpuinfo | wc -l grep ^processor /proc/cpuinfo | uniq -c -w 12 | cut -f 1 -d 'p' echo `grep ^processor /proc/cpuinfo | tail -n 1 | cut -f 2 -d ':'`+1 | bc Sabendo a quantidade de núcleos, como fazer com que a quantidade de processos rodando seja igual a essa quantidade? Pode ser feito assim: ============================================================ #!/bin/bash PROCESSADORES=`echo "Substitua por um dos 3 códigos apresentados acima :)"` for I in `ls -1 simulacao??.txt`; do while [ "$( ps uwwwx | grep simulador | grep -v grep | wc -l )" -eq $PROCESSADORES ]; do sleep 1 done ./simulador $I & done exit 0 ============================================================ - O código acima pode ser usado para tocar vários vídeos pelo mplayer ao mesmo tempo. Como evitar que os vídeos fiquem um em cima do outro? Toque ele com a opção '-vo x11' e ajuste a localização deles com '-geometry 0%:0%' ou '-geometry 0%:100%' ou '-geometry 100%:0%' ou '-geometry 100%:100%' (Cada uma dessa opções toca o vídeo em um canto da tela. Lembre-se dos quadrantes do plano cartesiano que fica fácil entender :) Uma observação importante é que o mplayer só funciona indo para background (com o &) se ele for executado com um 'nohup' antes. Precisamos entender o porquê disso. ======= Aula 22 (10 Jun 2009) Eficiência de pipes e substring no bash ======= - Considere a seguinte saída (esta saída é gerada pelo comando `brctl showmacs mybridge1` em uma máquina linux configurada para ser uma bridge): port no mac addr is local? ageing timer 1 00:10:4b:b6:c6:e4 no 119.25 1 00:50:04:43:82:85 no 0.00 1 00:50:da:45:45:b1 no 76.75 1 00:a0:24:d0:4c:d6 yes 0.00 1 00:a0:24:f0:22:71 no 5.81 1 08:00:09:b5:dc:41 no 22.22 1 08:00:09:fb:39:a1 no 27.24 1 08:00:09:fc:92:2c no 53.13 4 08:00:09:fc:d2:11 yes 0.00 1 08:00:09:fd:23:88 no 230.42 1 08:00:09:fe:0d:6f no 144.55 Como fazer para pegar os MAC addresses (a segunda coluna) de todos os endereços que não são locais? (tem 'no' na terceira coluna). Uma solução inicial seria esta: grep no | awk '{print $2}' | sed '1,1d' Porém, pensando em otimizar a seqüência de comandos, vale mais a pena colocar o sed logo no início (o sed vai apagar a primeria linha). Colocando o sed no início, o grep vai rodar em uma saída com menos linhas: sed '1,1d' | grep no | awk '{print $2}' Mas ainda dá para melhorar? O grep 'no' não pode ir para dentro do awk? Assim, excluí-se um comando, diminui a quantidade de processos e tudo deve rodar mais rápido (Claro, estamos considerando que a implementação de busca por expressão regular no awk é tão boa quanto a do grep e que a saída do comando pode ser grande. Em saídas pequenas não faz muita diferença ter 20 pipes ou 1 único pipe): sed '1,1d' | awk '/no/ {print $2}' E será que não dá para tirar o sed? Dá sim :) Existem duas opções. A primeira faz o awk começar a buscar só a partir da segunda linha assim: awk 'NR>1 && /no/ {print $2}' Outra opção, mais otimizada, seria falar para o awk buscar somente as linhas com 'no' que tenham tabulação antes. Assim, o 'no' da primeira linha não vai ser impresso: awk "/\tno/ {print \$2}" Então, vem a dúvida: existe alguma forma de otimizar automaticamente seqüências de pipe no shell? O SQL tem o comando EXPLAIN que dá detalhes a respeito de uma query antes dela ser executada. Com isso pode-se ter uma noção do quão pesada a query está. Se dá para fazer isso em bash, é uma incógnita. O principal problema é que, apesar do código-fonte estar disponível, não sabemos como as aplicações são implementadas. Por exemplo, o tail tem que ler o arquivo todo para imprimir as duas últimas linhas? Não faz sentido! O ideal é que seu código leia o arquivo de trás para frente. Fizemos uns testes com dois arquivos de tamanho diferente e a saída do tail levou o mesmo tempo. Saber como os programas funcionam pode ajudar na hora de escolher onde posicioná-los em uma seqüência de pipes. - É possível remover um pedaço de uma string no shell usando o %. Assim: export NOME=arquivo.flac Se fizermos: echo ${NOME%.flac} a saída será apenas "arquivo". Isso é muito útil quando precisamos mudar a extensão de um arquivo: for I in `ls -1 *.flac`;do echo ${I%.flac}.mp3; done ======= Aula 23 (17 Jun 2009) Script para redimensionar um conjunto de fotos ======= ##################################################################################### #!/bin/bash # Script para redimensionar fotos. Ele utiliza o imagemagick para fazer tudo. # Testado no fedora e no debian. As novas fotos são localizadas no mesmo # diretório que as fotos originais. # Por Cesar Chaves, Daniel Batista e Luciano Chaves # Em 17/06/2009 # Bugs? Não envie email pra gente! Resolva! # Sugestões? Envie para daniel@lrc.ic.unicamp.br # Dica: crie um lançador para o script no desktop e ponha o script no seu PATH. # Para redimensionar as fotos bastará selecioná-las e arrastá-las para o # lançador :) if [ $# -lt 3 ]; then echo "Uso: $0 <maior dimensão> <1|0> <arquivo1> [<arquivo2>] ..." echo "Onde: <maior dimensão> é a maior dimensão que a foto vai ter (horizontal ou vertical)" echo " <1|0> se a foto original vai ser sobrescrita (1) ou não (0)" echo " <arquivo*> é o caminho completo da foto" exit 1 fi # TODO: testar se os argumentos estão corretos (usar getopts) # Testo se todos os programas existem PROGRAMAS="identify dirname basename convert" for I in $PROGRAMAS; do type $I if [ $? -ne 0 ]; then exit 2 fi done # Para cada um dos arquivos, roda o convert (Estranho... rodando no fedora não # precisou disso porque ele chamou o script 1 vez para cada argumento mas # rodando no debian foi necessário porque ele chamou o script 1 única vez com # todos os argumentos. for I in `seq 3 $#`; do ARQUIVO=${!I} # TODO: Fazer o redimensionamento se for passado um diretório. Neste caso vai # rodar recursivo para todos os arquivos de dentro do diretório. if [ -f "${ARQUIVO}" ]; then SAIDA=`identify "${ARQUIVO}"` # Se o identify der erro ($? diferente de zero) é porque o arquivo não é uma imagem if [ $? -eq 0 ]; then # Com a saída do identify fica fácil pegar a resolução # horizontal x vertical GEOMETRY=`echo $SAIDA | grep -o "[0-9]*x[0-9]* " | tr -d ' '` HORIZONTAL=`echo $GEOMETRY | cut -f 1 -d 'x'` VERTICAL=`echo $GEOMETRY | cut -f 2 -d 'x'` # Se não for para sobrescrever a foto original então constrói o nome da # nova foto. Vai ter a resolução na frente. Por exemplo, /tmp/casa.jpg vai # virar /tmp/1024-casa.jpg if [ $2 -eq 0 ]; then NOVONOME="`dirname \"${ARQUIVO}\"`/$1-`basename \"${ARQUIVO}\"`" else NOVONOME=${ARQUIVO} fi # Descubro onde vai ficar a maior resolução. Na vertical ou na horizontal # (não sei se a foto está em orientação de retrato ou paisagem) mas só # mudo se a resolução da foto atual for maior do que a que eu quero # transformar if [ $HORIZONTAL -gt $VERTICAL ]; then if [ $HORIZONTAL -gt $1 ]; then convert "${ARQUIVO}" -resize $1x "$NOVONOME" fi else if [ $VERTICAL -gt $1 ]; then convert "${ARQUIVO}" -resize x$1 "$NOVONOME" fi fi fi fi done exit 0 ##################################################################################### ======= Aula 24 (01 Jul 2009) Scripts para gerenciar a lixeira do Gnome/KDE pelo shell ======= - O objetivo aqui é ter um novo "rm" que vai funcionar como um "mv" para o diretório ~/.local/share/Trash/ Este diretório é a lixeira que tanto o gnome quanto o KDE usam. Dentro deste diretório existem dois subdiretórios: info files O info mantém informações como o nome original do arquivo (o path completo) e a data que o arquivo foi removido. Cada arquivo (pode ser um arquivo regular ou um diretório) é mantido no "files". Se dois arquivos tem o mesmo basename o último é salvo com um nome .2, depois .3, e assim sucessivamente. Cada arquivo removido e mantido na lixeira tem um arquivo .trashinfo correspondente no subdir info. Fizemos 4 scripts: lxrm, lxls, lxcl e lxre: lxrm - Move o arquivo para a lixeira (ele pode ser usado em lugar do 'rm'. Um alias pode ser criado) lxls - Lista o conteúdo da lixeira lxcl - Limpa tudo da lixeira lxrs - Restaura algum arquivo da lixeira Abaixo seguem os códigos de cada um dos scripts ##################################################################################### #!/bin/bash # lxrm LIXEIRA="${HOME}/.local/share/Trash" if [ $# -lt 1 ]; then echo "ERRO" exit 1 fi DATA=`date +%Y-%m-%dT%T` while [ $# -ne 0 ]; do if [ "$( echo $1 | cut -c1 )" == "/" ]; then if [ -e "$1" ]; then if [ -e "${LIXEIRA}/files/`basename \"$1\"`" ]; then SAIDA=$( ls -1d ${LIXEIRA}/files/`basename "$1"`.[0-9]* 2>/dev/null | grep -E `basename "$1"`.[0-9]+$ | tail -n 1 2>/dev/null) if [ ! ${SAIDA} ]; then NOME="`basename "$1"`.2" else let NUMERO=`echo $SAIDA | rev | cut -f 1 -d '.' | rev`+1 NOME="`basename "$1"`.${NUMERO}" fi else NOME=`basename "$1"` fi echo "[Trash Info]" >> "$LIXEIRA/info/${NOME}.trashinfo" echo "Path=$1" >> "$LIXEIRA/info/${NOME}.trashinfo" echo "DeletionDate=$DATA" >> "$LIXEIRA/info/${NOME}.trashinfo" mv "$1" "$LIXEIRA/files/${NOME}" fi else J="`pwd`/$1" if [ -e "$J" ]; then if [ -e "${LIXEIRA}/files/`basename \"$J\"`" ]; then SAIDA=$( ls -1d ${LIXEIRA}/files/`basename "$J"`.[0-9]* 2>/dev/null | grep -E `basename "$J"`.[0-9]+$ | tail -n 1 2>/dev/null) if [ ! ${SAIDA} ]; then NOME="`basename "$J"`.2" else let NUMERO=`echo $SAIDA | rev | cut -f 1 -d '.' | rev`+1 NOME="`basename "$J"`.${NUMERO}" fi else NOME=`basename "$J"` fi echo "[Trash Info]" >> "$LIXEIRA/info/${NOME}.trashinfo" echo "Path=$J" >> "$LIXEIRA/info/${NOME}.trashinfo" echo "DeletionDate=$DATA" >> "$LIXEIRA/info/${NOME}.trashinfo" mv "$J" "$LIXEIRA/files/${NOME}" fi fi shift done exit 0 ##################################################################################### ##################################################################################### #!/bin/bash # lxls LIXEIRA="${HOME}/.local/share/Trash/" export IFS=' ' for I in `ls -1 ${LIXEIRA}/info/*.trashinfo 2>/dev/null`;do path=`grep ^Path\= $I | cut -f 2- -d '='` DATA=`grep ^DeletionDate\= $I | cut -f 2- -d '='` NOMEREAL=`basename $I` NOMEREAL=`echo ${NOMEREAL%.trashinfo}` echo -e "$DATA\t$NOMEREAL\t$path" done exit 0 ##################################################################################### ##################################################################################### #!/bin/bash # lxcl rm -rf ${HOME}/.local/share/Trash/files/* ${HOME}/.local/share/Trash/info/* exit 0 ##################################################################################### ##################################################################################### #!/bin/bash # lxrs LIXEIRA="${HOME}/.local/share/Trash" if [ $# -lt 1 ]; then echo "ERRO" exit 1 fi while [ $# -ne 0 ]; do if [ -e "${LIXEIRA}/files/$1" ]; then path=`grep ^Path\= "${LIXEIRA}/info/$1.trashinfo" | cut -f 2- -d '='` mv -v "${LIXEIRA}/files/$1" "$path" rm "${LIXEIRA}/info/$1.trashinfo" fi shift done exit 0 ##################################################################################### ======= Aula 25 (05 Ago 2009) Resumo do semestre, idéias e mais sobre o for e o zenity! ======= - É possível usar o 'for' no bash da mesma forma que no C. Isso é útil porque fica portável para o MacOS já que lá não existe o `seq`, mas sim o `jot` que não tem a mesma sintaxe. Aqui um exemplo para fazer um laço de 1 a 10: ##################################################################################### $ for ((i=0;i<11;i++)); do echo $i;done 0 1 2 3 4 5 6 7 8 9 10 ##################################################################################### - O zenity pode ser usado para informar ao usuário que algo está sendo feito. Para isso basta executá-lo com a opção --pulsate. Por exemplo: ##################################################################################### find / 2>/dev/null | zenity --progress --pulsate ##################################################################################### Isso faz o zenity ficar com a barra de progresso para um lado e para o outro. Termina quando o comando antes do pipe terminar. Se clicar em Cancelar. O comando antes do pipe é finalizado. ======= Aula 26 (10 Ago 2009) Como automatizar um acesso web? ======= - O curl é um programa executado no shell muito bom para reproduzir as ações executadas por um usuário durante o acesso a uma página web. Duas vantagens do curl sobre outros programas semelhantes são que ele suporta a definição da variável que informa para o servidor web qual é o navegador utilizado pelo usuário e também o suporte a cookies. Para definir a variável do navegador pode ser utilizada a opção "-A" do curl. Para salvar um cookie, utiliza-se a opção "-c" seguida do nome do arquivo onde o cookie será salvo. Para continuar o acesso à página com o cookie salvo anteriormente, utiliza-se a opção "-b" seguida do nome do arquivo onde o cookie foi salvo com a opção "-c". - Há mais informações sobre o curl nas aulas dos dias 26/11/2008 e 13/05/2009 - Entretanto, o curl sozinho muitas vezes não é útil quando uma página é acessada porque algumas informações só são trocadas entre o navegador e o servidor web quando o navegador tem a capacidade de interpretar javascripts ou outras linguagem além do HTML. Nesse caso recomenda-se a utilização de um sniffer durante o acesso à página a fim de ter um passo-a-passo de todas as informações trocadas entre cliente e servidor. De posse dos links, ips e nomes das conexões http realizadas, passa-se para o curl para que o acesso à página possa ser corretamente reproduzido. Um sniffer muito utilizado no GNU/Linux é o wireshark. A sua utilização básica é auto-explicativa. Mais informações podem ser encontradas em http://www.wireshark.org/ ======= Aula 27 (10 Ago 2009) Utilidades para o $RANDOM ======= - A variável $RANDOM no bash devolve números aleatórios uniformemente distribuídos entre 0 e 32767. Algumas execuções úteis: . Para obter um número aleatório entre 0 e x basta pegar o resto da divisão de $RANDOM por x+1. . Os dois scripts abaixo podem ser usados para gerar um número aleatório cuja string tenha exatamente 3 caracteres (por exemplo com zeros à esquerda) TRESMILISEGUNDOS=`let NOCACHE=RANDOM%1000+100; echo $NOCACHE | cut -c 1-3` TRESMILISEGUNDOS=`echo $RANDOM| rev | cut -c 1;echo $RANDOM | rev | cut -c 1; echo $RANDOM | rev | cut -c 1` ======= Aula 28 (26 Ago 2009) Processamento de imagens no shell ======= - Imagens no formato PGM são representadas por arquivos ASCII com matrizes que representam as imagens. Uma matriz de 200x200 representa uma figura de 200pixels por 200pixels. Cada valor inteiro na matriz representa a cor do pixel. No caso mais simples, esse valor é a cor em uma escala de cinza. 0 é preto e 255 é branco. É possível utilizar o shell para fazer um monte de operação interessante em figuras PGM. Uma informação importante é que o gimp escreve arquivos PGM com 1 único valor (pixel) por linha no arquivo ASCII. As 3 primeiras linhas do arquivo fornecem informações que permitem ao gimp remontar a figura (basta saber a resolução e assim ele consegue reconstruir a matriz). - Script 1: como fazer uma matriz PGM virar um arquivo igual ao gerado pelo gimp? Ou seja, um valor por linha: Primeira opção: ##################################################################################### for I in `cat $1`;do echo $I;done ##################################################################################### Segunda opção: ##################################################################################### cat $1 | tr ' ' '\n' ##################################################################################### - Script 2: como inserir uma borda branca de 3 pixels ao redor da figura? Ou seja, as 3 primeiras linhas terão apenas pixels com o valor 255, as 3 últimas linhas também e as 3 primeiras colunas e as 3 últimas colunas de todas as demais linhas. A entrada para este script precisa ser um arquivo "matricial": 1 2 3 4 3 3 3 3 4 4 4 4 1 2 3 4 ##################################################################################### #!/bin/bash LINHAS=`wc -l $1 | sed -E 's/^( )+//g' | cut -f 1 -d ' '` # Coloca as 3 primeiras linhas head -n 3 $1 | sed -E 's/[0-9]+/255/g' # Pega o meio do arquivo let MEIO=LINHAS-3 let MEIO2=MEIO-3 tail -n $MEIO $1 | head -n $MEIO2 | sed -E "s/[0-9]+ [0-9]+ [0-9]+$/255 255 255/g" | rev | sed -E "s/[0-9]+ [0-9]+ [0-9]+$/552 552 552/g" | rev # Coloca as 3 últimas linhas tail -n 3 $1 | sed -E 's/[0-9]+/255/g' exit 0 ##################################################################################### - Script 3: Como inverter as cores de um arquivo PGM? Ou seja, como tirar um negativo da imagem? Se um pixel é 0, na nova imagem o pixel será 255 (ou seja, basta fazer 255-(o valor do pixel atual)). O script abaixo compara se um arquivo é o "inverso" do outro caso sejam passados dois como entrada. Se for passado apenas 1 então ele só inverte. A entrada para este script pode ser tanto arquivo(s) "matricial" quanto "linha" (arquivo "linha" pode ser gerado com o Script 1 mostrado no início da aula): 1 2 3 4 3 3 3 3 4 4 4 4 1 2 3 4 ##################################################################################### #!/bin/bash TEMP1=`mktemp tmp.XXXXXX` TEMP2=`mktemp tmp.XXXXXX` echo "Inverso do arquivo $1 gerado em $TEMP1" for I in `cat $1`; do let J=255-I echo $J done >> $TEMP1 if [ $# -eq 2 ]; then for I in `cat $2`; do echo $I done >> $TEMP2 echo "Comparando $1 com $2..." diff --brief $TEMP1 $TEMP2 fi exit 0 ##################################################################################### - Script 4: Como transformar o arquivo linha em um arquivo matricial? Este script precisa saber a resolução da figura. $2 é o número de colunas e $3 o número de linhas: ##################################################################################### #!/bin/bash LINHAS=$3 COLUNAS=$2 for ((I=1;I<LINHAS+1;I++)); do let MULTIPLICADOR=COLUNAS*I head -n $MULTIPLICADOR $1 | tail -n $COLUNAS | paste -s -d ' ' - done exit 0 ##################################################################################### - Script 5: Como verificar se um arquivo PGM está com uma borda preta de 3 pixels correta?. A entrada para este script é um arquivo "matricial". ##################################################################################### #!/bin/bash # Compara 3 primeiras linhas for I in `head -n 3 $1`; do if [ $I -ne 0 ]; then export ERRO=1 break fi done if [ $ERRO ]; then echo "Erro nas primeiras linhas!" fi # Compara 3 últimas linhas for I in `tail -n 3 $1`; do if [ $I -ne 0 ]; then export ERRO=1 break fi done if [ $ERRO ]; then echo "Erro nas últimas linhas!" fi # Compara 3 primeiras colunas for I in `cut -f 1-3 -d ' ' $1`; do if [ $I -ne 0 ]; then export ERRO=1 break fi done if [ $ERRO ]; then echo "Erro nas primeiras colunas!" fi # Compara 3 últimas colunas if [ "$( grep -v " 0 0 0$" $1 )" != "" ]; then export ERRO=1 echo -n "Erro nas últimas colunas das linhas: " for I in `grep -n -v " 0 0 0$" $1 | cut -f 1 -d ':'`; do echo -n "$I " done echo fi if [ ! $ERRO ]; then echo "AEEEEEEEEE Vc tirou 10!!!" fi exit 0 ##################################################################################### ======= Aula 29 (2 Set 2009) Um wrapper para rodar o seq e uma utilidade para o gocr ======= - O MacOS não vem com o programa seq, um programa muito útil para gerar laços e muito utilizado no GNU/Linux. O script abaixo pode ser usado como um substituto para o seq. Este código precisa ser ampliado para aceitar as opções que o seq real aceita (a principal delas é o -w, que faz todos os números impressos terem o mesmo comprimento. Por exemplo, 02, 04, 06, 08, 10, ao invés de 2, 4, 6, 8, 10. ##################################################################################### #!/bin/bash # WHAT? # Um substituto para o seq. Isso é útil para quem tem MacOS pois não há seq no # bash dele. Só há o jot e é muito estranho de usar. Com este wrapper pro seq, # scripts que usem o seq no bash do linux vão funcionar sem problemas no MacOS. # HOW? # O melhor a fazer é ler a manpage do seq porque aí tudo que está implementado # aqui fica fácil de entender :) # BUGS? # Resolva e avise pra gente! :P # TODO # Colocar as opções -w, -f e -s (Dificilmente implementaremos as duas últimas. # O -w é útil). if [ $# -eq 1 ] && [ $1 -gt 0 ]; then PRIMEIRO=1 ULTIMO=$1 MEIO=1 elif [ $# -eq 2 ]; then PRIMEIRO=$1 ULTIMO=$2 MEIO=1 elif [ $# -eq 3 ]; then PRIMEIRO=$1 ULTIMO=$3 MEIO=$2 else exit 0 fi if [ $PRIMEIRO -le $ULTIMO ] && [ $MEIO -gt 0 ] ; then for ((i=PRIMEIRO;i<=ULTIMO;i+=MEIO)); do echo $i done elif [ $PRIMEIRO -ge $ULTIMO ] && [ $MEIO -lt 0 ]; then for ((i=PRIMEIRO;i>=ULTIMO;i+=MEIO)); do echo $i done else exit 0 fi exit 0 ##################################################################################### - Para usar o script acima no MacOS, salve ele em algum diretório (Recomendo que seja no ~/bin) com o nome seq, dê permissão de execução com o chmod +x seq e depois coloque este diretório no PATH adicionando as seguintes linhas no seu ~/.bash_profile: export PATH=$PATH:/Diretório/onde/o/seq/está - No vim, para substituir as tabulações por espaços pode-se adicionar as seguintes linhas no ~/.vimrc: set ts=3 set expandtab sw=3 ts=3 - O gocr é um programa que recebe como entrada uma imagem em formato .pgm e faz o reconhecimento de caracteres daquela imagem. Isso é útil para reconhecer de forma automática aquelas imagens que aparecem em sites para verificar se quem está acessando o conteúdo é uma máquina ou uma pessoa (esse processo é chamado de captcha). O script abaixo é um exemplo de como utilizar o gocr em um script para ler imagens deste tipo. Testamos ele e ele funcionou muito bem!!! Reconheceu os caracteres de várias imagens! O próximo passo vai ser utilizar o script abaixo para permitir que sejam enviadas mensagens SMS pelo console. Um exemplo de site que permite o envio de SMS's gratuitos e que usa essas imagens captcha fáceis de serem lidas pelo gocr é o http://www.torpedogratis.net/ (Aqui vem uma informação imoprtante. O gocr só funciona direito com imagens captcha fáceis de serem lidas) Obs.: para utilizar o script no MacOS é preciso baixar o código-fonte do imagemagick e do gocr. Depois ambos tem que ser compilados e instalados. Para isso é preciso ter instalado o x-code, o ambiente de desenvolvimento da Apple que está disponível no site da Apple ou no segundo DVD que vem com o Mac. ##################################################################################### #!/bin/bash # WHAT? # Um programa que mostra utilidade para o gocr. Ele fica num loop infinito # baixando imagens captcha e tentando fazer o reconhecimento. Sempre que ele # "pensa" que um reconhecimento certo foi feito, ele exibe no shell o que ele # encontrou e também a imagem para que seja comparado se está certo. # HOW? # O script usa bastante o convert (programa do imagemagick) e o gocr. O convert # é utilizado para transformar a imagem em pgm (o gocr só funciona com imagem # pgm). Depois as cores da imagem são tratadas de modo que fiquem letras pretas # no fundo branco. O passo seguinte é rodar o gocr e verificar se ele encontrou # 4 caracteres. Se sim, ele exibe o que ele encontrou. Se não, ele tenta de novo. O # script roda num laço infinito. Para interromper, basta pressionar CTRL+c. # BUGS? # Resolva e avise pra gente! :P # TODO # Processar a imagem captcha de modo a evitar que o gocr se atrapalhe quando # uma letra está mais em cima do que a outra (o gocr pensa que estão em linhas # diferentes e isso afeta a ordem com que os caracteres são reconhecidos). Esse # problema aconteceu com algumas imagens. # Vamos utilizar este script dentro de um script para enviar SMS pelo shell. O # site http://www.torpedogratis.net/ vai ser usado para enviar os SMS. # Conta quantas vezes o gocr foi rodado só para ter uma noção da quantidade de # acertos / quantidade de tentativas. export VEZES=0 # Para evitar ficar sujando seu diretório atual cd /tmp # Laço infinito. Para ler a próxima imagem, basta pressionar ENTER. Para sair, # CTRL+c. while [ 1 ]; do # A quantidade de caracteres que o gocr reconheceu. No exemplo que estamos # usando tem que ler 4. NUMERO=0 while [ $NUMERO -ne 4 ]; do rm -f captcha.jpg captcha1.pgm captcha2.pgm captcha2.jpg # Baixa a imagem (ponha aqui o link da imagem. Este link abaixo é um link # que a cada acesso gera uma imagem diferente. Ótimo para testar o script!) curl -O http://torpedo.oiloja.com.br/oitorpedo/captcha.jpg 1>/dev/null 2>/dev/null # Transforma a imagem em pgm no modo ASCII com o "-compress none" # (colocamos em modo ASCII porque ele vai gerar uma matriz em cima da qual # a gente pode trabalhar no shell) convert -compress none captcha.jpg captcha.pgm 1>/dev/null 2>/dev/null # Gera um novo arquivo com as cores extremas. As letras vão ficar pretas e # o fundo branco. Para isso basta fazer um teste nos pixels. Cores muito # claras (valores maiores que 230) ou muito escuras (menores que 150) são # cores das letras. Então colocamos todas elas pretas, ou seja, os valores # do pixel serão 0. As outras ficam brancas, 255. # Mas as 3 primeiras linhas temos que manter porque elas são # necessárias para interpretar o .pgm corretamente. head -n 3 captcha.pgm >> captcha1.pgm for I in `sed '1,3d' captcha.pgm`; do if [ $I -gt 230 ] || [ $I -lt 150 ]; then echo 0 >> captcha1.pgm else echo 255 >> captcha1.pgm fi; done # O gocr só gosta de imagens pgm com compressão, então rodamos o convert de # novo, dessa vez sem a opção "-compress none" convert captcha1.pgm captcha2.pgm 1>/dev/null 2>/dev/null # Vemos quantos caracteres o gocr leu. Se for 4, então sai deste laço # por causa da condição do while lá em cima. NUMERO=`gocr captcha2.pgm 2>/dev/null | tr -d ' ' | tr -d '\n' | wc -m` let VEZES=VEZES+1 done # Se sair do laço imprimimos as informações de quantas vezes rodou, exibe o que # o gocr leu e exibe também a figura. echo [RODOU $VEZES] gocr captcha2.pgm 2>/dev/null | tr -d ' ' | tr -d '\n' # Este último convert é só para gerar um .jpg que vai ficar mais fácil para # abrir no MacOS. Por algum motivo que ainda não sei, O convert não consegue # transformar o último pgm (captcha2.pgm) em jpg convert captcha.pgm captcha.jpg 1>/dev/null 2>/dev/null # Testamos se existe o xview. Se sim, abre com ele. Se não, roda o open (útil # para o MacOS) if [ "$(xview -version 2>/dev/null)" != "" ]; then PROGRAMA=xview else PROGRAMA=open fi $PROGRAMA captcha.jpg 1>/dev/null 2>/dev/null & # Pressionando ENTER, tudo continua... read done exit 0 ##################################################################################### ======= Aula 30 (9 Set 2009) Um script para enviar mensagens SMS utilizando o torpedogratis.net ======= - O vim tem suporte a abas!!! Para abrir dois arquivos em abas diferentes no vim, rode: vim -p arquivo1.txt arquivo2.txt Para mudar de abas basta: CTRL+PGDOWN ou CTRL+PGUP ou então pode ser digitado no modo visual: :tabnext :tabprev - O script da aula passada foi só o início de um script para enviar mensagens SMS gratuitas pelo shell utilizando o serviço do site www.torpedogratis.net. Abaixo segue o script. Em resumo, o curl faz todo o trabalho. Só foi preciso rodar um sniffer durante o acesso para descobrir cada um dos passos até a mensagem ser enviada. Ainda falta fazer alguns testes. O TODO no código abaixo lista tudo que falta fazer. ##################################################################################### #!/bin/bash # WHAT? # Um script que envia mensagens SMS pelo site torpedogratis.net. Ele estende o # script que usa o gocr e que foi feito na aula de 02/09/2009 # HOW? # O script usa bastante o curl para fazer o acesso ao site do torpedogratis # como se ele estivesse sendo feito por um navegador. Para descobrir quais URLs # devem ser acessadas nós fizemos um acesso real e gravamos tudo com o # wireshark. # BUGS? # Resolva e avise pra gente! :P # TODO # 1. Processar a mensagem de modo que sejam retirados os acentos e os caracteres # especiais. # 2. Modificar para funcionar com todas as operadoras que o torpedogratis # aceita: vivo, claro e tim. Por enquanto só funciona com a oi # 3. Lidar com mensagens maiores do que o tamanho máximo permitido (116) # 4. Colocar alguma identificação visual do progresso. Talvez: |/-\|/.... Por # enquanto está colocando pontos # 5. Fazer uma agenda if [ $# -ne 5 ]; then echo "Uso: $0 <DDD> <TELEFONE> <OPERADORA> <MENSAGEM> <NOME>" echo "Obs.: Por enquanto, <OPERADORA> só funciona se for oi" echo "Obs.: A mensagem só funciona se não tiver acento nem caracter especial" exit 1 fi # Dados para o envio do SMS DDD=$1 TELEFONE=$2 OPERADORA=$3 MENSAGEM=`echo "$4" | tr ' ' '+'` NOME=$5 if [ "${OPERADORA}" != "oi" ]; then echo "Uso: $0 <DDD> <TELEFONE> <OPERADORA> <MENSAGEM> <NOME>" echo "Obs.: Por enquanto, <OPERADORA> só funciona se for oi" echo "Obs.: A mensagem só funciona se não tiver acento nem caracter especial" exit 1 fi # O User Agent que o curl vai enviar para que os logs do torpedogratis não # detectem que estamos usando um "bot" UA="Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.11) Gecko/2009061212 Iceweasel/3.0.12 (Debian-3.0.12-1)" # Vai para o /tmp para não bagunçar seu diretório atual DIRATUAL=`pwd` cd /tmp # Conta quantas vezes o gocr foi rodado só para ter uma noção da quantidade de # acertos / quantidade de tentativas. export VEZES=0 # Laço infinito. Só sai quando conseguir enviar a mensagem. Ou seja, quando # a imagem for lida corretamente pelo gocr ACERTOU=0 while [ $ACERTOU -ne 1 ]; do # A quantidade de caracteres que o gocr reconheceu. No exemplo que estamos # usando tem que ler 4 (Este número -- 4 -- talvez mude para outras # operadoras. No caso da oi. São 4). NUMERO=0 while [ $NUMERO -ne 4 ]; do rm -f captcha.jpg captcha.png captcha1.pgm captcha2.pgm captcha2.jpg echo -n . # O primeiro acesso já envia a mensagem para o torpedogratis curl -A "$UA" -d operadora=${OPERADORA} -d ddd=${DDD} -d numero=${TELEFONE} -d sms=${MENSAGEM} -d nomer=${NOME} -d dddr=0 -d numeror=Seu+n%FAmero -d enviar.x=54 -d enviar.y=18 http://www.torpedogratis.net/sms.php -o 01.html 2>/dev/null # O .html recebido pelo primeiro acesso vai dizer um ID que será # necessário para os próximos acessos. Também neste .html terá o link # para o segundo acesso. ID=`head -n 1 01.html | cut -f 5 -d '=' | cut -f 1 -d '&'` ENDERECO02=`head -n 1 01.html | cut -f 4- -d '=' | cut -f 1 -d '"'` echo -n . # O segundo acesso pede o envio da mensagem (aqui identifica qual é a # operadora) para o site da operadora curl -A "$UA" http://www.torpedogratis.net/${ENDERECO02} -o 02.html 2>/dev/null # O .html recebido pelo segundo acesso vai dizer qual o endereço a seguir ENDERECO03=`grep script 02.html | cut -f 2 -d '"'` echo -n . # O terceiro acesso finalmente trará o link da imagem captcha que precisa # ser reconhecida curl -A "$UA" $ENDERECO03 -o 03.html 2>/dev/null # O link para a imagem captcha está neste .html ENDERECO04=`grep "img src=\"cap/" 03.html | cut -f 2 -d '"'` echo -n . # Finalmente, baixando a imagem captcha curl -A "$UA" http://www.torpedogratis.net/${ENDERECO04} -o captcha.png 2>/dev/null echo -n . # Transforma a imagem em pgm no modo ASCII com o "-compress none" # (colocamos em modo ASCII porque ele vai gerar uma matriz em cima da qual # a gente pode trabalhar no shell) convert -compress none captcha.png captcha.pgm 1>/dev/null 2>/dev/null echo -n . # Gera um novo arquivo com as cores extremas. As letras vão ficar pretas e # o fundo branco. Para isso basta fazer um teste nos pixels. Cores muito # claras (valores maiores que 230) ou muito escuras (menores que 150) são # cores das letras. Então colocamos todas elas pretas, ou seja, os valores # do pixel serão 0. As outras ficam brancas, 255. # Mas as 3 primeiras linhas temos que manter porque elas são # necessárias para interpretar o .pgm corretamente. head -n 3 captcha.pgm >> captcha1.pgm for I in `sed '1,3d' captcha.pgm`; do if [ $I -gt 230 ] || [ $I -lt 150 ]; then echo 0 >> captcha1.pgm else echo 255 >> captcha1.pgm fi; done echo -n . # O gocr só gosta de imagens pgm com compressão, então rodamos o convert de # novo, dessa vez sem a opção "-compress none" convert captcha1.pgm captcha2.pgm 1>/dev/null 2>/dev/null echo -n . # Vemos quantos caracteres o gocr leu. Se for 4, então sai deste laço # por causa da condição do while lá em cima. NUMERO=`gocr captcha2.pgm 2>/dev/null | tr -d ' ' | tr -d '\n' | wc -m` let VEZES=VEZES+1 done # Se sair do laço, é porque reconheceu 4 caracteres. Agora enviarei a saída # do gocr para o site do torpedogratis para ver se está certo. SAIDAGOCR=`gocr captcha2.pgm 2>/dev/null | tr -d ' ' | tr -d '\n'` ###### Só para debug ###### # # Este último convert é só para gerar um .jpg que vai ficar mais fácil para # # abrir no MacOS. Por algum motivo que ainda não sei, O convert não consegue # # transformar o último pgm (captcha2.pgm) em jpg # convert captcha.pgm captcha.jpg 1>/dev/null 2>/dev/null # # Testamos se existe o xview. Se sim, abre com ele. Se não, roda o open (útil # # para o MacOS) # if [ "$(xview -version 2>/dev/null)" != "" ]; then # PROGRAMA=xview # else # PROGRAMA=open # fi # echo ${SAIDAGOCR} # ${PROGRAMA} captcha.jpg 1>/dev/null 2>/dev/null & echo -n . # Envia o que o gocr leu e verifica se acertou curl -A "$UA" -d myid=${ID} -d cap=${SAIDAGOCR} -d enviar.x=127 -d enviar.y=15 "http://www.torpedogratis.net/send.php?id=${ID}&op=${OPERADORA}" -o 04.html 2>/dev/null # Quando acerta, aparece algo assim: # <div style=" padding-left: 90px; padding-top: 15px;"> # <img src="http://www.torpedogratis.net/componentes/imagens/8-0.gif"> # </div> # <script> window.parent.location="enviada.php?myid=140691&op=oi"; </script> # Quando erra, aparece algo assim: # <div style=" padding-left: 90px; padding-top: 15px;"> # <img src="http://www.torpedogratis.net/componentes/imagens/8-0.gif"> # </div> # <META HTTP-EQUIV="refresh" content="0;URL=envia.php?id=141010&op=oi"> # # <div style=" padding-left: 90px; padding-top: 15px;"> # <img src="http://www.torpedogratis.net/componentes/imagens/8-0.gif"> # </div> if [ "$( grep "<script>" 04.html )" != "" ]; then ACERTOU=1 fi done echo echo "Mensagem enviada com sucesso! Foram necessárias $VEZES tentativas para acertar" cd ${DIRATUAL} exit 0 ##################################################################################### ======= Aula 31 (14 Set 2009) Melhorando o script para enviar mensagens SMS utilizando o torpedogratis.net ======= - Cuidamos do TODO número 3: Lidar com mensagens maiores do que o tamanho máximo permitido (116) Fizemos alguns testes e vimos que na verdade o limite é de 93 caracteres. Resolvemos isso cortando a mensagem em vários pedaços e enviando cada um separado pelo próprio script. Segue o trecho que foi adicionado no script logo depois do export VEZES=0: ##################################################################################### TAMANHOMAX=90 # Apesar do site dizer que o máximo são 116. Vimos que não # funciona # Verifica o tamanho da mensagem. Se for maior do que 116 caracteres, quebra a # mensagem em pedaços e vai enviando 1 a 1. TAMANHO=`echo -n ${MENSAGEM} | wc -c` if [ ${TAMANHO} -gt ${TAMANHOMAX} ]; then let QUANTIDADE=TAMANHO/TAMANHOMAX+1 let TAMANHONOVO=(QUANTIDADE-1)*TAMANHOMAX if [ ${TAMANHONOVO} -eq ${TAMANHO} ]; then let QUANTIDADE=QUANTIDADE-1 fi echo "A mensagem foi quebrada em $QUANTIDADE... " # Faz um loop para dividir a mensagem em ${QUANTIDADE} pedaços for ((I=0;I<QUANTIDADE;I++)); do INFERIOR=`echo "$I*${TAMANHOMAX}+1"| bc` SUPERIOR=`echo "($I+1)*${TAMANHOMAX}" | bc` MENSAGEMCORTE=`echo $MENSAGEM | cut -c ${INFERIOR}-${SUPERIOR}` let J=I+1 echo "Enviando o pedaço $J..." $0 $1 $2 $3 $MENSAGEMCORTE $5 sleep 30 done exit 0 fi # Vai para o /tmp para não bagunçar seu diretório atual DIRATUAL=`pwd` cd /tmp ##################################################################################### ======= Aula 32 (7 Out 2009) Fazendo um script para ordenar diversas fotos pela data e hora ======= - Muitas vezes voltamos de viagens e copiamos um monte de fotos dos amigos. Um problema é organizar esse monte de fotos depois no sistema de arquivos já que cada fabricante das câmeras tem uma string específica para os nomes das fotos e cada câmera tem um contador interno para ir definindo os nomes dos arquivos. O ideal seria que todas as fotos fossem ordenadas pela hora que foram tiradas. Dessa forma fica fácil de organizar depois. Então, dado um diretório com vários subdiretórios cheios de fotos com nomes diversos, o ideal seria ter um script que organizasse todas as fotos em um único diretório com nomes sequenciais seguindo uma ordenação pela hora que foram tiradas. O diretório depois do script ser executado teria uma sequência de fotos como esta: buzios_0001.jpg buzios_0002.jpg ... A hora em que as fotos foram tiradas são armazenadas automaticamente pelas câmeras num cabeçalho chamado EXIF dentro das fotos .jpg. Existem vários programas que conseguem ler essas informações. O script que será exibido a seguir resolver o problema de ordenação de um monte de foto. Antes de apresentá-lo, seguem algumas informações interessantes que fomos descobrindo durante a escrita dele: . o programa identify do imagemagick com a opção -verbose lê o cabeçalho EXIF mas é muuuuuuuuuuito lento quando comparado com outros programas como o exif e o jhead. Optamos por usar o jhead pois nos testes que fizemos ele foi o mais rápido de todos; . existe printf no shell!! E a sintaxe segue a sintaxe do printf do C. Isso é muito útil. Por exemplo, para imprimir um número inteiro com uma quantidadde fixa de dígitos definida pela variável $DIGITOS, pode-se usar: printf "%0${DIGITOS}d" $NUM` Se DIGITOS for 4 e NUM for 10, a saída desse printf será 0010; . o sort faz ordenação por meses mas os nomes precisam estar em inglês e podem estar abreviados. Finalmente, segue o script. Ele funciona tanto com o GNU/Linux quanto com o MacOS, desde que seja instalado o programa jhead. No script tem o link de onde baixar o programa. Usuários de Debian e Ubuntu podem instalar com `apt-get install jhead`. ##################################################################################### #!/bin/bash # WHAT? # Um script que ordena um monte de fotos em um diretório pela data/hora que # foram tiradas. Isso é útil quando duas pessoas fazem uma viagem e voltam # cada uma com um conjunto de fotos diferente. Sem esse script dá o maior trabalho # organizar. # HOW? # O segredo do script é o find para achar todos os arquivos e depois o sort -n # que deve ser executado sobre as informações de data/hora que podem ser pegos # por diversos programas no shell. Utilizamos o programa jhead mas o exif e o # identify também pode ser utilizados (este último é muito, muito, muito # lento!!!) A informação de data/hora fica armazenada no cabeçalho EXIF da # foto. Toda câmera digital escreve dados neste cabeçalho. Entre os vários # dados, está a hora/data. # BUGS? # Resolva e avise pra gente! :P # TODO # Colocar uma opção para que os nomes dos arquivos criados tenha a data também # (Tem que lembrar de tirar os ":" do dia/hora para evitar problemas com partições fat. # O comando que vai ser executado para ler o cabeçalho EXIF. Nós fizemos testes # com o jhead. Podem ser utilizados também o identify do imagemacick e o exif. # Para rodar no MacOS, o desenvolvedor do jhead já disponibiliza um binário # pronto. O site para baixar é http://www.sentex.net/~mwandel/jhead/ COMANDO="jhead" if [ $# -ne 2 ]; then echo "Uso: $0 <diretorio> <prefixo>" echo "Vai ordenar as fotos pela data/hora renomeando todas com o <prefixo>_xxxxxx.jpg" exit 1 fi # Os nomes de todos os arquivos de imagens ficarão no arquivo texto TEMP junto # com as suas datas. Obs.: o tmp.XXXXXXXXXX é obrigatório no MacOS mas opcional # no GNU/Linux. Para não dar erro, estamos usando em todas as chamadas do # mktemp. TEMP=`mktemp tmp.XXXXXXXXXX` echo "Renomeando as fotos... " # O ONDE_TO é para permitir uma "animação" com um traço enquanto o código roda export ONDE_TO=0 # Laço principal em busca das fotos. É aqui que o cabeçalho EXIF é lido em # busca da data/hora export IFS=' ' for I in `find $1 -iname "*.jpg" -or -iname "*.jpeg" -or -iname "*.png" -or -iname "*.gif" -or -iname "*.bmp"`; do DATA=`$COMANDO "$I" | grep -i "Date/Time" | cut -f 2- -d ':'` echo "$DATA|$I" >> $TEMP case $ONDE_TO in 0) echo -e -n "\b-"; ONDE_TO=1;; 1) echo -e -n "\b\\"; ONDE_TO=2;; 2) echo -e -n "\b|" ; ONDE_TO=3;; 3) echo -e -n "\b/" ; ONDE_TO=0;; esac done # Ordenando os arquivos. Como a data/hora fica no formato # ano:mes:dia hora:minuto:segundo, basta usar o sort com a opção -n que ele já # ordena certo TEMP2=`mktemp tmp.XXXXXXXXXX` sort -n $TEMP > ${TEMP2} # Para colocar uma quantidade de zeros a esquerda que mantenha todos os nomes # dos arquivos com a mesma quantidade de caracteres é preciso saber quantos # arquivos existem. # Os seds são de fato necessários só no MacOS porque ele põe uns espaços antes # do número de linhas/caracteres mas não vai dar erro no linux por causa disso. DIGITOS=`wc -l ${TEMP2} | sed -E 's/^( )+//g' | cut -f 1 -d ' ' | wc -c | sed -E 's/^( )+//g' ` let DIGITOS=DIGITOS-1 # Para evitar problemas caso o script seja executado em um diretório onde ele # foi executado anteriormente, enviamos todas as fotos para um diretório # temporário e depois trazemos elas de volta. TEMPDIR=`mktemp -d tmp.XXXXXXXXXX` NUM=1 for I in `cat ${TEMP2} | cut -f 2- -d '|'`;do EXTENSAO=`echo $I | rev | cut -f 1 -d . | rev` # O bash tem um printf igual ao do C e isso é muito útil para, por exemplo, # imprimir números com uma quantidade certa de zeros à esquerda NOMENOVO=`printf "${2}_%0${DIGITOS}d.${EXTENSAO}" $NUM` mv $I $TEMPDIR/${NOMENOVO} let NUM=NUM+1 case $ONDE_TO in 0) echo -e -n "\b-"; ONDE_TO=1;; 1) echo -e -n "\b\\"; ONDE_TO=2;; 2) echo -e -n "\b|" ; ONDE_TO=3;; 3) echo -e -n "\b/" ; ONDE_TO=0;; esac done # Trazendo as fotos de volta mv $TEMPDIR/* $1/ echo exit 0 ##################################################################################### ======= Aula 33 (15 Out 2009) Usando o gawk para calcular média e algumas dicas do vim ======= - Uma forma trivial de calcular a média de vários números, um por linha, é contar a quantidade de linhas com o wc e depois juntas as linhas com o "+" usando o paste e passando o resultado para o bc. Só que isso fica muito lento para arquivos grandes já que o bc não é tão rápido para fazer contas e pelo fato de que o arquivo tem que ser lido por inteiro duas vezes. Uma vez pelo wc e outra vez pelo paste. Com o awk dá para calcular a média passando apenas 1 vez pelo arquivo: gawk '{SOMA+=$1} END {printf ("%f\n",SOMA/NR)}' <arquivo> Neste exemplo, a média está sendo calculada para o número que está no primeiro campo do arquivo. Se quiser pegar a média de números que estão em outra posição basta mudar o $1 da SOMA. O MacOS não vem com o gawk mas este pode ser facilmente compilado pegando o fonte em http://ftp.gnu.org/gnu/gawk/gawk-3.1.7.tar.bz2 O MacOS vem com um awk mas não é o da GNU. Apesar do exemplo acima ter funcionado no awk padrão do Mac, o ideal é fazer tudo sempre usando o gawk pois ele, pelo menos segundo a manpage, tem várias melhorias em relação ao awk tradicional. - Quando fazemos buscas no vim usando a '/', a última busca sempre fica em destaque (highlight) e isso é chato quando buscamos uma ocorrência que aparece várias vezes no texto. Para tirar o highlight basta fazer: ESC :nohls Se quiser voltar o highlight: ESC :set hls - Para avançar x linhas dentro do vim basta, no modo visual, teclar o valor de x e pressionar ENTER - Para abrir um arquivo pelo vim já na linha x basta rodar o comando assim: vim +x <arquivo> ======= Aula 34 (21 Out 2009) Algumas dicas do vim ======= - É possível apagar tudo do cursor até o final da linha no vim com as teclas d$ (tem que ter pressionado o ESC antes). Para apagar do início da linha até a posição do cursor usa-se d^ (tem que ter pressionado ESC antes também e não pode esquecer de dar um espaço no final). Isso é útil caso as extensões do vim estejam habilitadas no shell (para habilitar isso basta rodar `set -o vi`). - Uma forma rápida de se movimentar dentro de um código usando o vim é com a tecla %. Se vc rodar o % dentro de uma função o cursor move-se até o final da função, ou do bloco do if por exemplo. Isso é útil para códigos grandes, cujos blocos não cabem em uma única tela. - Uma opção de programa para fazer merge entre dois arquivos é o kdiff3. Ele abre dois arquivos e identifica o que um tem de diferente em relação ao outro permitindo que o usuário mescle as diferenças e faça um arquivo com as diferenças de um e do outro. Isso é útil principalmente quando os arquivos são códigos que foram modificados por duas pessoas diferentes e quando cada uma delas editou partes diferentes. Por exemplo, cada uma pode ter implementado funções diferentes. ======= Aula 35 (28 Out 2009) LaTeX, aliases, dotfiles e nomes estranhos para arquivos ======= - Um programa WYSIWYG útil para editar textos em LaTeX é o kile. Ele já possui comandos para formatar o texto de forma automática (por exemplo, itálico, negrito, etc...). No caso do debian, para que ele funcione direito, é preciso instalar também o programa okular para conseguir visualizar o .pdf gerado pelo kile. - Na maioria dos .bashrc já existem alguns aliases definidos para evitar desastres. Esses aliases forçam a execução dos programas de manipulação de arquivos com a opção interativa (-i). Dessa forma, sempre que um arquivo for apagado o usuário será questionado se ele tem certeza daquela ação. O '-i' também é utilizado no `cp` e no `mv`. Nesse caso o usuário é questionado caso operação sobrescreva algum arquivo já existente. Para ver os aliases definidos basta rodar o comando: alias - A grande maioria dos programas em sistemas Unix-like mantém suas configurações em arquivos ou diretórios ponto (dotfiles) armazenados no home do usuário. Geralmente os nomes dos arquivos são .<nome_do_programa>. Algumas vezes também tem um "rc" no fim do nome. Por exemplo, existe o .bashrc citado anteriormente, existem o .vimrc, o .mozilla, o .Skype, dentre outros. É importante ter cuidado na hora de manipular os dotfiles. O problema se dá pelo fato de que utilizar .* no shell pode ser perigoso. Isso acontece porque o shell expande o .* para .. e acaba aplicando as ações (cp, mv, rm, chmod, etc...) de forma recursiva para os diretórios pai do diretório atual. Portanto, um `rm -rf .*` terá consequências desastrosas se o usuário for o root. O comando vai começar a descer na árvore de diretórios apagando tudo que encontrar pela frente. Saber que os dotfiles existem pode ajudar a resolver problemas de alguns aplicativos. Por exemplo, certas versões do firefox mantém um arquivo lock dentro do ~/.mozilla/firefox/<diretorio> (o nome do diretório é gerado de forma aleatória na primeira execução do firefox) quando ele está aberto. A existência desse arquivo impede que outra instância do firefox seja aberta. Portanto se o programa morrer, por exemplo por causa de um desligamento abrupto do computador, o arquivo lock continuará lá e o programa não vai abrir mais. Para resolver isso basta ir no diretório e apagar o arquivo de lock. - Os dotfiles são considerados arquivos ocultos em sistemas Unix-like. Um 'ls' normal não lista esses arquivos. Para que eles sejam listados é necessário rodar um 'ls' com a opção -a para que todos (all) os arquivos sejam listados. Existem outras formas de tornar os arquivos "ocultos". Para isso pode-se utilizar o espaço ou o - na hora de criar os nomes dos arquivos. Por exemplo, o comando: touch " " vai criar um arquivo cujo nome é composto por 5 espaços. Ao dar um ls num diretório com um arquivo assim dificilmente o arquivo será notado :) Já com relação aos arquivos começando com "-" o problema se dá pelo fato de que muitos programas no shell usam o '-' para passar opções. Então se existe um arquivo com nome "-f", o rm vai dar erro ao tentar compilar ele pois vai pensar que o "-f" é a opção sendo passada: rm -f para resolver o problema dos arquivos com espaço ou com - deve-se utilizar a \ antes desses caracteres especiais. ======= Aula 36 (4 Nov 2009) Install Fest e mudança do endereço MAC ======= - O ubuntu é massa! Parece MacOS :) Após umas poucas perguntas ele já está instalado e funcionando! :) - É possível mudar o endereço MAC de um computador com linux rodando o comando: sudo /sbin/ifconfig eth0 hw ether aa:bb:cc:00:11:22 No caso do MacOS o comando é: sudo ifconfig en0 lladdr aa:bb:cc:00:11:22 Onde aa:bb:cc:00:11:22 é o novo endereço MAC. Só tem que lembrar de mudar o eth0 ou o en0 para a interface de rede correta. ======= Aula 37 (25 Nov 2009) drivers, módulos, hardware e wifi ======= - Como fazer para descobrir informações sobre o hardware de um computador? A forma mais fácil é rodando o comando lspci. Este comando lista todo o hardware do computador que utiliza o barramento PCI (a maioria do hardware atual usa esse barramento). Isso é útil para descobrir por exemplo qual placa de rede sem fio um computador tem. Assim fica fácil depois ir no google e buscar informações sobre como instalar aquela placa no linux. - Os drivers no linux são disponibilizados como módulos. Para descobrir quais módulos estão instalados em uma máquina com linux basta rodar o comando `lsmod`. Sempre que um módulo para um dado hardware é carregado (isso pode ser feito com o comando `modprobe nome_do_modulo`) mensagens são impressas no log do kernel que pode ser lido através da execução do comando `dmesg`. Muitos problemas com os drivers podem ser solucionados somente lendo a saída do dmesg. - Alguns hardwares são gerenciados por comandos específicos. Por exemplo, placas de rede wifi podem ser gerenciadas pelo comando `iwconfig`. Este comando vai listar todas as placas de rede sem fio no computador e fornecer informações a cerca da rede a qual ela está conectada. A execução do iwconfig pode confirmar se o driver da placa de rede está ou não funcionando. ======= Aula 38 (2 Dez 2009) SMS, proxy, FedEx e Debian ======= - O script EnviaSMS do dia 9 de setembro parou de funcionar. Para ele voltar a funcionar as seguintes mudanças tiveram que ser feitas: . Adicionados os parâmetros "-d dddr=${SEUDDD} -d numeror=${SEUNUMERO}" na primeira chamada do curl. SEUDDD e SEUNUMERO tem que ser definidos no início do código. São os dados do remetente que agora são obrigatórios. . O acesso agora tem que ser feito por meio de proxy. Para isso basta substituir todas as chamadas do curl por: "curl --socks5 ${PROXY}" e definir PROXY no início do código no formato: ip_do_proxy:porta_do_proxy. Alguns sites que possuem proxys abertos: http://www.xroxy.com/proxylist.htm e http://aliveproxy.com/ - O site do FedEx mantém um rastreio de itens da mesma forma que o site dos correios do Brasil. Segue abaixo um script útil que vai enviando e-mail sempre que o status de um conjunto de itens muda no site do FedEx. O script envia umas mensagens divertidas :) Para ele funcionar corretamente é necessário que seja executado em algum local que tenha servidor SMTP. Ele pode ser colocado no crontab. ##################################################################################### #!/bin/bash # WHAT? # Script para ver onde estão itens do FedEx. Ele verifica o .html de n links, # onde n é a quantidade de pacotes do FedEx (isso é útil para compras de itens # múltiplos na Apple Store. Geralmente eles são enviados por pacotes # separados). Caso haja alguma diferença com relação ao .html anterior ele # envia um email avisando onde cada pacote está. # O link dos itens é algo parecido com isto: # http://www.fedex.com/Tracking?action=track language=english cntry_code=us initial=x tracknumbers=753606241234 # Quantidade de links (isso pode ser melhorado para ficar relacionado com o # tamanho do vetor) QUANT=2 # MUDE AQUI EMAIL="eu@host.com" # MUDE AQUI! link[0]="..." link[1]="..." # link[2]="..." # link[3]="..." # MUDE AQUI id[0]="mouse" id[1]="iPod" # id[2]="macbook" # id[3]="teclado" for ((i=0;i<QUANT;i++)); do if [ ! -e /tmp/apple.$i ]; then touch /tmp/apple.$i fi done for ((i=0;i<QUANT;i++)); do links -dump ${link[$i]} | sed -n '/Initiated/,/Shipment Dates/p' | sed -n '/Delivered/,/Shipment Dates/p' | sed '1,1d' | sed '$d' > /tmp/apple.$i.novo if [ "$( diff /tmp/apple.$i.novo /tmp/apple.$i )" != "" ]; then mv /tmp/apple.$i.novo /tmp/apple.$i if [ "$( head -n 1 /tmp/apple.$i | tr -d ' ' )" == "Delivered" ]; then echo -e "Olá!\n\nEu sou o pacote da Apple com o ${id[$i]} e estou enviando esta mensagem\npara avisar que eu cheguei!!!!!! ÊBÁ ÊBÁ ÊBÁ!!!! :)\n\nPara você acreditar em mim, olhe o status que está no site do FedEx:\n\n========================================\n" > /tmp/apple.email cat /tmp/apple.$i >> /tmp/apple.email echo -e "========================================\n\nMaiores detalhes em:\n${link[$i]}\n\nObrigado por me comprar! :) :)\n" >> /tmp/apple.email else echo -e "Olá!\n\nEu sou o pacote da Apple com o ${id[$i]} e estou enviando esta mensagem\npara avisar que mudei meu status no site do FedEx :)\n\nAgora eu estou assim:\n\n========================================\n" > /tmp/apple.email cat /tmp/apple.$i >> /tmp/apple.email echo -e "========================================\n\nMaiores detalhes em:\n${link[$i]}\n\nLogo logo nos veremos! :)\n" >> /tmp/apple.email fi # Verifica a hora para desejar bom dia/boa tarde ou boa noite HORA=`date +%k` if [ $HORA -ge 5 ] [ $HORA -le 12 ] ; then echo -e "Bom dia!\n" >> /tmp/apple.email elif [ $HORA -ge 13 ] [ $HORA -le 18 ]; then echo -e "Boa tarde!\n" >> /tmp/apple.email else echo -e "Boa noite!\n" >> /tmp/apple.email fi mutt ${EMAIL} -s "[Mudança na entrega da Apple] Status do envio -- ${id[$i]}" -F ~/.muttrc < /tmp/apple.email fi done exit 0 ##################################################################################### - Por que o Debian se chama Debian? Porque o Ian Murdock, fundador do Debian, quis prestar uma homenagem à Debra, sua esposa na época. Deb + Ian = Debian :) ======= Aula 39 (14 Abr 2010) Revisão, let, eval, $_ e readonly ======= - A página http://tldp.org/LDP/abs/html/internal.html fornece um excelente tutorial sobre comandos do bash - O let suporta diversas operações aritméticas que o C suporta. Por exemplo, `let 'a>>=2'` desloca o valor de 'a' dois bits para a direita. Outras operações possíveis são <<=, ++, --, +=, -=. Uma operação muito útil do let é substituir o uso do if com o operador '?'. O trecho de código abaixo dá um exemplo: ##################################################################################### let "a = (b==256?1:0)" if [ $a -eq 1 ]; then echo "usando o let descobri que b é 256" fi ##################################################################################### - É possível "montar" um comando em uma variável e depois executá-lo com o eval. O trecho de código abaixo executa o $COMANDO que é um `ls -1`: ##################################################################################### export COMANDO="ls -1" eval $COMANDO ##################################################################################### - Para poupar o trabalho de digitar demais no shell pode-se utilizar o $_ . Essa variável especial é substituída pelo último argumento do comando anterior que foi executado. Um atalho para o $_ é pressionar 'ESC' e depois o '_'. Um exemplo de utilidade é quando se move um arquivo para um diretório e depois se deseja ir para aquele diretório: ##################################################################################### mv arquivo /usr/local/diretorio/gigante/nome/muito/grande cd $_ ##################################################################################### Ao invés de digitar $_ pode-se teclar o 'ESC' seguido de '_' - É possível utilizar constantes em bash. Para isso basta declarar a variável como readonly. O bash não deixa que o conteúdo da variável seja modificado se ela for readonly. Se a variável for declarada assim: ##################################################################################### readonly export a=3 ##################################################################################### E alguém tentar modificar o conteúdo, vai receber a mensagem: -bash: a: readonly variable ======= Aula 40 (28 Abr 2010) sites, mp3 e mais um monte de bash script ======= - O site http://vimcasts.org/ fornece vários vídeos que exemplificam o poder do "vim". Umas dicas muito legais disponíveis no link são relativas à movimentação do cursor no "vim". É possível editar uma dada linha, entrar no modo visual, navegar no arquivo e voltar exatamente para o ponto onde a linha foi editada antes de entrar no modo visual. Para isso basta digitar g; . Também é possível verificar todas as mudanças feitas no arquivo com o comando :changes e modificar o cursor na área visível da tela com as teclas H, M e L. H vai para o topo da janela, M para o meio e L para a parte inferior. - Para quem quiser experimentar um OpenBSD de graça, é possível solicitar um shell em http://devio.us/services . O seu pedido vai ser analisado e depois eles enviam um email falando se foi aceito. A máquina tem um monte de coisa e dá pra brincar bastante. Tem até PHP e SGBD. - Uma opção interessante para rodar programas do GNU/Linux no MacOS é usar o "virtualbox". Ele virtualiza um hardware x86 e em cima dele é possível instalar, por exemplo, um Debian. Depois de instalado é possível dar SSH para esta máquina virtual e rodar até mesmo programas que usem o X ou que acessem a placa de som. Funciona muito bem mas se o objetivo é ter uma máquina virtual com um acesso mais direto ao hardware, o "parallels" seria a melhor solução. O problema é que este último é pago. - É possível adicionar muitas informações em um mp3. Por exemplo, capas e letras. O "eyeD3" pode ser usado para adicionar essas informações. Por exemplo: ##################################################################################### eyeD3 --lyrics=por::"$(cat letra.txt)" arquivo.mp3 ##################################################################################### Adiciona a letra contida no arquivo "letra.txt" no mp3. ##################################################################################### eyeD3 --add-image=capa.jpg:FRONT_COVER arquivo.mp3 ##################################################################################### Adiciona a capa do arquivo capa.jpg no mp3. - É possível esperar um processo terminar antes de seguir num script usando o comando "wait". Por exemplo: ##################################################################################### processo1 & processo2 & processo3 & processo4 & wait echo "acabou" ##################################################################################### Só vai imprimir o "acabou" depois que todos os processos terminarem. Isso pode ser útil quando se quer executar uma quantidade de processos que não ultrapasse a quantidade de núcleos de uma máquina. - Diretórios que tenham muitos acessos em um script podem ser mantidos em uma pilha usando os comandos "pushd" e "popd". Por exemplo, se vc está no diretório /a, quer ir para o diretório /b, realizar alguma operação lá e depois voltar para o /a, é possível fazer: ##################################################################################### echo "estou no /a" pushd /b # Esse comando automaticamente roda o "cd /b" echo "fui pro /b e vou fazer algum coisa" popd # Esse comando automaticamente roda o "cd /a" echo "agora voltei para o /a" ##################################################################################### Claro que com só dois diretórios isso não faz muito sentido pois nesse caso o "cd -" faria a mesma coisa. O "pushd" e o "popd" são de fato úteis quando há muitos diretórios. - O bash tem um monte de opção. É possível vê-las, ligá-las ou desligá-las usando o comando "shopt". - Programas chamados no bash são procurados pelo PATH mas após terem sido executados uma vez, eles não são mais procurados lá no PATH. Eles são mantidos em uma tabela hash interna do bash. Por exemplo, se vc executa o "mv" pela primeira vez numa sessão, o bash armazena o "/bin/mv" na tabela hash dele e nas próximas chamadas, ele vai saber onde o "mv" está apenas consultando essa tabela. Isso pode ser um problema caso vc crie um "mv" seu, que esteja em um diretório precedente ao /bin no seu PATH, e tente executá-lo. O bash vai continuar chamando o "/bin/mv" porque está na hash. Para remover o conteúdo da hash é necessário rodar: ##################################################################################### hash -d mv ##################################################################################### Dessa forma, o bash vai procurar o "mv" no PATH de novo. O comando "hash" pode ser útil para mudar, por exemplo, o "ns" executado em uma série de experimentos. É possível mudar a localização apenas modificando o conteúdo da tabela "hash". - O programa "units" realiza a conversão entre diversas unidades. É possível por exemplo, converter de galões para litros rodando o comando e passando os valores no prompt (é possível utilizar TAB para que o programa exiba todas as unidades suportadas) Abaixo tem um exemplo de execução do programa ##################################################################################### linuxssa@wolfman $ units 613 units, 44 prefixes You have: gallon You want: litre * 3.7854118 / 0.26417205 You have: ^C linuxssa@wolfman $ ##################################################################################### Na verdade o programa mostra a relação entre as duas unidades - Uma forma segura de apagar arquivos é realizada usando o "shred". Ao invés de rodar "rm arquivo", é possível fazer "shred -u arquivo". Antes de apagar o arquivo, o "shred" escreve um monte de lixo para garantir que aquele arquivo não vai ser lido por nenhuma ferramenta de análise forense. Também é possível escrever um monte de zeros com a opção "-z" e é possível escrever diversas vezes em cima do arquivo com a opção "-n". Isso é altamente recomendado para executar no /dev/sda antes de vender um disco rígido pessoal para outra pessoa. - Um número inteiro pode ser fatorado utilizando o "factor". Isso é útil quando se quer dividir um número em conjuntos de tamanhos iguais. - A ordem topológica de um conjunto de strings pode ser gerada utilizando o comando "tsort". Ele recebe como entrada uma quantidade par de strings sendo que cada par define a prioridade entre duas strings. A partir desses pares, o programa monta a ordem topológica. Isso é útil para o caso de ter um script que realiza diversas tarefas com dependências entre si. Scripts assim são frequentemente executados em grades computacionais. ======= Aula 41 (26 Mai 2010) segurança e mais um monte de bash script ======= - É possível obter informações de um computador remoto usando o programa nmap. O nmap lista todas as portas abertas do computador. Ele pode ser executado sem ser root assim: nmap nome_da_maquina A saída do nmap lista as portas abertas e os possíveis serviços rodando ali (por exemplo, se a porta 22 estiver aberta ele vai dizer que tem um SSH rodando ali) É possível fazer o nmap descobrir até o sistema operacional que está rodando na máquina. Para isso ele tenta descobrir algumas particularidades do SO através do comportamento dele quando ele recebe alguma conexão TCP (muitas ações variam de SO para SO). Para descobrir o SO, basta executar o nmap com a opção -O. Nesse caso, é necessário rodá-lo como root. - O chkrootkit é um programa útil para descobrir se seu sistema está comprometido. Ele verifica se ferramentas básicas do sistema, como o 'ls', o 'ps', o 'gcc' foram modificadas e também busca por arquivos que possam estar ocultos no sistema. O chkrootkit pode ser baixado de www.chkrootkit.org. - Uma forma de criptografar e descriptografar arquivos no shell é usando o gpg. O primeiro passo é criar um par de chaves pública/privada assim: gpg --gen-key o processo é interativo. Depois de geradas as chaves, um arquivo pode ser criptografado assim: gpg -e arquivo será criado o arquivo.gpg. O arquivo original pode ser apagado. Para descriptografar, faz-se: gpg -d arquivo.gpg É importante observar que será pedida a chave secreta ao descriptograr. A chave secreta foi definida na execução do gpg com o argumento --gen-key. - O comando 'caller' ajuda na depuração de scripts. Se ele for chamado de dentro de uma função, e se essa função for usada num script, será impresso na tela a linha do script em que a função foi chamada e qual função chamou aquela função. Por exemplo, o trecho de script: ######################################################## #!/bin/bash function2 () { echo faz alguma coisa caller 0 } function2 exit 0 ######################################################## Quando executado, imprime: ######################################################## faz alguma coisa 8 main ./caller.sh ######################################################## O que indica que a função foi chamada a partir do main na linha 8. - É possível remover e colocar tabs em um arquivo usando os comandos expand e o unexpand. O primeiro, substitui as tabulações por 8 espaços consecutivos. O segundo substitui 8 espaços consecutivos por tabulações. - Às vezes substituir as tabulações por espações não ajuda a organizar um código. Pode ser necessário fazer mais modificações. Para isso existe o programa 'indent'. Ele pode, por exemplo, colocar todos os comentários de um código com /* e /* ao invés de //. Pode também colcoar todos os códigos em um estilo específico. Por exemplo, estilo GNU ou estilo kernel. Ele só funciona com códigos em C (possivelmente C++ também) - É possível juntar arquivos que tenham informações referentes a um mesmo registro usando o join. Por exemplo, se um arquivo tem o conteúdo: Daniel 133 Batista 444 e outro tem: Daniel Salvador Batista Campinas o join, executado com os dois arquivos como argumento, devolveria na saída padrão: Daniel 133 Salvador Batista 444 Campinas É importante observar que os arquivos precisam estar com a mesma ordenação. Para isso pode-se utilizar o sort antes. ======= Aula 42 (9 Jun 2010) bash script e nessus ======= - Um alias muito útil para ter no bash é o que define o grep como 'grep --color -n'. Com essas opções, o grep vai colorir o padrão encontrado e vai anexar o número da linha do padrão. Isso ajuda quando se está programando um código grande em que o padrão aparece em diversas linhas. - Uma forma de obter o código ascii de um dado caracter é utilizando o binário 'od'. Ele imprime o conteúdo em octal de um dado arquivo. Para imprimir os códigos ASCII ele pode ser executado com os argumentos '-An -t dC'. Resultado semelhante pode ser obtido com a execução do binário 'hexdump' que imprime o conteúdo do arquivo em hexadecimal. - O 'cut' aceita listas de "pedaços" que devem ser cortados sem que essas listas correspondam a uma faixa. Por exemplo: `cut -f 1,4,10 arquivo.txt` vai pegar os campos 1, 4 e 10 o arquivo.txt. - Por padrão, o delimitador mantido pelo cut na saída é o mesmo lido da entrada. Para modificá-lo basta definir com o argumento '--output-delimiter'. - É possível definir o tipo de linha no gnuplot com a opção 'lt' que deve ser adicionada em cada linha com 'plot'. Por exemplo, 'lt 1' define uma linha cheia. O legal é que é possível definir vários plots com o mesmo 'lt' mas isso não é problema pois o gnuplot define tipos diferentes de pontos (quadrado, triângulo, círculo, etc...) que garantem que os gráficos sejam diferenciados. - Um programa muito útil para fazer auditoria de segurança em uma organização (ou até mesmo em casa) é o nessus disponível em www.nessus.org. O programa instala um daemon que escuta via HTTPS. Quando alguém conecta na porta definida, é aberto uma página web em flash que permite a criação de "scans" que são realizados com o objetivo de buscar vulnerabilidades nas máquinas selecionadas. O resultado da execução do nessus é um relatório, que pode ser exportado para HTML, contendo as falhas encontradas. Para algumas falhas são apresentadas também sugestões de solução. - É possível formatar um arquivo em termos de quantidade de colunas usando os programas fold e fmt. O primeiro simplesmente quebra o arquivo em uma determinada quantidade de colunas (ou bytes). O segundo possui mais opções e pode, por exemplo, quebrar a linha em conjunto com um preenchimento das linhas para evitar que sejam geradas linhas pequenas (equivalente ao comando 'gq' do vim). - Um programa semelhante ao a2ps para gerar .ps a partir de texto é o enscript. Diferente do a2ps, ele é útil para qualquer tipo de arquivo texto. O arquivo não precisa ser código-fonte. ======= Aula 43 (23 Jun 2010) downloads com conexões paralelas usando o curl ======= - Muitos servidores HTTP limitam a taxa de transferência de conexões individuais. Isso é chato quando se tem uma conexão de alta velocidade, já que o servidor vai acabar sendo o gargalo. Uma forma de contornar isso é fazendo conexões paralelas ao servidor de modo a baixar diversos pedaços do arquivo em cada conexão. Depois basta juntar tudo e pronto. Têm-se o arquivo inteiro. O script abaixo usa o curl para fazer isso. ##################################################################################### #!/bin/bash # WHAT? # Um script que faz vários downloads em paralelo de vários pedaços de um # arquivo e depois junta como o arquivo original. Isso é útil para # utilizar em sites que limitam a taxa de envio de dados por conexão. # Para se ter uma idéia, em um teste feito com o download da iso do # debian, sem este script, o tempo de download fica 8 vezes maior. # HOW? # O script usa o curl passando como argumento a faixa de bytes do # arquivo que deve ser copiada. Portanto, basta fazer uma divisão e ir # pegando pedaços do arquivo. No final um simples cat junta tudo como # um arquivo só. # BUGS? # Resolva e avise pra gente! :P # TODO # Tratar o problema causado pelos servidores que limitam # conexões simultâneas de forma automática (ao perceber que o servidor # limita as conexões, o script tem que ir fazendo as conexões # paralelas menor ou igual a esse limite) if [ $# -ne 3 ]; then echo "Uso: $0 <quantidade_de_conexoes> <nome_do_arquivo> <url>" exit 1 fi QUANTIDADE=$1 NOME=$2 URL=$3 # Arquivo temporário onde será armazenado o tamanho do arquivo SAIDA=`mktemp tmp.XXXXXXXXXX` # Intervalo de tempo (segundos) entre execuções sucessivas do curl INTERVALO=1 # Inicialmente lemos o tamanho do arquivo em bytes. O curl -I é # utilizado para pegar apenas as informações do cabeçalho HTTP (o # tamanho é uma dessas informações) curl -I $URL -o ${SAIDA} 2>/dev/null 1>/dev/null TAMANHO=`grep Content-Length ${SAIDA} | cut -f 2 -d ' ' | grep -oE [0-9]+` rm -f ${SAIDA} if [ "$TAMANHO" == "" ]; then echo "O tamanho do arquivo não foi encontrado :(. Vou sair." exit 2 fi # Quantos bytes terá cada pedaço baixado por uma conexão individual do # curl PEDACO=`echo "$TAMANHO/$QUANTIDADE" | bc` # Laço para baixar todos os pedaços. Como a divisão pode não dar # exata, fazemos o loop até o penúltimo pedaço. O último pedaço vai # ser baixado até o fim do arquivo. # Obs.: o curl considera que o primeiro byte do arquivo é o byte 0 INICIO=0 FIM=$PEDACO let TAMANHO1=TAMANHO-PEDACO # Os pedaços terão um sufixo "-xxx" onde xxx é um número sequencial. # Isso é necessário para juntar tudo na ordem certa no final. let SUFIXO=0 for I in `seq $PEDACO $PEDACO ${TAMANHO1}`;do NOMEI=`echo "${NOME}-${SUFIXO}"` curl -o ${NOMEI} -r $INICIO-$FIM $URL 1>/dev/null 2>/dev/null & # sleep necessário para o servidor não pensar que isso é um DoS sleep ${INTERVALO} let INICIO=$I+1 let FIM=$I+$PEDACO let SUFIXO++ done # Pegando o pedaço final até o fim do arquivo let TAMANHO=TAMANHO-1 NOMEI=`echo "${NOME}-${SUFIXO}"` curl -o ${NOMEI} -r $INICIO-$TAMANHO $URL 1>/dev/null 2>/dev/null & let QUANTIDADE-- for I in `seq 0 $QUANTIDADE`; do LISTA_DE_ARQUIVOS=`echo ${LISTA_DE_ARQUIVOS} ${NOME}-$I` done # Espera todos os curl terminarem wait # Junto tudo e apaga os pedaços cat ${LISTA_DE_ARQUIVOS} > ${NOME} rm -f ${LISTA_DE_ARQUIVOS} exit 0 ##################################################################################### - O watch é um programa que permite uma solução elegante para repetir um dado comando diversas vezes. Por exemplo, ao invés de fazer: while [ 1 ]; do df -h; sleep 1; done Pode ser feito: watch -n 1 "df -h" Para interromper a execução do watch basta pressionar CTRL+c ======= Aula 44 (30 Jun 2010) Textos com formas no LaTeX e SSH acelerado ======= - O pacote shapepar do LaTeX permite que um certo parágrafo seja formatado como uma figura geométrica. Por exemplo, um coração, uma estrela, ou um círculo. Basta fazer por exemplo: \heartpar{o parágrafo vai aqui blablablablabla} que o texto entre '{' e '}' é formatado pelo pacote. Mais informações e exemplos de uso podem ser encontrados em http://www.ctan.org/tex-archive/macros/latex/contrib/shapepar/shapepar.pdf É super fácil :) - Todos os clientes/servidores SSH a partir da versão 3 (a versão do programa não a versão do protocolo) suportam aceleração na conexão. Com esse suporte habilitado, a primeira conexão é realizada da forma convencional passando pelo processo de troca de chaves. As conexões seguintes compartilham a conexão inicial e dessa forma não precisam realizar todo o processo de novo, o que faz com que as conexões sejam estabelecidas de forma mais rápida (e sem senha, mesmo que o SSH não esteja configurado para ser realizado sem senha). Para adicionar no ~/.ssh/config: Host * ControlMaster auto ControlPath ~/.ssh/master-%r@%h:%p Mais informações em http://www.linux.com/archive/feed/54498 ======= Aula 45 (1 Set 2010) if, look, cmp, sdiff e stat ======= - Sempre que a saída de um comando for utilizada dentro da condição de um 'if' é importante colocar dentro de $() ao invés de ``. Se `` for utilizado e o comando não der nenhuma saída, o bash dará erro de que há a falta de um argumento para o operador de comparação. Por exemplo: if [ `echo` == "bla" ]; then Dá o erro: line 3: [: ==: esperado operador unário O correto é escrever o código assim: if [ "$( echo )" == "bla" ]; then - Há duas opções para uma expressão booleana (com E e OU) no if. Pode ser utilizado &&, || ou -a, -o. Por exemplo: if [ "$( echo 1 )" == "1" ] && [ "$( echo 1)" == "1" ]; then é equivalente a: if [ "$( echo 1 )" == "1" -a "$( echo 1)" == "1" ]; then e: if [ "$( echo 1 )" == "1" ] || [ "$( echo 1)" == "1" ]; then é equivalente a: if [ "$( echo 1 )" == "1" -o "$( echo 1)" == "1" ]; then - A maioria dos sistemas Unix-like possui um dicionário interno que na verdade é um arquivo com várias palavras de um dado idioma. Geralmente esse arquivo fica em /usr/share/dict/words. O comando "look" faz uma busca por palavras neste arquivo. O comando: look bla busca todas as palavras que possuam a substring "bla". Se a palavra começa com "bla", ela é impressa por inteiro. Caso contrário, apenas "bla" é impresso. - Uma forma mais eficiente de comparar dois arquivos binários é usar o comando 'cmp' ao invés do comando 'diff'. O 'cmp' faz comparação bit a bit e interrompe assim que encontra algum bit diferente entre os dois arquivos. - O 'sdiff' permite que dois arquivos, diferentes por apenas algumas linhas, sejam unidos em um terceiro de modo que o usuário escolha qual linha de que arquivo deve ser utilizada. Isso é útil por exemplo em códigos que tenham sido modificados por duas pessoas e apenas uma implementação deve ser escolhida. O comando: sdiff -o arq3 arq1 arq2 compara o arquivos arq1 com o arquivo arq2, se houver linhas diferentes pergunta ao usuário qual linha deve ser a escolhida, e salva o "merge" no arquivo arq3. - O comando 'stat' apresenta diversas informações "de baixo nível" sobre um arquivo. Três informações interessantes são as datas denominadas "Access", "Modify" e "Change". A primeira é modificada sempre que o arquivo é lido. A segunda é modificada sempre que o conteúdo do arquivo é modificado e a terceira é modificada sempre que qualquer informação, inclusive o conteúdo, do arquivo é modificada (por exemplo, as permissões). É importante observar que se o arquivo já estiver no cache, a data de "Access" não é modificada. ======= Aula 46 (15 Set 2010) bash e números começando com 0 ======= - Assim como em muitas linguagens de programação, o bash entende que números que começam com '0' são números na base 8. Isso pode trazer problemas sérios em scripts. Por exemplo, a operação abaixo: export J="07" let J++ na verdade está incrementando o número 7 na base 8 de 1 unidade. O resultado dessa operação vai ser 10 na base 8, o que é equivalente a 8 na base 10. Se logo depois do let for feito: echo $J o resultado vai ser 8 (apesar do número estar na base 8 e da operação ser feita nesta base, o resultado do echo sempre é na base 10). Até aí tudo bem. Porém, se o código fosse este abaixo: export J="08" let J++ echo $J o bash daria erro. A mensagem exibida seria: #################################################################################### let: 08: valor muito grande para esta base de numeração (error token is "08") #################################################################################### e o valor de J não seria modificado. Esse caso não é tão crítico, já que o bash pelo menos reclama. Se bem que em um caso onde o script seja testado só até 7, o programador terá a falsa impressão de que está tudo certo e só quando o código rodar com o valor 08 é que ele vai descobrir o problema. O pior mesmo acontece quando a operação em octal dá um resultado diferente da operação em decimal. Por exemplo, no trecho de código: export J="01701" let J++ echo $J o bash não vai reclamar e o valor de J impresso vai ser 962!!! E não 1702. Existem duas formas de fazer o bash realizar a conta de um número começando com 0 como se fosse decimal. A primeira é simplesmente tirar o 0 da frente do número. Isso pode ser feito com o sed: export J="01701" export JOK=`echo $J | sed -r 's/^0+//'` let JOK++ echo ${JOK} A segunda forma é passar a base para a operação do let: export J="01701" let J=10#${J}+1 echo $J Embora essa segunda forma seja melhor em termos de desempenho, ela deixa o código bem ilegível, já que utilizar marcador de comentário para outra coisa que não seja comentário é muito estranho. ======= Aula 47 (22 Set 2010) script para copiar uma árvore de diretórios com datas novas ======= - O script abaixo funciona tanto no GNU/Linux quanto no MacOS ##################################################################################### #!/bin/bash # WHAT? # Um script que copia um diretório para outro local e faz com que os # arquivos neste novo local tenham diferentes horários de acesso e # modificação (sem isso, os arquivos ficariam todos com a data atual # em que eles foram copiados) # HOW? # O segredo do script está em sortear datas aleatórias dentro de uma # faixa de datas. Para evitar que as datas sejam sempre iguais a # 00:00, as datas são processadas em timestamps (quantidade de # segundos desde 1/1/1970) Após saber a data, utiliza-se o touch para # modificá-la. # BUGS? # Resolva e avise pra gente :P # TODO # Fazer modificação no script para ele criar árvores de arquivos # vazios sem necessidade de ter uma origem if [ $# -ne 4 ]; then echo "Uso: $0 <data-inicio> <data-fim> <diretorio-fonte> <diretorio-destino>" echo "Ou: $0 <data-fim> <data-inicio> <diretorio-fonte> <diretorio-destino>" echo "Onde: <data-inicio> e <data-fim> é no formato yyyymmdd" echo " e o script diferencia a <data-inicio> da <data-fim>" echo " vendo quem é menor e quem é maior" exit 1 fi # Precisamos tanto da data no formato original yyyymmdd quanto no # formato de timestamp DATAINICIO=$1 DATAINICIOTS=$(date --date="$1" +"%s" 2>/dev/null) # Se der erro é porque provavelmente estou num macos. Nesse caso o comando é # diferente: if [ "$?" -eq "1" ]; then DATAINICIOTS=$(date -j -f "%Y %m %d %H %M %S" ${1}000000 +"%s" 2>/dev/null) # Se continuar dando erro, não sei :( if [ "$?" -eq "1" ]; then echo "Erro ao definir a data :(" exit 2 fi fi DATAFIM=$2 DATAFIMTS=$(date --date="$2" +"%s" 2>/dev/null) if [ "$?" -eq "1" ]; then DATAFIMTS=$(date -j -f "%Y %m %d %H %M %S" ${2}000000 +"%s" 2>/dev/null) if [ "$?" -eq "1" ]; then echo "Erro ao definir a data :(" exit 2 fi fi DIRETORIOFONTE=$3 DIRETORIODESTINO=$4 echo "Seja coerente!!! Não ponha uma data 1000 anos no futuro!!!!" echo "Pressione ENTER se vc entendeu :D" read # Se as datas estiverem trocadas, conserta if [ $DATAINICIOTS -gt $DATAFIMTS ]; then TEMP=$DATAFIMTS DATAFIMTS=$DATAINICIOTS DATAINICIOTS=$TEMP TEMP=$DATAFIM DATAFIM=$DATAINICIO DATAINICIO=$TEMP fi # Copia o diretório fonte para o diretório destino cp -rf ${DIRETORIOFONTE} ${DIRETORIODESTINO} # Calcula a diferença entre as duas datas. Assim dá para saber dentro # de que intervalo é necessário sortear números aleatórios let DIFERENCA=DATAFIMTS-DATAINICIOTS export IFS=' ' for I in `find ${DIRETORIODESTINO}`;do # A multiplicação dos dois RANDOM é necessária para garantir que # será sorteado um número grande o suficiente para possibilitar # que todo o intervalo da DIFERENCA seja percorrido. let INCREMENTO=(RANDOM*RANDOM)%DIFERENCA # Para pegar a data basta rodar o date $INCREMENTO segundos após a # DATAINICIO DATA=$(date --date="$INCREMENTO seconds $DATAINICIO" +"%Y%m%d%H%M.%S" 2>/dev/null) if [ "$?" -eq "1" ]; then DATA=$(date -j -f "%Y %m %d %H %M %S" -v+${INCREMENTO}S ${DATAINICIO}000000 +"%Y%m%d%H%M.%S" 2>/dev/null) if [ "$?" -eq "1" ]; then echo "Erro ao definir as datas novas :(" exit 3 fi fi # Finalmente, roda o touch touch -a -m -t $DATA $I done echo "OK. Diretório copiado e com datas diferentes" exit 0 ##################################################################################### ======= Aula 48 (29 Set 2010) script para usar um IP e MAC já existentes em uma rede wifi ======= - O script abaixo funciona somente no GNU/Linux. Para ele funcionar no MacOS seria necessário ter o programa macchanger compilado para esse SO. Também é provável que a sintaxe de alguns comandos precise ser modificada. Obs.: o script está incompleto. Até agora ele consegue salvar algumas informações e consegue capturar o tráfego até a hora em que algum pacote com destino ou origem igual à portas 80* passe na rede ##################################################################################### #!/bin/bash # WHAT? # Um script que tenta se conectar em uma rede wifi usando um IP e um # MAC já em uso # HOW? # O script roda o tcpdump para buscar por alguém que esteja conectado # na rede e acessando algum serviço na porta 80 ou 8080. Quando algum # pacote para as portas 80 ou 8080 é encontrado as suas informações # são salvas e o IP e o MAC utilizados são usados pelo macchanger e # pelo ifconfig na máquina local. # BUGS? # Resolva e avise pra gente :P # TODO # Terminar o script. Por enquanto ele só está salvando as informações # iniciais de rota, ap, DNS e buscando com o tcpdump por alguma # conexão às portas 80 e 8080. Falta agora listar os MACs e os IPs # encontrados para modificar depois com ifconfig e macchanger # TODO # Diminuir a quantidade de arquivos temporários. Muita coisa pode ser # salva em variáveis if [ $# -ne 1 ]; then echo "Uso: $0 <interface>" echo "Obs.: Vc já tem que estar conectado na rede pela " exit 1 fi # 1-) Pegar o roteador padrão da rede pois vamos precisar quando # matarmos o DHCP e definirmos o ip novo manualmente ROTEADOR=$(route -n | grep wlan0 | grep ^0\.0\.0\.0 | sed -r 's/( )+/ /g' | cut -f 2 -d ' ') echo "Roteador padrão salvo: $ROTEADOR... OK" # 2-) Pegar o MAC do access point (Obs.: em alguns casos o MAC que # aparece no iwconfig é diferente do MAC real por isso a forma de # pegar foi modificada. Agora o arp é executado para descobrir o MAC # do roteador padrão) #MACAP=$(iwconfig $1 | grep "Access Point" | cut -f 4- -d ':' | tr [:upper:] [:lower:] | tr -d ' ') MACAP=$(arp -n ${ROTEADOR} | sed -r 's/( )+/ /g' | cut -f 3 -d ' ' | tail -n 1) echo "MAC do roteador padrão: ${MACAP}... OK" # 3-) Salvar o resolv.conf (tem as informações de DNS) pois vamos # precisar quando matarmos o DHCP e definirmos o ip novo manualmente MKTEMP_R=$(mktemp /tmp/tmp.XXXXX) cp /etc/resolv.conf ${MKTEMP_R} echo "Informações de DNS salvas em ${MKTEMP_R}... OK" # 5-) Rodando o tcpdump para buscar alguém acessando internet MKTEMP_T=$(mktemp /tmp/tmp.XXXXX) tcpdump -l -n -e -i ${1} 2>/dev/null 1>${MKTEMP_T} & echo -n "Capturando tráfego em ${MKTEMP_T}..." # Enquanto não tiver nenhuma conexão em alguma porta começando com 80 # (provavelmente vai ser só o HTTP na 80 ou na 8080), espera while [ "$(cat ${MKTEMP_T} | grep -E "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\.80" | wc -l)" == "0" ]; do sleep 1 done echo "OK" killall -9 tcpdump 1>/dev/null 2>/dev/null # 6-) Descobrindo o ip e o mac de quem conectou -- Vai buscar todas as conexões # em portas começando com 80. A idéia é pegar a 80 ou a 8080 MKTEMP_G=$(mktemp /tmp/tmp.XXXXX) cat ${MKTEMP_T} | grep -E [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\.80 > ${MKTEMP_G} # PARAMOS AQUI EM 29/09/2010 # TODO: na hora de fazer a busca tem que eliminar as linhas que tenham sido da # própria máquina. Uma dica seria nào acessar internet no momento mas isso pode # não ser suficiente. O ideal é ter um grep que exclua tudo com o MAC da # própria máquina. Pode ser feito no cat de cima. Nesse caso seria necessário # descobrir o MAC da máquina antes (basta rodar um ifconfig $1) rm -f ${MKTEMP_T} ${MKTEMP_R} ${MKTEMP_G} exit 0 ##################################################################################### ======= Aula 49 (06 Out 2010) script para usar validar os IPs capturador pelo tcpdump ======= - Para o script da aula passada funcionar é necessário que seja verificado se algum IP capturado pelo tcpdump é um IP da rede local. Para isso é necessário validar o IP com relação ao IP da rede e à máscara. Fazendo uma operação AND entre os números é possível validar o IP. O script abaixo faz isso. Ele pode ser usado num '|' recebendo as linhas do tcpdump. ##################################################################################### #!/bin/bash # WHAT? # Um script que recebe na entrada padrão uma linha de captura do # tcpdump e verifica se na linha há algum ip que faça parte de uma # dada rede (ou seja, se o ip está dentro da faixa válida de ips # daquela rede levando em consideração o endereço da máscara) # HOW? # O script faz um AND bit a bit entre cada ip da linha do tcpdump e a # máscara da rede. Se o resultado for o endereço da rede é porque # aquele ip é válido para aquela rede. # BUGS? # Resolva e avise pra gente :P # TODO # Descobrir porque o for com o IFS igual a ponto não deu certo para # fazer o loop pelos 4 números do IP if [ $# -ne 2 ] && [ $# -ne 3 ]; then echo "Uso: $0 <rede> <mascara> [imprimeip]" echo "Se o ip for válido:" echo " Se [imprimeip] for passado o script vai devolver na saída o ip válido" echo " Se [imprimeip] for passado o script vai devolver na saída a linha toda" echo "Se o ip for inválido:" echo " Nada é impresso. Nem a linha nem o ip" exit 1 fi REDE=$1 MASCARA=$2 # Recebe a linha da entrada padrão. Isso é útil para usar o script # após um pipe com o tcpdump antes LINHA=$(cat -) # Devolve apenas os dois ips que vão ser a origem e o destino dos # pacotes IPS=$(echo $LINHA | grep -oE "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" | head -n 2) # Verifico se algum dos dois IPs é válido CERTOS=2 for IP in `echo $IPS`; do OK=1 # A idéia vai ser comparar cada 1 dos 4 números separados por ponto # do IP for ((I=1;I<5;I++)); do PEDACOREDE=$(echo $REDE | cut -f $I -d .) PEDACO1=$(echo $MASCARA | cut -f $I -d .) PEDACO2=$(echo $IP | cut -f $I -d .) let "RESULTADO=${PEDACO1} & ${PEDACO2}" if [ ${RESULTADO} -ne ${PEDACOREDE} ]; then let CERTOS-- let OK-- break fi done # Se o IP é válido e se o [imprimeip] foi passado no shell, então # imprime o IP if [ "${OK}" -ne "0" ] && [ $# -eq "3" ]; then echo ${IP} fi done # Se algum dos IPs foi válido e o [imprimeip] não foi passado no # shell, então imprime a linha toda if [ ${CERTOS} -ne "0" ]; then if [ $# -ne 3 ]; then echo ${LINHA} fi fi exit 0 #####################################################################################