Programa��o Shell Linux, com J�lio Neves e Rubens Queiroz

3. Workshop Programação Shell Linux - Parte 3

Na primeira parte deste workshop nós afirmamos que o grande poder das Shell scripts é a combinação de centenas de comandos em conjunto com estruturas de laço muito poderosas. Quando fazemos isto nós estamos fazendo uma enorme alavancagem, ou seja, estamos multiplicando em muitas vezes o resultado de nosso esforço. Pense bem, cada programa que você usar, seja ele qual for, exigiu provavelmente muitas horas, dias, semanas ou mesmo meses para ser desenvolvido e funcionar bem. Quando você usa um programa destes você está obtendo uma alavancagem, pois você não precisará reinventar a roda. Como afirma um ditado bem conhecido do mundo nerd: Programadores bons escrevem bom código, programadores geniais tomam emprestado código bom.

Como disse Arquimedes:

Dê-me uma alavanca e um ponto de apoio e levantarei o mundo.

A programação shell Linux maximiza ao máximo esta afirmação. Ao escrever uma shell script simples, você está utilizando o resultado de centenas de horas de trabalho de diversos programadores geniais.

Agora que você já sabe usar diversas estruturas de laço e decisão, apresentaremos, nesta terceira parte do workshop, diversos comandos e macetes muito úteis e alguns de seus recursos mais utilizados, para que você possa começar a utilizá-los de imediato.

Vamos então começar nossa exploração de alguns comandos muito utilizados.

Retornar ao sumário

3.1. sort

É complicado afirmar isto, mas eu não consigo pensar em outro comando que seja tão utilizado quanto o sort (ordenação). Eu já escrevi dezenas de scripts usando este comando, sempre, é claro, em combinação com outros comandos.

Vejamos então alguns exemplos de aplicação. O comando sort, na sua forma mais simples, serve para ordenar o conteúdo de um arquivo. Tomemos o arquivo arq1.txt, que possui o seguinte conteúdo:

$ cat arq1.txt
x
a
h
j
k

O comando abaixo, executado sobre o arquivo arq1.txt, gerará o seguinte resultado:

$ sort arq1.txt
a
h
j
k
x

Podemos também ordenar o conteúdo de um arquivo de forma reversa, para isto basta fornecer a diretiva -r:

$ sort -r arq1.txt
x
k
j
h
a

Outro recurso muito útil é a combinação do conteúdo de dois arquivos. Neste caso, o conteúdo dos dois arquivos precisará estar ordenado previamente conforme nossa necessidade.

$ cat arq2.txt
aa
yy

$ cat arq3.txt
bb
zz

$ sort -m arq2.txt arq3.txt
aa
bb
yy
zz

A saída do comando sort, em todos os exemplos apresentados, tem sido redirecionada para a tela. Caso queiramos redirecionar esta saída para um arquivo para processamento posterior, temos duas opções equivalentes:

$ sort arq1.txt arq2.txt > arq1_arq2.txt 

ou

$ sort arq1.txt arq2.txt -o arq1_arq2.txt 

Como a Dicas-L tem mais de 20.000 assinantes, com bastante frequência eu preciso realizar alguma manutenção para remover endereços, classificar os assinantes por domínio, dentre outras coisas.

Como nós todos sabemos, o endereço Queiroz@Dicas-L.com.BR é exatamente igual a queiroz@dicas-l.com.br. Para endereços eletrônicos, a caixa (maiúsculas ou minúsculas) com a qual o email é escrito, não faz absolutamente nenhuma diferença. Por esta razão, qualquer operação realizada na minha lista de assinantes, tem que considerar os endereços independentemente da caixa.

Para ordenar um arquivo independentemente da caixa, usamos a diretiva -f ou --ignore-case.

$ cat lista.txt
sonia@acme.com.br
Rubens@acme.com.br
MARIA@ACME.COM.BR
mAria@acme.com.br
RUBENS@ACME.COM.BR

$ sort -f -u lista.txt
mAria@acme.com.br
Rubens@acme.com.br
sonia@acme.com.br

Como vimos, emails que diferem apenas pela caixa, são exatamente iguais. Acrescentando a diretiva -u, que significa unique ou único, eliminamos as duplicatas, independentemente da forma como foram escritas. Esta alternativa é muito útil quando precisamos combinar diferentes arquivos de endereços eletrônicos para criar uma lista única e para evitar que uma mesma pessoa receba mais de uma mensagem. Muita gente fica bastante chateada quando recebe a mesma informação mais de uma vez.

Continuando, vamos usar o comando sort em conjunto com outros comandos, para localizar os arquivos do sistemas que estão ocupando mais espaço. Isto pode ser feito de várias maneiras, uma delas é com o comando find:

$ find . -size +1024k -print 

Este comando faz uma busca, a partir do diretório corrente, por arquivos que possuam um tamanho superior a 1024k ou 1MB.

É possível também associar a saída do comando find ao comando ls para sabermos exatamente o tamanho dos arquivos encontrados:

$ find . -size +100M | xargs ls -l
-rw------- 1 queiroz queiroz 122M Jun 11 22:53 ./.com.google.Chrome.nYb6u2
-rw-r--r-- 1 queiroz queiroz 133M Out 24 21:30 512x288-video-Dailymotion.mp4
-rw-r--r-- 1 queiroz queiroz 1,6G Set  3 14:09 linuxmint-18.2-cinnamon-64bit.iso
-rw-r--r-- 1 queiroz queiroz 123M Mar  2  2017 Universal_Studios,_Fev_2017.zip
-rw-r--r-- 1 queiroz queiroz 230M Set  6 22:02 WebinarJam_Webinar.mp

Como queremos identificar os maiores arquivos, o mais conveniente é ordenar o resultado em ordem reversa, dos maiores arquivos para os menores:

