3.3 Lição 2
Certificação: |
Linux Essentials |
---|---|
Versão: |
1.6 |
Tópico: |
3 O poder da linha de comando |
Objetivo: |
3.3 Como transformar comandos em script |
Lição: |
2 of 2 |
Introdução
Na última seção, usamos este exemplo simples para demonstrar a criação de scripts no Bash:
#!/bin/bash # Um script simples para saudar um único usuário. if [ $# -eq 1 ] then username=$1 echo "Hello $username!" else echo "Please enter only one argument." fi echo "Number of arguments: $#."
-
Todos os scripts devem começar com um shebang, que define o caminho para o intérprete.
-
Todos os scripts devem incluir comentários para descrever seu uso.
-
Este script em particular trabalha com um argumento, que é passado para o script quando chamado.
-
Este script contém uma declaração if, que testa as condições de uma variável interna
$#
. Essa variável foi configurada para o número de argumentos. -
Se o número de argumentos passados para o script for igual a 1, o valor do primeiro argumento é passado para uma nova variável chamada
username
e o script ecoa uma saudação ao usuário. Caso contrário, uma mensagem de erro é exibida. -
Finalmente, o script ecoa o número de argumentos. Isso é útil para a depuração (debugging).
Este é um exemplo útil para começar a explicar alguns dos outros recursos dos scripts Bash.
Códigos de saída
Você deve ter notado que nosso script tem dois estados possíveis: ou ele imprime "Hello <usuário>!"
ou uma mensagem de erro. Isso é bastante normal para muitos dos utilitários fundamentais. Veja o cat
, que você já deve estar conhecendo bem.
Vamos comparar um uso bem-sucedido do cat
com uma situação em que ele falha. Relembrando, o exemplo acima é um script chamado new_script.sh
.
$ cat -n new_script.sh 1 #!/bin/bash 2 3 # Um script simples para saudar um único usuário. 4 5 if [ $# -eq 1 ] 6 then 7 username=$1 8 9 echo "Hello $username!" 10 else 11 echo "Please enter only one argument." 12 fi 13 echo "Number of arguments: $#."
Este comando é bem-sucedido; vemos que a flag -n
também imprimiu os números de linha. Isso é muito útil na depuração de scripts, mas note eles não fazem parte do script.
Agora vamos verificar o valor de uma nova variável interna $?
. Por enquanto, observe somente a saída:
$ echo $? 0
Agora, vamos considerar uma situação em que o cat
não funcionará. Primeiro, veremos uma mensagem de erro e, em seguida, verificaremos o valor de $?
.
$ cat -n dummyfile.sh cat: dummyfile.sh: No such file or directory $ echo $? 1
A explicação para esse comportamento é a seguinte: qualquer execução do utilitário cat
retornará um código de saída. O código de saída informa se o comando foi bem-sucedido ou se ocorreu um erro. Um código de saída zero indica que o comando foi concluído com êxito. Isso vale para quase todos os comandos do Linux com os quais trabalhamos. Qualquer outro código de saída indica algum tipo de erro. O código de saída do último comando a ser executado será armazenado na variável $?
.
Os códigos de saída geralmente não são vistos por usuários humanos, mas são muito úteis ao escrever scripts. Considere um script que serve para copiar arquivos para uma unidade de rede remota. A tarefa de cópia pode falhar de diversas maneiras: por exemplo, a máquina local talvez não esteja conectada à rede ou a unidade remota pode estar cheia. Graças ao código de saída do nosso utilitário de cópia, podemos alertar o usuário sobre esses problemas ao executar o script.
É sempre uma boa ideia implementar códigos de saída, e é o que faremos agora. Temos dois caminhos em nosso script, um sucesso e um fracasso. Vamos usar zero para indicar o sucesso e um para indicar o fracasso.
1 #!/bin/bash 2 3 # Um script simples para saudar um único usuário. 4 5 if [ $# -eq 1 ] 6 then 7 username=$1 8 9 echo "Hello $username!" 10 exit 0 11 else 12 echo "Please enter only one argument." 13 exit 1 14 fi 15 echo "Number of arguments: $#."
$ ./new_script.sh Carol Hello Carol! $ echo $? 0
Note que o comando echo
na linha 15 foi totalmente ignorado. O uso de exit
encerra o script imediatamente e, portanto, essa linha nunca é encontrada.
Como manipular múltiplos argumentos
Até agora, nosso script pode tratar apenas de um único nome de usuário por vez. Qualquer número de argumentos além de um causará um erro. Vamos explorar como é possível tornar esse script mais versátil.
O primeiro instinto de um usuário pode ser usar mais variáveis posicionais como $2
, $3
e assim por diante. Infelizmente, não podemos prever o número de argumentos que um usuário pode querer usar. Para resolver esse problema, será útil introduzir mais variáveis internas.
Vamos modificar a lógica do nosso script. Se houver zero argumentos, aparecerá um erro, mas qualquer outro número de argumentos fará o script ser bem-sucedido. Este novo script será chamado friendly2.sh
.
1 #!/bin/bash 2 3 # um script simples para saudar os usuários 4 5 if [ $# -eq 0 ] 6 then 7 echo "Please enter at least one user to greet." 8 exit 1 9 else 10 echo "Hello $@!" 11 exit 0 12 fi
$ ./friendly2.sh Carol Dave Henry Hello Carol Dave Henry!
Existem duas variáveis internas que contêm todos os argumentos passados para o script: $@
e $*
. Na maioria das vezes, ambas se comportam da mesma forma. O Bash analisará (parse) os argumentos e separará cada argumento quando encontrar um espaço entre eles. De fato, o conteúdo de $@
é o seguinte:
|
|
|
|
|
|
Se você conhece outras linguagens de programação, vai reconhecer esse tipo de variável como um arranjo (array). Para criar arranjos no Bash, basta pôr um espaço entre elementos como a variável FILES
no script arraytest
abaixo:
FILES="/usr/sbin/accept /usr/sbin/pwck/ usr/sbin/chroot"
Ele contém uma lista de diversos itens. Por enquanto, ele não é muito útil, porque ainda não introduzimos nenhuma maneira de gerir esses itens individualmente.
Loops for
Vamos voltar ao exemplo arraytest
, mostrado anteriormente. Se você se lembra, neste exemplo estamos especificando um arranjo chamado FILES. O que precisamos é de uma maneira de “desdobrar” esta variável e acessar cada valor individual, um após o outro. Para isso, usaremos uma estrutura chamada loop for, presente em todas as linguagens de programação. Existem duas variáveis às quais nos referiremos: uma é o intervalo e a outra é o valor individual em que estamos trabalhando no momento. Este é o script em sua totalidade:
#!/bin/bash FILES="/usr/sbin/accept /usr/sbin/pwck/ usr/sbin/chroot" for file in $FILES do ls -lh $file done
$ ./arraytest lrwxrwxrwx 1 root root 10 Apr 24 11:02 /usr/sbin/accept -> cupsaccept -rwxr-xr-x 1 root root 54K Mar 22 14:32 /usr/sbin/pwck -rwxr-xr-x 1 root root 43K Jan 14 07:17 /usr/sbin/chroot
Se você consultar novamente o exemplo friendly2.sh
acima, verá que estamos trabalhando com um intervalo de valores contidos dentro de uma única variável $@
. Por uma questão de clareza, chamaremos a última variável de username
. Nosso script fica assim:
1 #!/bin/bash 2 3 # um script simples para saudar os usuários 4 5 if [ $# -eq 0 ] 6 then 7 echo "Please enter at least one user to greet." 8 exit 1 9 else 10 for username in $@ 11 do 12 echo "Hello $username!" 13 done 14 exit 0 15 fi
Lembre-se de que a variável definida aqui pode ter qualquer nome, e que todas as linhas entre do… done
serão executadas uma vez para cada elemento do arranjo. Vamos observar a saída de nosso script:
$ ./friendly2.sh Carol Dave Henry Hello Carol! Hello Dave! Hello Henry!
Agora vamos supor que queremos fazer a saída parecer um pouco mais humana. Queremos que a saudação apareça em uma só linha.
1 #!/bin/bash 2 3 # um script simples para saudar os usuários 4 5 if [ $# -eq 0 ] 6 then 7 echo "Please enter at least one user to greet." 8 exit 1 9 else 10 echo -n "Hello $1" 11 shift 12 for username in $@ 13 do 14 echo -n ", and $username" 15 done 16 echo "!" 17 exit 0 18 fi
A notar:
-
Ao usar
-n
comecho
, suprimimos a nova linha após a impressão. Isso significa que todos os ecos serão impressos na mesma linha, e a nova linha será impressa somente após o!
na linha 16. -
O comando
shift
removerá o primeiro elemento do arranjo, de modo que:
|
|
|
|
|
|
Se torna:
|
|
|
|
Observemos a saída:
$ ./friendly2.sh Carol Hello Carol! $ ./friendly2.sh Carol Dave Henry Hello Carol, and Dave, and Henry!
Verificando erros com expressões regulares
Podemos querer verificar todos os argumentos que o usuário está inserindo. Por exemplo, se quisermos garantir que todos os nomes passados para friendly2.sh
contenham apenas letras e que quaisquer caracteres ou números especiais causem um erro. Para executar essa verificação de erro, usaremos grep
.
Lembre-se de que podemos usar expressões regulares com grep
.
$ echo Animal | grep "^[A-Za-z]*$" Animal $ echo $? 0
$ echo 4n1ml | grep "^[A-Za-z]*$" $ echo $? 1
O ^
e o $
indicam, respectivamente, o início e o fim da linha. O [A-Za-z]
indica um intervalo de letras, maiúsculas ou minúsculas. O *
é um quantificador e modifica o intervalo de letras para que ele vá de zero a muitas letras. Em resumo, o grep
será bem-sucedido se a entrada consistir somente de letras, mas se não for o caso ele falhará.
A próxima coisa a observar é que o grep
está retornando códigos de saída com base nas correspondências que encontra. Uma correspondência positiva retorna 0
e uma negativa retorna 1
. Podemos usar isso para testar os argumentos dentro de nosso script.
1 #!/bin/bash 2 3 # um script simples para saudar os usuários 4 5 if [ $# -eq 0 ] 6 then 7 echo "Please enter at least one user to greet." 8 exit 1 9 else 10 for username in $@ 11 do 12 echo $username | grep "^[A-Za-z]*$" > /dev/null 13 if [ $? -eq 1 ] 14 then 15 echo "ERROR: Names must only contains letters." 16 exit 2 17 else 18 echo "Hello $username!" 19 fi 20 done 21 exit 0 22 fi
Na linha 12, estamos redirecionando a saída padrão para /dev/null
, que é uma maneira simples de suprimi-la. Não queremos ver nenhuma saída do comando grep
, mas somente testar seu código de saída, o que acontece na linha 13. Observe também que estamos usando um código de saída 2
para indicar um argumento inválido. Geralmente, é recomendável usar códigos de saída distintos para indicar erros diferentes; assim, um usuário mais experiente pode usar esses códigos de saída para solucionar problemas.
$ ./friendly2.sh Carol Dave Henry Hello Carol! Hello Dave! Hello Henry! $ ./friendly2.sh 42 Carol Dave Henry ERROR: Names must only contains letters. $ echo $? 2
Exercícios guiados
-
Leia o conteúdo de
script1.sh
, abaixo:#!/bin/bash if [ $# -lt 1 ] then echo "This script requires at least 1 argument." exit 1 fi echo $1 | grep "^[A-Z]*$" > /dev/null if [ $? -ne 0 ] then echo "no cake for you!" exit 2 fi echo "here's your cake!" exit 0
Qual a saída destes comandos?
-
./script1.sh
-
echo $?
-
./script1.sh cake
-
echo $?
-
./script1.sh CAKE
-
echo $?
-
-
Leia o conteúdo do arquivo
script2.sh
:for filename in $1/*.txt do cp $filename $filename.bak done
Descreva a finalidade deste script em seu entendimento.
Exercícios Exploratórios
-
Crie um script capaz de receber qualquer número de argumentos do usuário e que imprima apenas os argumentos com números maiores que 10.
Resumo
Nesta lição, você aprendeu:
-
O que são códigos de saída, o que significam e como implementá-los
-
Como verificar o código de saída de um comando
-
O que são os loops
for
e como usá-los com arranjos -
Como usar
grep
, expressões regulares e códigos de saída para verificar dados inseridos pelo usuário em scripts.
Comandos usados nos exercícios:
shift
-
Remove o primeiro elemento de um arranjo.
Variáveis Especiais:
$?
-
Contém o código de saída do último comando executado
$@
,$*
-
Contém todos os argumentos passados para o script, como um arranjo.
Respostas aos Exercícios Guiados
-
Leia o conteúdo de
script1.sh
, abaixo:#!/bin/bash if [ $# -lt 1 ] then echo "This script requires at least 1 argument." exit 1 fi echo $1 | grep "^[A-Z]*$" > /dev/null if [ $? -ne 0 ] then echo "no cake for you!" exit 2 fi echo "here's your cake!" exit 0
Qual a saída destes comandos?
-
Comando:
./script1.sh
Saída:
This script requires at least 1 argument.
-
Comando:
echo $?
Saída:
1
-
Comando:
./script1.sh cake
Saída:
no cake for you!
-
Comando:
echo $?
Saída:
2
-
Comando:
./script1.sh CAKE
Saída:
here’s your cake!
-
Comando:
echo $?
Saída:
0
-
-
Leia o conteúdo do arquivo
script2.sh
:for filename in $1/*.txt do cp $filename $filename.bak done
Descreva a finalidade deste script em seu entendimento.
Este script faz cópias de segurança de todos os arquivos que terminam com
.txt
em um subdiretório definido no primeiro argumento.
Respostas aos Exercícios Exploratórios
-
Crie um script capaz de receber qualquer número de argumentos do usuário e que imprima apenas os argumentos com números maiores que 10.
#!/bin/bash for i in $@ do echo $i | grep "^[0-9]*$" > /dev/null if [ $? -eq 0 ] then if [ $i -gt 10 ] then echo -n "$i " fi fi done echo ""