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.
É 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
).
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
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.
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.
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
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
.
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.
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.
man crontab
, para saber mais sobre o comando crontab
man 5 crontab
, para saber mais sobre o formato do arquivo crontab
man cron,
para saber mais sobre o comando cron
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á:
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.
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.
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
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!
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 é?
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:]'
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.
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 |