$ find . -size +100M | xargs ls -lh | sort -k5 -hr  
-rw-r--r-- 1 queiroz queiroz 1,6G Set  3 14:09 ./linuxmint-18.2-cinnamon-64bit.iso
-rw-r--r-- 1 queiroz queiroz 230M Set  6 22:02 ./WebinarJam_Webinar.mp4
-rw-r--r-- 1 queiroz queiroz 133M Out 24 21:30 ./512x288-video-Dailymotion.mp4
-rw-r--r-- 1 queiroz queiroz 123M Mar  2  2017 ./Universal_Studios,_Fev_2017.zip
-rw------- 1 queiroz queiroz 122M Jun 11 22:53 ./.com.google.Chrome.nYb6u2

Como de hábito, podemos fazer esta mesma pesquisa de uma outra forma:

$ find . -size +100M -exec ls -lh {} + | sort -k5 -hr

O exec terminado com + (que ninguém conhece) é centenas de vezes mais rápido que o exec com ;.. Veja o teste onde pesquiso no meu diretório todos os arquivos que usam o comando date:

$ time find . -type f -exec grep -l date {} \; > /dev/null 2>&1 
real    0m3.653s
user    0m0.052s
sys     0m0.228s

$ time find . -type f -exec grep -l date {} + > /dev/null 2>&1 
real    0m0.023s
user    0m0.012s
sys     0m0.004s

O comando sort ordenará a saída numericamente (-n) tomando por base o valor do quinto campo (a contagem começa em 1, que é o default) e reverter o resultado (-hr), colocando os maiores valores na frente.

A diretiva -h usada tanto no comando ls quanto no comando sort significa human readable format, ou seja, um formato que é apropriado para compreensão de seres humanos. Caso não especificassemos esta diretiva, teríamos um número em bytes e para ter a dimensão exata do seu significado (kbytes, megabytes ou gigabytes), precisaríamos fazer algumas continhas. Você pode ver que os valores estão ordenados numericamente e de forma correta, mesmo quando os valores contém letras.

Para ficar ainda melhor podemos fazer uma listagem contendo apenas o nome do arquivo e seu tamanho:

$ find . -size +100M -printf "%s\t%p\n" | sort -nr
1,6G ./linuxmint-18.2-cinnamon-64bit.iso
230M ./WebinarJam_Webinar.mp4
133M ./512x288-video-Dailymotion.mp4
123M ./Universal_Studios,_Fev_2017.zip
122M ./.com.google.Chrome.nYb6u2

onde:

%s Tamanho do arquivo em bytes
\t <TAB>
%p Caminho relativo do arquivo (se fosse %f, o nome do diretório seria removido)
\n <ENTER>

Para encerrar, coloque a linha acima em uma shell script, chamada, por exemplo, de findbig.sh de forma a tornar mais fácil o seu uso.

Mas como no Shell tudo tem diversas formas de fazer, sendo cada uma mais simples que a outra, sugiro que você simplesmente crie um apelido para esta linha fazendo:

$ alias grandoes='find . -size +100M -printf "%s\t%p\n" | sort -nr' 

Nesta sessão, toda vez que você escrever grandoes no prompt terá esta lista. Mas se você quiser que esse apelido sirva para qualquer sessão, basta colocar essa linha em um arquivo que é carregado durante o seu login como p.ex. o ~/.bashrc. Essa é a magia e a simplicidade do Shell.

Todos os comandos acima foram testados em um sistema LinuxMint 18.1. Para outros sistemas podem ocorrer ligeiras variações na sintaxe dos comandos e no ordenamento dos campos do comando ls que requeiram algumas alterações.

Para encerrar, vejamos em mais detalhes o recurso de ordenação segundo um dos campos de um arquivo.

$ cat arquivo1.txt
1:2:3:4:5:6
1:1:3:4:5:6
1:4:3:4:5:6
1:2:3:4:5:6
1:0:3:4:5:6
1:2:3:4:5:6
1:7:3:4:5:6
1:2:3:4:5:6
1:0:3:4:5:6
1:9:3:4:5:6

Como podemos ver, cada registro deste arquivo possui 6 campos delimitados pelo caractere :.

O comando

$ sort -t: -k2 -n arquivo1.txt 

gerará a seguinte saída

  |
  v
1:0:3:4:5:6
1:0:3:4:5:6
1:1:3:4:5:6
1:2:3:4:5:6
1:2:3:4:5:6
1:2:3:4:5:6
1:2:3:4:5:6
1:4:3:4:5:6
1:7:3:4:5:6

Observe que o segundo campo (diretiva -k2) , indicado pela seta, está ordenado numericamente em ordem crescente. Os campos deste arquivo são separados por :. O tipo de separador é indicado pela diretiva -t:. Em seguida à diretiva -t poderíamos indicar qualquer tipo de separador. O campo a ser ordenado é indicado pela diretiva -k2 (a contagem dos campos começa em 1).

A ordenação também pode ser feita numericamente, do maior para o menor valor:

$ sort -t: -k2 -nr arquivo1.txt 
  |
  v
1:9:3:4:5:6
1:7:3:4:5:6
1:4:3:4:5:6
1:2:3:4:5:6
1:2:3:4:5:6
1:2:3:4:5:6
1:2:3:4:5:6
1:1:3:4:5:6
1:0:3:4:5:6
1:0:3:4:5:6

Para encerrar, um uso interessante do comando sort. Vamos determinar quais são os comandos que você mais usa em seu sistema GNU/Linux. O registro dos comandos emitidos fica armazenado no arquivo .bash_history. Vamos lá:

$ sort .bash_history | uniq -c | sort -nr | head
341 ls
70 cd
29 ssh ii
29 ls -l
24 cd Downloads
20 vi a.t2t
16 ls -lt | head
16 cd ..
15 ls -lt
13 vi A
13 detox -s utf_8 *

O comando uniq remove as duplicidades e coloca um contador do número de ocorrências (diretiva -c). O comando sort com a diretiva -nr faz uma ordenação numérica (-n) reversa (-r) e são listadas apenas as dez primeiras ocorrências (comando head).

Estes são, na minha opinião, os recursos mais utilizados do comando sort, mas o comando é muito mais poderoso. Para saber mais, consulte a documentação do programa (man sort).

Retornar ao sumário

3.2. date

O comando date, na sua forma mais simples, exibe a data do sistema:

$ date
Qua Out 25 22:34:03 BRST 2017

Mas não é só isto, podemos usar o comando date para diversas finalidades.

A tabela abaixo apresenta as múltiplas diretivas aceita pelo comando date:

Diretiva Significado
%d Dia
%m Mês em representação numérica
%Y Ano representado em quatro dígitos
%F Equivalente a %Y-%m-%d (formato frequentemente utilizado para inserção em bancos de dados)
%A Dia da semana por extenso

O sinal de + indica que utilizaremos os caracteres de formatação. Na Dicas-L, o arquivo em que escrevo a mensagem do dia, tem o formato AAAAMMDD, ou seja, ano com quatro algarismos, mês com dois algarismos e dia também com dois algarismos. Eu tento escrever tudo com bastante antecedência, então para que meu script descubra qual arquivo formatar, publicar no site, e enviar para os assinantes, eu preciso fazer um teste:

TIP_FILE=`date +%Y%m%d`

if [ -s $TIP_FILE.t2t ]
then
.....

O valor da variável TIP_FILE é obtido a partir do comando date +%Y%m%d. Pela tabela acima, podemos ver que a data gerada tem o formato que preciso (AAAAMMDD). Uma vez obtido o nome do arquivo, faço então um teste para ver se o arquivo existe (if [ -s $TIP_FILE.t2t ]). A diretiva -s verifica se o arquivo existe e se possui tamanho diferente de zero. Caso exista, os comandos subsequentes (não listados) fazem todo o trabalho (formatar, atualizar o site, enviar o email, gerar a lista de autores, lista de artigos, e um monte de outras coisas).

No meu sistema, o idioma é o português do Brasil. Mas dependendo da necessidade, eu posso gerar a saída do comando date em outros idiomas. Para isto, basta definir a variável LANG seguida do comando date:

$ LANG=pt_BR.utf8 date
Qua Out 25 22:51:26 BRST 2017
$ LANG=en date
Wed Oct 25 22:51:32 BRST 2017

3.2.1. Deslocamento no tempo com date

Para informar ao comando date o deslocamento de uma data, utilizamos o parâmetro -d ou --date. O deslocamento pode ser feito em dias (day), semanas(week), meses(month) ou anos(year):

$ date -d "1 month"
Sáb Nov 25 23:01:34 BRST 2017
$ date -d "2 week"
Qua Nov  8 23:01:56 BRST 2017

Para "voltarmos no tempo" utilizamos a variante ago ou então especificamos valores numéricos positivos (para avançar no tempo) e negativos (para datas passadas).

$ date -d "3 year ago"
Sáb Out 25 23:03:20 BRST 2014

No exemplo abaixo, queremos definir a variável amanha com a data do dia seguinte, usando a diretiva tomorrow:

$ amanha=`date +%d --date="tomorrow"`
$ echo $amanha
10
$ amanha=`date +%D --date="tomorrow"`
$ echo $amanha
10/10/17

A diretiva +%d refere-se ao dia do mês, e apenas a ele. Já a diretiva +%D se refere à data completa, no formato dd/mm/aa.

Vejamos mais alguns exemplos:

$ amanha=`date +%D --date="next week"`
$ echo $amanha
10/16/17
$ amanha=`date +%D --date="next month"`
$ echo $amanha
11/09/17
$ amanha=`date +%d --date="-10 days"`
$ echo $amanha
29

As diretivas next week e next month significam respectivamente próxima semana e próximo mês. Vejam que interessante, você pode também fazer contas com a data, como em -10 days, que é a data de hoje menos dez dias. A diretiva +10 days significa, calcule qual será a data somando dez dias à data de hoje.

Até agora lidamos apenas com datas, mas o comando date pode exibir também o horário. Com a conectividade global que temos hoje, suponha que você queira saber o horário na costa oeste dos Estados Unidos, para assistir a uma palestra na Internet que foi anunciada com a hora de Los Angeles. Você precisa traduzir este horário para a hora local, para não perder a palestra. Simples:

$ TZ='America/Los_Angeles' date
Qua Out 25 17:26:50 PDT 2017

Estamos cinco horas adiantados em relação a Los Angeles. TZ é abreviação de timezone.

Em linhas gerais é isto, experimentando com variações dos comandos acima, é possível fazer bastante coisa. Posso arriscar dizer que este comando é um dos mais comumente usados em shell scripts. Vale a pena conhecê-lo melhor.

A documentação do comando date (man date) fornece informações bem detalhadas sobre as diretivas aceitas.

Retornar ao sumário

3.3. slice

Este comando incorpora toda a funcionalidade dos comandos split e csplit. Ao contrário dos comandos split e csplit, o comando slice não é padrão em sistemas Unix. Foi escrito em 1987 e fazia parte de uma distribuição de utilitários chamada unix-c, que infelizmente não consigo mais encontrar. De qualquer forma, eu guardei o código fonte, que pode ser baixado do repositório da Dicas-L.

Este comando está sendo incluído neste workshop justamente para demonstrar o poder de scripts shell. Por exemplo, o desenvolvedor do programa slice tinha uma necessidade que ia além das funcionalidades dos programaps split e csplit, e criou um programa que lhe atendesse. Isto feito, o programa pode ser inserido em shell scripts que podem então contar com todos os recursos criados. Podemos fazer isto com qualquer programa, o que reforça ainda mais o conceito da alavancagem, citado no começo deste texto.

O pacote slice contém o código fonte, você precisa instalar os compiladores e demais softwares para poder compilá-lo.

Vejamos então alguns exemplos de uso:

1) Divisão de um mailbox em vários arquivos contendo uma mensagem cada:

$ slice -f mailbox -m
$ ls
mailbox:2017-05-02.14:28     mailbox:2017-12-10.10:04:47
mailbox:2017-05-24.13:35:43  mailbox:2017-02-06.09:00:15
mailbox:2017-05-24.13:40:04  mailbox:2017-02-26.09:42:23

São criados vários arquivos iniciados por "mailbox:" e os números que se seguem identificam a data da mensagem.

2) Divisão de um arquivo sempre que for encontrada uma determinada string

Para dividir um arquivo sempre que for encontrada a string "###", eliminando nos arquivos resultantes a linha que contiver esta string (diretiva -x), fazemos:

$ slice -f arq1 -e "###" -x 

Além disto, o nome dos arquivos gerados pode ser configurado através de algumas diretivas aceitas pelo comando slice. Caso o arquivo original contenha as linhas:

$ cat arquivo1.txt
------ ARQ1
abcdefghijklmno
—---- ARQ2
abcdefghijklmno
—---- ARQ3
abcdefghijklmno
—---- ARQ4
abcdefghijklmno
—---- ARQ5
abcdefghijklmno
—---- ARQ6

O comando

$ slice -f arquivo1.txt -e "------" -x x.#2
$ ls
arquivo1.txt  x.ARQ1  x.ARQ2  x.ARQ3  x.ARQ4  x.ARQ5  x.ARQ6

Os arquivos gerados receberam o prefixo "x." e o sufixo é o segundo (#2) campo da linha que preencheu os requisitos para divisão dos arquivos, indicado pelo parâmetro (-e "---<CUT>---"), em nosso caso a string ARQx, onde x varia de 1 a 6.

Repetindo, o programa está disponível para download no repositório da Dicas-L. Vale muito a pena conhecer e usar este aplicativo.

Retornar ao sumário

3.4. cut

Outro comando muito útil é o cut. Para nossos exemplos, utilizaremos um arquivo chamado exemplos_cut.txt:

$ cat exemplos_cut.txt
abcdefgh
12345678
ijklmnop
9ABCDEFG

3. Seleção de uma coluna

Para selecionar uma coluna de um arquivo, usamos a diretiva -c. O comando a seguir seleciona a terceira coluna de cada linha:

$ cut -c3 exemplos_cut.txt
c
3
k
B

2. Seleção de uma faixa de colunas

Em vez de apenas uma coluna, podemos especificar uma faixa:

$ cut -c3-5 exemplos_cut.txt 
cde
345
klm
BCD

A diretiva -c3-5 instrui o comando cut a pegar as colunas 3, 4 e 5.

3. Seleção de colunas de caracteres especificando apenas a posição de início ou fim.

Podemos também instruir o programa a pegar todas as colunas a partir de uma posição:

$ cut -c3- exemplos_cut.txt 
cdefgh
345678
klmnop
BCDEFG

A diretiva -c3- indica uma faixa de colunas, porém não especifica o fim, então todas as colunas começando na coluna 3 são selecionadas.

$ cut -c-3 exemplos_cut.txt 
abc
123
ijk
9AB

Da mesma forma, a diretiva -c-3 indica uma faixa sem especificar o valor inicial, que é então assumido como a primeira coluna. Neste exemplo, todas as colunas até a coluna 3 são selecionadas.

4. Seleção de campos específicos de um arquivo

O comando cut também aceita delimitadores. Um exemplo clássico é o arquivo /etc/passwd, cujos registros contém diversos valores separados pelo delimitador ::

 root:x:0:0:root:/root:/bin/bash 

Como podemos ver, o arquivo /etc/passwd contém 8 campos.

Para selecionar o nome de todos os usuários definidos no arquivo /etc/passwd, fazemos:

$ cut -d: -f1 /etc/passwd
root
daemon
bin
sys
sync
games
man
lp
mail
...

Da mesma forma como fizemos com as colunas nos exemplos anteriores, podemos selecionar múltiplos campos de um arquivo.

$ cut -d: -f1,7 /etc/passwd
root:/bin/bash
daemon:/usr/sbin/nologin
bin:/usr/sbin/nologin
sys:/usr/sbin/nologin
sync:/bin/sync
games:/usr/sbin/nologin
man:/usr/sbin/nologin
lp:/usr/sbin/nologin

Este comando seleciona dois campos do arquivo /etc/passwd: o nome do usuário e a shell atribuída a este usuário.

5. Exibição de todos os campos, com exceção de alguns

Vamos agora combinar o comando grep com o comando cut, exibindo todas as informações sobre determinados usuários, excluindo o campo 7, a shell definida para este usuário:

grep "/bin/bash" /etc/passwd | cut -d':' --complement -s -f7
root:x:0:0:root:/root
queiroz:x:1000:1000:Rubens Queiroz de Almeida,,,:/home/queiroz

Como este comando foi exibido em meu computador pessoal, apenas os usuários root e queiroz possuem a shell /bin/bash. Para estes usuários se exibiu a linha correspondente do arquivo /etc/passwd, com exceção do campo 7, a shell.

6. Alterar o delimitador dos campos na saída do comando cut

Como pudemos ver nos exemplos com o arquivo /etc/passwd, o delimitador utilizado para gerar a saída do comando cut foi o mesmo do arquivo original. Para alterar o delimitador a ser usado, utilizamos a diretiva --output-delimiter:

$ cut -d':'  -s -f1,6,7 --output-delimiter='#' /etc/passwd
root#/root#/bin/bash
daemon#/usr/sbin#/usr/sbin/nologin
bin#/bin#/usr/sbin/nologin
sys#/dev#/usr/sbin/nologin
sync#/bin#/bin/sync
games#/usr/games#/usr/sbin/nologin
man#/var/cache/man#/usr/sbin/nologin
lp#/var/spool/lpd#/usr/sbin/nologin
mail#/var/mail#/usr/sbin/nologin
news#/var/spool/news#/usr/sbin/nologin
...

7. Combinação do comando cut com outros comandos

O comando cut é muito utilizado em conjunto com outros processos, visto que ele pode extrair trechos da saída gerada por outros comandos.

Vejamos um exemplo com o comando ps (listagem de processos). O comando ps gera uma saída como abaixo:

$ ps -ef | head

UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 Nov03 ?        00:00:03 /sbin/init splash
root         2     0  0 Nov03 ?        00:00:00 [kthreadd]
root         3     2  0 Nov03 ?        00:00:00 [ksoftirqd/0]
root         5     2  0 Nov03 ?        00:00:00 [kworker/0:0H]
root         7     2  0 Nov03 ?        00:00:52 [rcu_sched]
root         8     2  0 Nov03 ?        00:00:00 [rcu_bh]
root         9     2  0 Nov03 ?        00:00:00 [migration/0]
root        10     2  0 Nov03 ?        00:00:00 [watchdog/0]
root        11     2  0 Nov03 ?        00:00:00 [watchdog/1]

A saída é formatada em colunas e para isto, o que separa os campos são um ou mais espaços em branco. Isto é um problema para nós, pois o comando cut aceita apenas um caractere como separador.

Precisamos então combinar o comando cut com outros comandos para eliminar os múltiplos espaços em branco em sequência.

Vejamos como podemos fazer isto:

$ ps -ef | sed 's/\s\+/ /g' | cut -d' ' -f2,8 
PID CMD
1 /sbin/init
2 [kthreadd]
3 [ksoftirqd/0]
5 [kworker/0:0H]
7 [rcu_sched]
8 [rcu_bh]
9 [migration/0]
10 [watchdog/0]
11 [watchdog/1]

... várias linhas removidas

A diretiva \s do sed casa com os caracteres em branco <TAB> e o próprio espaço em branco, e o caractere + indica que dois ou mais caracteres em branco ou tabulações devem ser substituídas por apenas um espaço em branco, tornando assim a saída gerada adequada para processamento pelo comando cut.

Para substituir os espaços em branco em sequência, podemos também usar o comando tr:

$ ps -ef | tr -s ' ' | cut -d' ' -f2,8 

Retornar ao sumário

3.5. crontab, cron

Um dos assuntos mais citados na enquete que conduzimos sobre assuntos possíveis para o nosso curso EAD de programação shell, foi a automatização de tarefas. Por esta razão, decidimos incluir neste workshop uma abordagem abrangente do assunto.

Sem sombra de dúvida, um complemento fundamental para shell scripts, é a possibilidade de agendamento de tarefas. Com este recurso, propiciado pelos aplicativos cron e crontab, a tarefa de administração de sistemas fica grandemente facilitada. Eu mantenho cinco portais (Dicas-L, Contando Histórias, Aprendendo Inglês, Inglês Instrumental e o portal do Instituto de Desenvolvimento do Potencial Humano). Todas as tarefas de manutenção destes portais são feitas por meio de shell scripts, com execução automatizada por meio do cron. O envio das mensagens, a publicação das páginas, o backup do banco de dados, a cópia de segurança de todo o portal para outro servidor, a verificação da taxa de ocupação de espaço em disco, alertas em caso de uso excessivo de CPU e muito mais. Em suma, automatizei tudo que pude, caso contrário seria impossível cuidar de tantos portais.

A automatização de tarefas em sistemas GNU/Linux é feita por meio de dois programas: crontab e cron. Além destes programas, temos também, para cada usuário, um arquivo chamado crontab, que é o local onde são gravadas as diretivas que serão seguidas pelo cron. O arquivo crontab é editado pelo comando crontab. Os arquivos crontab dos usuários são gravados no diretório /var/spool/cron/crontabs.

3.5.1. O formato do arquivo crontab

Uma linha do arquivo crontab possui o seguinte formato:

0 6 * * * comando 

Os cinco primeiros campos determinam a periodicidade de execução do comando. Abaixo segue uma explicação do significado de cada um destes campos:

Campo Significado Valores
1 Minutos 0 a 59
2 Hora 0 a 23
3 Dia do mês 1 a 31
4 Mês 1 a 12
5 Dia da semana 0 a 7, sendo que os números 0 e 7 indicam o domingo
6 Comando a ser executado qualquer comando válido do sistema

Exemplos

0  20 * * 1-5 comando

O comando será executado de segunda a sexta (1-5), exatamente às 20h.

10 10 1 * * comando

O comando será executado às 10h10, do dia 1º de todos os meses.

0,10,20,30,40,50, * 31 12 * comando

No dia 31 de dezembro, o comando será executado a cada dez minutos, o dia inteiro. Sempre que um campo for preenchido com o asterisco, significa que aquele campo assumirá todos os valores possíveis.

Podemos abreviar esta notação, em vez de especificar cada um dos minutos, podemos fazer da seguinte forma:

*/10 * 31 12 * comando

Os dias da semana podem também ser indicados por sua abreviação: sun, mon, tue, wed, thu, fri, sat.

Prosseguindo ...

* * * * * comando

Esta linha da crontab fará com que o comando seja executado todos os minutos, de todos os dias, de todos os meses. É apenas um exemplo, não faça isto, a não ser que tenha uma razão muito boa para tal.

Isto é o básico, mas o aplicativo cron oferece mais alguns recursos muito úteis. A tabela abaixo relaciona mais alguns parâmetros que podemos usar na crontab com seu significado:

Parâmetro Descrição Equivale a
@reboot ocorre ao iniciar o computador
@yearly ocorre 1 vez ao ano 0 0 1 1 *
@annually o mesmo que @yearly 0 0 1 1 *
@monthly ocorre 1 vez ao mês 0 0 1 * *
@weekly ocorre 1 vez na semana 0 0 * * 0
@daily Executa uma vez ao dia 0 0 * * *
@midnight mesmo que @daily 0 0 * * *
@hourly ocorre 1 vez a cada hora 0 * * * *

A diretiva @reboot é particularmente útil, pois em cada sistema nós temos tarefas que precisam ser executadas quando da inicialização da máquina e este parâmetro é um atalho muito conveniente para representar estas atividades.

Para utilizar estes parâmetros, bastam dois campos: o parâmetro em si e o nome do comando. Por exemplo, para executar o backup do banco de dados diariamente, acrescente a seguinte linha ao arquivo crontab:

@daily /usr/local/bin/db_backup.sh

O script db_backup.sh deve ser criado pelo administrador com as diretivas apropriadas para o backup do banco de dados.

Importante, se o seu computador não estiver no ar quando do horário da execução do comando, o cron não fará um novo agendamento, esta situação precisa ser tratada manualmente. A única exceção é a diretiva @reboot, que faz com que o comando especificado seja executado quanto a máquina for ligada.

3.5.2. Edição da crontab

Falta agora descobrir como editar a crontab, o que é bastante simples:

$ crontab -e

A diretiva -e indica ao programa crontab que queremos editar seu conteúdo. O arquivo crontab padrão vem com um conjunto de instruções (em inglês) sobre a sua sintaxe:

# Edit this file to introduce tasks to be run by cron.
# 
# Each task to run has to be defined through a single line
# indicating with different fields when the task will be run
# and what command to run for the task
# 
# To define the time you can provide concrete values for
# minute (m), hour (h), day of month (dom), month (mon),
# and day of week (dow) or use '*' in these fields (for 'any').# 
# Notice that tasks will be started based on the cron's system
# daemon's notion of time and timezones.
# 
# Output of the crontab jobs (including errors) is sent through
# email to the user the crontab file belongs to (unless redirected).
# 
# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
# 
# For more information see the manual pages of crontab(5) and cron(8)
# 
# m h  dom mon dow   command

Todas as linhas iniciadas por # são comentários, e você pode removê-las quando quiser, sem problema algum.

O arquivo crontab será aberto utilizando o editor de sua preferência. Você pode definir esta preferência através da variável de ambiente EDITOR:

export EDITOR=vi 

Se você não conhece o vi, pode usar o nano, que é um editor mais amigável, embora com muito menos recursos.

Caso deseje apenas listar o conteúdo do arquivo crontab, digite:

$ crontab -l

O usuário root pode editar diretamente a crontab dos usuários do sistema que administra. Para isto basta executar o comando:

# crontab -u [nome do usuário] -e 

IMPORTANTE: não é só colocar o comando na crontab e esquecer da vida, precisamos gerar alertas ou verificar a saída gerada pelo arquivo para ver se tudo deu certo.

Por padrão, o cron envia para o usuário um email com o resultado do comando. Através da análise destas mensagens, podemos decidir o que fazer.

Caso queiramos que o email seja enviado para um outro usuário, podemos definir, no arquivo crontab, a variável MAILTO:

 MAILTO="admin@mydomain.com" 

E se eu errar na edição da crontab, corro o risco de perder alguma coisa? Fique tranquilo, sempre que você gravar a crontab, se houver algum erro de sintaxe, o sistema te avisa, não grava as alterações e te pergunta se você quer refazer a edição:

$ crontab -e

crontab: installing new crontab
"/tmp/crontab.KA6PeR/crontab":24: bad minute
errors in crontab file, can't install.
Do you want to retry the same edit? (y/n)

Não se esqueça, sempre coloque o caminho completo do comando, visto que a variável PATH da execução do comando via cron é diferente daquela do seu ambiente.

3.5.3. Referências

Retornar ao sumário

3.6. Raridades do Shell

Passo agora a palavra para o Júlio Neves, que vai nos presentear com algumas dicas e macetes fantásticos. Muita coisa o Júlio descobre através da leitura do código fonte do bash, recursos que nem estão documentados. Aproveitem!

Por Júlio Cezar Neves

Finalizando esta série de dicas que começaram com o Rubens Queiroz, quero mostrar para vocês umas coisas raras de se ver em Shell, que poucos conhecem ou sabem usar. Vamulá:

3.6.1. O comando if testa instrução

Obviamente o comando if não se enquadra na categoria de "coisas raras de se ver em Shell" mas é bem diferente do resto, porque aqui o comando if não testa condição, por incrível que pareça, testa instrução. Experimente fazer:

VouProcurar=root
if grep -q $VouProcurar /etc/passwd
then
    echo $VouProcurar pode se logar
else
    echo $VouProcurar não pode se logar
fi

Agora experimente atribuir a VouProcurar um valor que não esteja definido em /etc/passwd e execute este pedaço de código novamente.

3.6.2. if testando o test

Já que estamos falando em if, você deve estar se pensando: "mas eu sou um viciado dependente de testar condições. Como fazer isso?"

E eu te garanto: existe um comando chamado test que só testa condições e para testá-las você usa o if testando o test. Veja:

if ! test -d dir
then
    mkdir dir
fi; cd dir

A exclamação nega a pergunta, então eu testo se não existe um diretório (-d) dir, quando então vou criá-lo. O importante é que ao final dessas linhas você estará no diretório dir, existisse ele ou não.

3.6.3. Substituições com "if test

Esse negócio de if test ... é muito estranho e por isso o comando test também pode ser substituido por um ou dois par(es) de colchetes ([...] ou [[...]]). No mesmo exemplo...

if [ ! -d dir ]
then
    mkdir dir
fi; cd dir

Agora vou te contar a mágica: toda instrução manda um código de retorno, que está na variável $?, e esse código será zero se o comando for bem sucedido, caso não seja, esse código será diferente de zero e os malucos que fizeram o interpretador resolveram criar um conector && e outro ||, de tal forma que se fizermos CMD1 && CMD2, o comando CMD2 só será executado se CMD1 for bem sucedido, se fosse CMD1 || CMD2, o segundo só será executado caso o primeiro tenha falhado. Você já deve ter notado que o && equivale ao then e o || ao else. Então esse test que acabamos de ver, nem precisa do if e pode ficar de tamanho mínimo se for (bem) escrito assim:

[ -d dir ] || mkdir dir; cd dir

Ou:

[[ -d dir ]] || mkdir dir; cd dir

3.6.4. Ainda o comando test

Você sabe pegar um dado vindo pelo pipe ou redirecionado para seu script? Não? Isso é porque você não conhece bem o comando test. Com a opção -t FD ele testa se o descritor FD está aberto no seu terminal. Ora, como sabemos que a entrada primária tem o descritor zero, podemos fazer:

[[ -t 0 ]] || echo Tem entrada primária

Mas isso não vai te resolver, você precisa saber quais são os parâmetros passados, então faça:

[[ -t 0 ]] || echo Recebi $(cat -)

Vamos aproveitar essa linha: crie um arquivo chamado vemdopipe.sh com os seguintes dados:

$ cat vemdopipe.sh
[[ -t 0 ]] || echo "Recebi pela entrada primária (pipe ou redirecionamento) $(cat -)"
[[ -n "$@" ]] && echo Recebi os seguintes parâmetros: $@

em seguida vamos torná-lo executável, faça:

$ chmod +x vemdopipe.sh    # Ou chmod 755 vemdopipe.sh 

E para testá-lo faça:

$ echo F{a,o,in,us}ca | vemdopipe.sh 
$ vemdopipe.sh F{a,o,in,us}ca 
$ vemdopipe.sh < /etc/passwd 

Viu!? O bacalho agora está apto a receber o dado venha lá de onde ele vier!

3.6.5. named pipes

Mudando de assunto, vamos falar em named pipes. Você sabia que pode sincronizar 2 ou mais processos assíncronos, trocando informações entre eles usando esta técnica? Deixa eu te mostrar: abra 2 terminais no mesmo diretório e em um deles faça:

$ mkfifo paipi
$ ls -l paipi
prw-r--r-- 1 julio julio 0 Nov  4 18:08 paipi

Viu!? É um arquivo do tipo p e se o seu ls for colorido, verá que seu nome tem uma cor de burro quando foge. Agora em um terminal escreva:

 cat paipi 

Calma, não se desespere! Ele não congelou (pinguim não congela, janelas congelam ;), ele está ouvindo uma ponta do named pipe, esperando que se fale algo na outra ponta. Então vamos para o outro terminal para falar. Redirecione qualquer saída para o named pipe que ela "miraculosamente" aparecerá no primeiro terminal, que a esta altura já não terá aparência de "congelado". Por exemplo, faça:

ls -l > paipi

E dessa forma podemos trocar dados entre 2 processos. Genial, não é?

3.6.6. Substituição de processos

Baseado nos named pipes, existe também uma implementação muito interessante que é a Substituição de Processos (process substitution) que ocorre quando você põe um < ou um > grudado na frente do parêntese da esquerda. Teclando-se o comando:

$ cat <(ls -l)

O menor (<) tem que estar colado no parêntese

O que será que aconteceu para que isso funcionasse? Bem, de uma coisa podemos ter certeza: o que está à frente do cat, seu argumento, é um arquivo, senão o cat não funcionaria.

Para descobrirmos que tipo de arquivo é esse, vamos inverter o comando, de forma que o ls -l nos dê pistas do que está ocorrendo:

$ ls -l >(cat)
l-wx------ 1 julio julio 64 Set 19 15:18 /dev/fd/63 -> pipe:[20029]

Agora eu digo para você: da mesma forma que o fd 0 é a entrada padrão, o fd 1 é a saída padrão e o fd 2 é a saída de erros padrão, o fd 63 é um named pipe. Repare que o arquivo é um link para o processo que o executa, o que desta forma resulta que a saída do comando está sendo gerada por um arquivo tipo named pipe e temporário (só sobrevive durante a execução do comando).

Você deve estar pensando que isso é uma maluquice de nerd, né? Então suponha que você tenha dois diretórios: dir e dir.bkp e deseja saber se os dois estão iguais (aquela velha dúvida: será que meu backup está atualizado?). Basta comparar os dois diretórios com o comando diff fazendo:

$ diff <(ls -la dir) <(ls -la dir.bkp) 

O comando diff, cuja entrada só aceita arquivos, receberá a listagem dos dois diretórios como se fossem arquivos (named pipes temporários ou /dev/fd/63), lê-los e compará-los.

Um uso comum de tr é converter caracteres em minúsculas em maiúsculas.Isso pode ser feito de várias maneiras. Aqui estão três delas:

$ tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ
$ tr a-z A-Z
$ tr '[: lower:]' '[: upper:]'

Retornar ao sumário

3.7. Conclusão

Com esta parte chegamos ao final de nosso workshop. Como você deve ter notado, o assunto é muito amplo e nós apenas arranhamos a superfície das possibilidades do que pode ser feito com a programação Shell Linux. Mas ainda assim, temos certeza de que este pequeno workshop lhe dará as ferramentas para aumentar a sua produtividade em no mínimo 100%, garantido. Mostramos o que pode ser feito e como aprender mais.

Antigamente, só tinha cabimento contratar para ser admin o profissional que conhecesse muito bem Shell e C. Com a chegada das interfaces gráficas, o Shell foi sendo cada vez mais deixado de lado, até chegarmos à situação atual na qual uma máquina com s.o. Linux ou Unix pode prescindir de tudo: C, Python, editor de texto, ..., qualquer coisa, só não funciona sem o Shell e, assim mesmo, tem muito admin que acha que não precisa de Shell! Incrível, né? É sempre bom lembrar: com uma interface gráfica o admin pode fazer tudo que ela permite. Com Shell ele pode fazer tudo que precisar ser feito.

Sabe o que diferencia um profissional que sabe programar em shell e outro que não sabe? O que não sabe sempre diz para o chefe "Ah, isto não dá para fazer". Já quem conhece programação shell, e você agora é um deles, sempre dirá que dá para fazer e muito rápido, com algumas poucas linhas de código.

E você sabe quem progride na carreira? É aquele que tem uma atitude positiva, que faz acontecer, que resolve os problemas, as pessoas que focam na solução. Quem conhece faz acontecer, não existe verdade mais eloquente do que essa.

Como dissemos, neste pequeno workshop lhe demos muitas ferramentas úteis, mas ainda tem muita coisa a ser aprendida. Por isto lhe convidamos a participar do curso completo de programação Shell Linux desenvolvido pelo Júlio Cezar Neves. O curso está previsto para durar cinco semanas e neste período vamos lhe conduzir desde o muito básico até o muito complexo.

O Júlio Neves fez parte da equipe de desenvolvimento do SOX, que foi um sistema Unix nacional, criado pela Cobra Computadores. Como tal, o Júlio possui um profundo conhecimento de sistemas operacionais o que lhe permite trazer para o curso não apenas o que consta da documentação, mas também aquilo que está escondido no código do bash. São dicas e truques que não se encontram em lugar nenhum.

Por esta razão lhe convidamos a participar deste curso inédito, na plataforma EAD, que lhe permitirá reforçar em muito os conceitos aprendidos neste workshop, além de aprender aquelas dicas reservadas apenas para os iniciados.

As inscrições serão abertas no dia 8 de novembro, às 13h, e você, que participou deste workshop, receberá o aviso da inscrição antes que seja feita a divulgação mais ampla, por meio da mailing list da Dicas-L e de diversos outros portais de software livre nacionais. Será criado um portal de inscrição exclusivo para os participantes do workshop, que será aberto às 12h do dia 8 de novembro. Nós lhe enviaremos um comunicado na noite do dia 7 de novembro.

O Júlio e eu faremos o acompanhamento diário dos fóruns do curso, respondendo as dúvidas e esclarecendo o que for preciso, além das reuniões semanais que serão feitas via Youtube.

Não sabemos quando o curso será oferecido novamente nesta modalidade, com o nosso acompanhamento diário, então, se você quer progredir em sua carreira, e se tornar um profissional para quem não existem problemas, apenas soluções, não perca esta oportunidade. O workshop teve a inscrição de mais de 400 pessoas, e este número não para de crescer, então, pense e decida como quer conduzir sua carreira de agora em diante.

Para encerrar, reproduzo uma pequena história, que é uma alegoria do valor do investimento no desenvolvimento pessoal.


3.8. O Velho Lenhador

Certa vez, um velho lenhador, conhecido por sempre vencer os torneios que participava, foi desafiado por um outro lenhador jovem e forte para uma disputa. A competição chamou a atenção de todos os moradores da localidade. Muitos acreditavam que finalmente o velho perderia a condição de campeão dos lenhadores, em função da grande vantagem física do jovem desafiante.

No dia marcado, os dois competidores começaram a disputa, na qual o jovem se entregou com grande energia e convicto de que seria o novo campeão. De tempos em tempos olhava para o velho e, às vezes, percebia que ele estava sentado. Pensou que o adversário estava velho demais para a disputa, e continuou cortando lenha com todo vigor.

Ao final do prazo estipulado para a competição, foram medir a produtividade dos dois lenhadores e pasmem! O velho vencera novamente, por larga margem, aquele jovem e forte lenhador.

Intrigado, o moço questionou o velho:

— Não entendo, muitas das vezes quando eu olhei para o senhor, durante a competição, notei que estava sentando, descansando, e, no entanto, conseguiu cortar muito mais lenha do que eu, como pode!!

— Engano seu! Disse o velho. Quando você me via sentado, na verdade, eu estava amolando meu machado. E percebi que você usava muita força e obtinha pouco resultado.


Agora que chegamos ao fim deste workshop, não se esqueça de deixar seu comentário. Críticas são bem-vindas, pois somente poderemos aperfeiçoar este trabalho sabendo onde podemos melhorar, os trechos que não estão claros, enfim, qualquer coisa que julgar que possa nos ajudar. Também gostamos de elogios, pois nos motivam a continuar este trabalho. A sua ajuda é muito importante e este trabalho teve como guia as muitas sugestões que recebemos dos nossos leitores.

Muito obrigado a todos vocês e até breve!

Júlio Cezar Neves Rubens Queiroz de Almeida
Retornar ao sumário

Privacy Policy | Política de Privacidade