2025-08-12 10:07:16 -03:00
|
|
|
#+title: Shell Script na Prática
|
|
|
|
#+author: Blau Araujo
|
|
|
|
#+email: blau@debxp.org
|
|
|
|
|
|
|
|
|
|
|
|
* Desafio 2: Sua graça
|
|
|
|
|
|
|
|
** Objetivos
|
|
|
|
|
|
|
|
- Receber dados interativamente
|
|
|
|
- Avaliar expressões
|
|
|
|
- Tomar decisões lógicas
|
|
|
|
- Dividir linhas de texto em campos
|
|
|
|
- Contar caracteres
|
|
|
|
- Executar comandos repetidamente
|
|
|
|
- Alterar caixas de texto (maiúsculas e minúsculas)
|
|
|
|
- Comparar valores numéricos
|
|
|
|
|
|
|
|
** Enunciado
|
|
|
|
|
|
|
|
Solicitar a digitação do nome da pessoa utilizadora e imprimir uma mensagem
|
|
|
|
de saudação.
|
|
|
|
|
|
|
|
Exemplo:
|
|
|
|
|
|
|
|
#+begin_example
|
2025-08-21 12:22:06 -03:00
|
|
|
:~$ ./salve.sh
|
|
|
|
Olá, qual é a sua graça?
|
2025-08-12 10:07:16 -03:00
|
|
|
> Blau
|
|
|
|
Salve, Blau!
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
** Evolução 1
|
|
|
|
|
|
|
|
Se nada for digitado, imprimir ~Salve, simpatia!~.
|
|
|
|
|
|
|
|
** Evolução 2
|
|
|
|
|
|
|
|
Se várias palavras forem digitadas, apenas a última deve ser utilizada
|
|
|
|
na saudação.
|
|
|
|
|
|
|
|
** Evolução 3
|
|
|
|
|
|
|
|
Para cada palavra digitada, imprimir a mensagem abaixo com o primeiro
|
|
|
|
caractere de ~<palavra>~ em caixa alta:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
<palavra> tem <n> caracteres
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
** Evolução 4
|
|
|
|
|
|
|
|
Alterar o estágio anterior de modo a imprimir ~caractere~ ou ~caracteres~ de
|
|
|
|
acordo com a quantidade de caracteres de ~<palavra>~.
|
|
|
|
|
|
|
|
|
2025-08-21 12:22:06 -03:00
|
|
|
* Anotações da aula 2
|
|
|
|
|
|
|
|
** Receber dados interativamente
|
|
|
|
|
|
|
|
Existem 5 formas de passar dados para um script:
|
|
|
|
|
|
|
|
- Argumentos de linha de comando;
|
|
|
|
- Exportação de variáveis;
|
|
|
|
- Leitura de arquivos (por redirecionamento);
|
|
|
|
- Leitura da saída de outros comandos (/pipe/);
|
|
|
|
- Leitura interativa (dados digitados pelo usuário).
|
|
|
|
|
|
|
|
*** Manipulação de argumentos
|
|
|
|
|
|
|
|
Como visto na [[../01/README.org#headline-16][aula 1]], nós utilizamos os /parâmetros posicionais/ e as
|
|
|
|
expansões de parâmetros para manipular argumentos:
|
|
|
|
|
|
|
|
#+begin_src bash
|
|
|
|
#!/bin/bash
|
|
|
|
|
|
|
|
# Expande o primeiro argumento ou "simpatia", se não houver nenhum...
|
|
|
|
echo "Salve, ${1:-simpatia}!"
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
Para testar:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ ./salve Fulano
|
|
|
|
Salve, Fulano!
|
|
|
|
:~$ ./salve
|
|
|
|
Salve, simpatia!
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
*** Manipulação de variáveis exportadas
|
|
|
|
|
|
|
|
Se, antes da palavra utilizada como /invocação/ do comando, houver uma ou
|
|
|
|
mais atribuições de variáveis (palavras no formato ~NMOME=VALOR~), essas
|
|
|
|
variáveis serão exportadas para o processo do shell que será iniciado
|
|
|
|
para executar o script e, portanto, poderão ser utilizadas como qualquer
|
|
|
|
outra variável definida localmente.
|
|
|
|
|
|
|
|
Exemplo (script ~salve.sh~):
|
|
|
|
|
|
|
|
#+begin_src bash
|
|
|
|
#!/bin/bash
|
|
|
|
|
|
|
|
# Expande a variável 'nome' ou "simpatia", se 'nome' não existir...
|
|
|
|
echo "Salve, ${nome:-simpatia}!"
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
Para testar:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ nome=Fulano ./salve.sh
|
|
|
|
Salve, Fulano!
|
|
|
|
:~$ ./salve
|
|
|
|
Salve, simpatia!
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
*** Leitura de arquivos com o comando 'read'
|
|
|
|
|
|
|
|
No Bash, nós podemos ler o conteúdo de arquivos com o comando interno ~read~:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
read [OPÇÕES] [VAR] [< ARQUIVO]
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
O ~read~ lê *uma linha* de texto recebida, em princípio, pela /entrada padrão/
|
|
|
|
(o teclado do terminal). Portanto, para que a leitura da linha de um arquivo
|
|
|
|
seja possível, nós precisamos fazer um /redirecionamento de leitura/, que
|
|
|
|
associa a entrada padrão (dispositivo /stdin/) para um arquivo aberto para
|
|
|
|
leitura.
|
|
|
|
|
|
|
|
Exemplo:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ read linha < /etc/os-release
|
|
|
|
:~$ echo $linha
|
|
|
|
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
Deste modo, apenas a primeira linha de ~ARQUIVO~ será lida e seu conteúdo
|
|
|
|
será associado à variável ~VAR~. Se ~VAR~ não for informada, será utilizada
|
|
|
|
a variável interna ~REPLY~:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ read < /etc/os-release
|
|
|
|
:~$ echo $REPLY
|
|
|
|
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
Além do redirecionamento de leitura, existe uma forma muito utilizada para
|
|
|
|
|
|
|
|
*** Leitura da saída de outros comandos
|
|
|
|
|
|
|
|
Um dos mecanismos mais característicos de sistemas /Unix-like/, como o GNU/Linux,
|
|
|
|
é o chamado /pipe/ (de /canalização/). Através desse mecanismo, a /saída padrão/
|
|
|
|
de um comando é redirecionada para um arquivo especial (do tipo /pipe/) ao
|
|
|
|
mesmo tempo que a /entrada padrão/ do comando seguinte é redirecionada para
|
|
|
|
esse mesmo arquivo:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
┌───────────┐ stdout ┌──────────────┐ stdin ┌───────────┐
|
|
|
|
│ COMANDO 1 │-------->│ ARQUIVO PIPE │-------->│ COMANDO 2 │
|
|
|
|
└───────────┘ └──────────────┘ └───────────┘
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
No shell, o encadeamento de comandos por /pipe/ é feito com o /operador de pipe/
|
|
|
|
(~|~) e, nos nossos scripts, a leitura da saída de outro comando também é feita
|
|
|
|
com o comando interno ~read~, mas com um detalhe muito importante: no /pipe/,
|
|
|
|
todos os comandos são executados em novos processos do shell (chamados de
|
|
|
|
/subshells/) e, por isso, a variável associada à linha lida pelo ~read~ não
|
|
|
|
estará disponível na sessão do shell em que o comando foi executado!
|
|
|
|
|
|
|
|
Por exemplo:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ echo banana | read linha
|
|
|
|
:~$ echo $linha
|
|
|
|
<--- nada foi expandido!
|
|
|
|
:~$
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
Contudo, se o comando ~read~ for utilizado em uma sessão não interativa do
|
|
|
|
shell (como as que executam scripts), os comandos serão executados no
|
|
|
|
mesmo processo que participa do /pipe/ e, portanto, a variável associada
|
|
|
|
à leitura do ~read~ estará disponível localmente.
|
|
|
|
|
|
|
|
Exemplo:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ echo banana | bash -c 'read linha; echo $linha'
|
|
|
|
banana
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
*** Leitura interativa (dados digitados pelo usuário)
|
|
|
|
|
|
|
|
Sem o redirecionamento de leitura, o comando ~read~ adotará seu comportamento
|
|
|
|
padrão e lerá o dispositivo na /entrada padrão/ (o teclado) para receber uma
|
|
|
|
linha digitada pelo usuário:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ read
|
|
|
|
Uma linha digitada...
|
|
|
|
:~$ echo $REPLY
|
|
|
|
Uma linha digitada...
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
O Bash ainda oferece a opção ~-p~ no comando ~read~ para que seja definida uma
|
|
|
|
mensagem de /prompt/:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ read -p 'Digite algo: '
|
|
|
|
Digite algo: algo
|
|
|
|
:~$ echo $REPLY
|
|
|
|
algo
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
Mas, se estivermos utilizando uma implementação estritamente POSIX do shell
|
|
|
|
(shell ~sh~, por exemplo), a opção ~-p~ não existe e o prompt pode ser impresso
|
|
|
|
antes com o comando ~printf~:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ printf 'Digite sua graça: '; read nome
|
|
|
|
Digite sua graça: Blau
|
|
|
|
:~$ echo $nome
|
|
|
|
Blau
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
O shell ~dash~, muito utilizado como shell POSIX, implementa a opção ~-n~
|
|
|
|
no comando ~echo~ (opção também presente no ~echo~ do Bash) para que o texto
|
|
|
|
impresso não seja terminado com uma quebra de linha. Então, outra opção
|
|
|
|
compatível seria:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ echo -n 'Digite sua graça: '; read nome
|
|
|
|
Digite sua graça: Blau
|
|
|
|
:~$ echo $nome
|
|
|
|
Blau
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
Em implementações POSIX, a única opção do ~read~ é ~-r~, que impede que barras
|
|
|
|
invertidas (~\~) sejam interpretadas como caracteres de /escape/:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ read -p 'Digite algo: '
|
|
|
|
Digite algo: 123\n456
|
|
|
|
:~$ echo "$REPLY"
|
|
|
|
123n456
|
|
|
|
↑
|
|
|
|
A barra invertida sumiu porque "escapou" o caractere 'n'...
|
|
|
|
|
|
|
|
:~$ read -r -p 'Digite algo: '
|
|
|
|
Digite algo: 123\n456
|
|
|
|
:~$ echo "$REPLY"
|
|
|
|
123\n456
|
|
|
|
↑
|
|
|
|
A barra invertida foi mantida...
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
A opção ~-r~ é muito útil para manter a integridade do arquivo lido, mas a sua
|
|
|
|
omissão pode ser muito útil no uso interativo do ~read~, pois possibilita
|
|
|
|
o uso da barra invertida para quebrar a digitação de linhas muito longas
|
|
|
|
em linhas menores:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ read -p 'Digite algo: '
|
|
|
|
Digite algo: uma linha muito \
|
|
|
|
> comprida.
|
|
|
|
:~$ echo $REPLY
|
|
|
|
uma linha muito comprida.
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
O caractere ~>~, exibido no início da linha quebrada, é o /prompt de continuação/
|
|
|
|
(definido na variável ~PS2~, do Bash), e sempre aparece no terminal quando a
|
|
|
|
digitação de um comando precisa ser quebrada em várias linhas.
|
|
|
|
|
|
|
|
Para mais informações sobre o ~read~ do Bash, consulte:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
help read
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
O uso do ~read~ para receber dados digitados pelo usuário é o que chamamos
|
|
|
|
de /entrada interativa/.
|
|
|
|
|
|
|
|
** Avaliação de expressões
|
|
|
|
|
|
|
|
Na /Evolução 1/, o problema pede que seja exibida a mensagem /"Salve, simpatia!"/
|
|
|
|
se nada for digitado, o que poderia ser resolvido facilmente com uma expansão
|
|
|
|
condicional:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ read -r -p 'Digite sua graça: '
|
|
|
|
Digite sua graça:
|
|
|
|
:~$ echo "Salve, ${REPLY:-simpatia}!"
|
|
|
|
Salve, simpatia!
|
|
|
|
:~$ read -r -p 'Digite sua graça: '
|
|
|
|
Digite sua graça: Blau
|
|
|
|
:~$ echo "Salve, ${REPLY:-simpatia}!"
|
|
|
|
Salve, Blau!
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
Mas, o usuário poderia digitar espaços, o que resultaria em:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ read -r -p 'Digite sua graça: ' nome
|
|
|
|
Digite sua graça: <-- Foram digitados 4 espaços!
|
|
|
|
:~$ echo "Salve, ${nome:-simpatia}!"
|
|
|
|
Salve, !
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
Embora seja possível tratar a linha associada a ~nome~ para remover eventuais
|
|
|
|
espaços, uma solução mais direta e semântica pode ser alcançada com uma
|
|
|
|
avaliação do resultado da expansão da variável utilizando uma das formas do
|
|
|
|
comando ~test~:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
test ARGUMENTOS
|
|
|
|
[ ARGUMENTOS ]
|
|
|
|
[[ EXPRESSÃO ]] (no Bash)
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
- Com os comandos ~test~ e ~[~ as expressões avaliadas são passadas como ~ARGUMENTOS~;
|
|
|
|
- Com o comando ~[[~, tudo entre os colchetes é interpretado como ~EXPRESSÃO~.
|
|
|
|
|
|
|
|
Apesar das diferenças, o objetivo desses comandos é avaliar se ~EXPRESSÃO~
|
|
|
|
(passada ou não como ~ARGUMENTOS~) é verdadeira. Se for, os comandos terminarão
|
|
|
|
com sucesso (estado de término ~0~); caso contrário, terminarão com erro
|
|
|
|
(estado de término ~1~).
|
|
|
|
|
|
|
|
Para saber como as expressões podem ser escritas, consulte:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
help test (opções em comum)
|
|
|
|
help [[ (opções específicas do "test do Bash")
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
O comando ~[[~ é chamado de /"test do Bash"/ e se encaixa na categoria de /comandos
|
|
|
|
compostos/, ou seja, comandos delimitados por /palavras reservadas/ do shell -- logo,
|
|
|
|
~[[~ e]] ~]]~ são palavras reservadas e, para serem interpretados como palavras, precisam
|
|
|
|
estar separados de outras palavras na linha do comando.
|
|
|
|
|
|
|
|
Um dos operadores que podemos utilizar em todas as versões do ~test~ é o ~=~, que
|
|
|
|
compara duas /strings/ e avalia verdadeiro se ambas forem iguais. Sendo assim,
|
|
|
|
nós poderíamos tentar avaliar...
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ read nome
|
|
|
|
<--- foram digitados 4 espaços
|
|
|
|
:~$ test $nome = ''
|
|
|
|
bash: test: =: esperava operador unário
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
Isso aconteceu porque ~$nome~ expandiu quatro espaços, o que, internamente, seria
|
|
|
|
o mesmo que escrever:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
+--- espaços são separadores de palavras!
|
|
|
|
↓
|
|
|
|
:~$ test = ''
|
|
|
|
bash: test: =: esperava operador unário
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
Com o /test do Bash/, porém, tudo entre os colchetes duplos é avaliado como uma
|
|
|
|
expressão e, se a variável não expandir nada antes do operador ~=~, a comparação
|
|
|
|
ainda será válida, pois é o resultado da sua expansão que está em avaliação:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ read nome
|
|
|
|
<--- foram digitados 4 espaços
|
|
|
|
:~$ [[ $nome = '' ]] <--- nenhum erro foi reportado!
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
Para visualizar o estado de término do último comando executado, nós podemos
|
|
|
|
expandir o parâmetro especial ~?~:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ read nome
|
|
|
|
<--- foram digitados 4 espaços
|
|
|
|
:~$ [[ $nome = '' ]]
|
|
|
|
:~$ echo $?
|
|
|
|
0
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
O estado de término ~0~ representa /sucesso/ e, caso do /test do Bash/, significa
|
|
|
|
que a avaliação resultou em /verdadeira/. Mas, se o usuário digitasse alguma
|
|
|
|
coisa, a expressão avaliaria como /falsa/ e o /test do Bash/ terminaria com estado
|
|
|
|
~1~, representando um término com erro:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ read nome
|
|
|
|
Fulano
|
|
|
|
:~$ [[ $nome = '' ]]
|
|
|
|
:~$ echo $?
|
|
|
|
1
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
*** Testando expansões vazias de forma compatível
|
|
|
|
|
|
|
|
A forma compatível de verificar se a expansão de uma variável resulta em
|
|
|
|
algo que signifique "vazio" é com o operador ~-z~ (de /zero/):
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ read nome
|
|
|
|
|
|
|
|
:~$ test -z $nome; echo $?
|
|
|
|
0
|
|
|
|
:~$ [ -z $nome ]; echo $?
|
|
|
|
0
|
|
|
|
:~$ [[ -z $nome ]]; echo $?
|
|
|
|
0
|
|
|
|
:~$ read nome
|
|
|
|
Beltrano
|
|
|
|
:~$ test -z $nome; echo $?
|
|
|
|
1
|
|
|
|
:~$ [ -z $nome ]; echo $?
|
|
|
|
1
|
|
|
|
:~$ [[ -z $nome ]]; echo $?
|
|
|
|
1
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
** Decisões lógicas
|
|
|
|
|
|
|
|
No shell, exceto em avaliações de expressões, todas as decisões lógicas são
|
|
|
|
feitas com base em estados de término (/sucesso/ ou /erro/). É isso que se
|
|
|
|
utiliza em estruturas condicionais (como ~if~) e loops condicionais (como
|
|
|
|
~while~ e ~until~) para decidir o que será executado em seguida. Porém, estas
|
|
|
|
não são as únicas formas de condicionar a execução de comandos.
|
|
|
|
|
|
|
|
*** Operadores de encadeamento condicional
|
|
|
|
|
|
|
|
Comandos simples podem ser encadeados de várias formas com os /operadores
|
|
|
|
de controle/ do shell:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
==================================================================================
|
|
|
|
| Operador | Encadeamento |
|
|
|
|
==================================================================================
|
|
|
|
| ; | Encadeamento incondicional síncrono (um depois do outro). |
|
|
|
|
|----------|---------------------------------------------------------------------|
|
|
|
|
| & | Encadeamento incondicional assíncrono (um ao mesmo tempo do outro). |
|
|
|
|
|----------|---------------------------------------------------------------------|
|
|
|
|
| && | Encadeamento condicional "se sucesso". |
|
|
|
|
|----------|---------------------------------------------------------------------|
|
|
|
|
| || | Encadeamento condicional "se erro". |
|
|
|
|
|----------|---------------------------------------------------------------------|
|
|
|
|
| | | Encadeamento por pipe. |
|
|
|
|
==================================================================================
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
Com os operadores de encadeamento condicional, a execução do comando seguinte
|
|
|
|
fica condicionada ao término com sucesso ou erro do *último comando executado*.
|
|
|
|
|
|
|
|
Portanto, no caso da /Evolução 1/ do desafio, nós poderíamos fazer:
|
|
|
|
|
|
|
|
#+begin_src bash
|
|
|
|
#!/bin/bash
|
|
|
|
|
|
|
|
read -p 'Digite sua graça: ' nome
|
|
|
|
|
|
|
|
[[ -z $nome ]] && nome=simpatia
|
|
|
|
|
|
|
|
echo Salve, $nome!
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
** Dividir linhas de texto em campos
|
|
|
|
|
|
|
|
Outra finalidade do comando ~read~ é separar a linha lida em campos. Por padrão,
|
|
|
|
o caractere espaço é utilizado como delimitador e cada palavra da linha pode
|
|
|
|
ser atribuída a uma variável diferente.
|
|
|
|
|
|
|
|
Exemplo:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ read d m y
|
|
|
|
25 09 2025
|
|
|
|
:~$ echo $d/$m/$y
|
|
|
|
25/09/2025
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
Se houver *menos* campos do que a quantidade de variáveis, as últimas não terão
|
|
|
|
um valor associado:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ read d m y
|
|
|
|
25 09
|
|
|
|
:~$ echo $d/$m/$y
|
|
|
|
25/09/
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
Se houver *mais* campos do que a quantidade de variáveis, a última receberá todo
|
|
|
|
o restante da linha:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ read d m y
|
|
|
|
25 09 2025 século XXI
|
|
|
|
:~$ echo $d/$m/$y
|
|
|
|
25/09/2025 século XXI
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
Isso funciona muito bem, mas não se aplica muito ao caso do desafio, pois não
|
|
|
|
há como saber quantas palavras o usuário irá digitar. Portanto, nos restam
|
|
|
|
duas opções: aprender a trabalhar com /vetores/ no Bash (variáveis associadas
|
|
|
|
a uma lista de valores) ou utilizar comandos externos (utilitários).
|
|
|
|
|
|
|
|
*** Solução com o interpretador da linguagem AWK
|
|
|
|
|
|
|
|
Embora seja muito utilizado como um utilitário qualquer, o ~awk~ é um
|
|
|
|
interpretador de uma das linguagens de programação mais poderosas criadas
|
|
|
|
para sistemas /Unix-like/ e pode resolver facilmente o nosso problema:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ read data
|
|
|
|
25 09 2025
|
|
|
|
:~$ echo $data | awk '{print $NF}'
|
|
|
|
2025
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
Onde:
|
|
|
|
|
|
|
|
- ~print~: instrução para imprimir strings (equivalente ao ~echo~)
|
|
|
|
- ~$NF~: expansão do campo de número igual ao número de campos
|
|
|
|
|
|
|
|
No ~awk~, por padrão, os campos das linhas são delimitados por espaços e
|
|
|
|
tabulações e são numerados como se fossem os parâmetros posicionais do
|
|
|
|
shell, onde o campo ~$0~ representa a linha inteira. Portanto, se há 3 campos,
|
|
|
|
~NF~ será ~3~ e o campo ~$3~ será impresso.
|
|
|
|
|
|
|
|
*** Solução com 'cut' e 'rev'
|
|
|
|
|
|
|
|
O utilitário ~cut~ separa a linha em campos e imprime apenas os que forem
|
|
|
|
selecionados na linha do comando:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ read data
|
|
|
|
25 09 2025
|
|
|
|
:~$ echo $data | cut -d' ' -f3
|
|
|
|
2025
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
Onde:
|
|
|
|
|
|
|
|
- ~-d~: caractere utilizado como delimitador de campos
|
|
|
|
- ~-f~: lista dos campos a serem impressos
|
|
|
|
|
|
|
|
Mas, como não sabemos exatamente a quantidade de campos, nós podemos inverter
|
|
|
|
os caracteres da linha na variável ~data~ com o utilitário ~rev~:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ echo $data | rev
|
|
|
|
5202 90 52
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
Assim, o ~cut~ pode selecionar apenas o primeiro campo:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ echo $data | rev | cut -d' ' -f1
|
|
|
|
5202
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
Por fim, basta "desinverter" o campo com o ~rev~ novamente:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ echo $data | rev | cut -d' ' -f1 | rev
|
|
|
|
2025
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
*** Solução com o utilitário 'grep' e expressões regulares
|
|
|
|
|
|
|
|
O utilitário ~grep~ é uma poderosa ferramenta de filtragem de linhas de texto
|
|
|
|
a partir de padrões descritos com /expressões regulares/ (REGEX). Com ele e
|
|
|
|
as especificações de expressões regulares da linguagem Perl (PCRE), é
|
|
|
|
possível fazer...
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ echo $data | grep -oP '.* \K.*'
|
|
|
|
2025
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
Onde:
|
|
|
|
|
|
|
|
- ~-o~: imprimir apenas o que casar com a expressão regular
|
|
|
|
- ~-P~: utilizar especificações PCRE
|
|
|
|
|
|
|
|
Com as especificações PCRE, nós podemos utilizar o operador ~\K~, que diz
|
|
|
|
que todo o padrão escrito antes dele deve ser descartado do casamento. Então,
|
|
|
|
se a REGEX ~'.* .*'~ casa com qualquer quantidade de qualquer caractere seguida
|
|
|
|
de um espaço e qualquer outra quantidade de caracteres quaisquer, com o operador
|
|
|
|
~\K~ somente a parte que vier depois do último espaço será casada com o padrão e,
|
|
|
|
por isso, impressa pelo ~grep -o~.
|
|
|
|
|
|
|
|
#+begin_quote
|
|
|
|
As expressões regulares serão estudadas mais adiante e são mais simples do
|
|
|
|
que parece: esta é só uma demonstração do poder dos utilitários disponíveis
|
|
|
|
para uso via shell no GNU/Linux.
|
|
|
|
#+end_quote
|
|
|
|
|
|
|
|
*** Substituição de comandos (uma nova expansão)
|
|
|
|
|
|
|
|
O problema dessas soluções com comandos externos é que todos eles imprimem
|
|
|
|
suas saídas no terminal -- e nós precisamos desses dados em uma variável
|
|
|
|
para expandir na mensagem ~"Salve, $nome!"~.
|
|
|
|
|
|
|
|
Entretanto, nunca faltam soluções no shell, e nós podemos recorrer a outro
|
|
|
|
tipo de expansão chamada de /substituição de comandos/:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
$(COMANDOS)
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
Com ela, os comandos podem ser executados em um /subshell/ e suas saídas são
|
|
|
|
expandidas, podendo ser utilizadas diretamente numa concatenação de strings
|
|
|
|
ou atribuídas a variáveis.
|
|
|
|
|
|
|
|
Por exemplo:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ read nome
|
|
|
|
Blau Araujo
|
|
|
|
:~$ echo "Salve, $(echo $nome | awk '{print $NF}')!"
|
|
|
|
Salve, Araujo!
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
Ou ainda:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ read nome
|
|
|
|
Blau Araujo
|
|
|
|
:~$ last=$(echo $nome | grep -oP '.* \K.*')
|
|
|
|
:~$ echo "Salve, $last!"
|
|
|
|
Salve, Araujo!
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
*** Solução com vetores
|
|
|
|
|
|
|
|
A implementação do ~read~ no Bash oferece a opção ~-a~, que atribui cada palavra
|
|
|
|
da linha, sequencialmente, a um vetor. Vetores são um tipo de variável que tem
|
|
|
|
seu nome associado não a um valor, mas a uma lista de valores. Cada valor da
|
|
|
|
lista é chamado de /elemento/ e é identificado por um índice numérico que inicia
|
|
|
|
com ~0~. Para expandir um elemento qualquer, nós utilizamos o nome do vetor e
|
|
|
|
um /subscrito/ com o índice desse elemento:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ read -a data
|
|
|
|
25 09 2025
|
|
|
|
:~$ echo ${data[0]}/${data[1]}/${data[2]}
|
|
|
|
25/09/2025
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
Se quisermos o último elemento, basta utilizar ~-1~ no subscrito:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ echo ${data[-1]}
|
|
|
|
2025
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
O número ~-1~ significa /"o primeiro de trás para frente"/, e o mesmo princípio
|
|
|
|
poderia ser utilizado para expandir o penúltimo elemento:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ echo ${data[-2]}
|
|
|
|
09
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
** Contar caracteres
|
|
|
|
|
|
|
|
A contagem de caracteres do valor associado a uma variável pode ser feita
|
|
|
|
com o modificador ~#~ antes do identificador em uma expansão de parâmetros:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ var=banana
|
|
|
|
:~$ echo ${#var}
|
|
|
|
6
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
Se quisermos a quantidade de caracteres do elemento de um vetor, a ideia
|
|
|
|
é exatamente a mesma:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ echo ${data[-1]}
|
|
|
|
2025
|
|
|
|
:~$ echo ${#data[-1]}
|
|
|
|
4
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
** Executar comandos repetidamente
|
|
|
|
|
|
|
|
A execução de comandos repetidamente requer o uso de uma estrutura de
|
|
|
|
repetição, e o shell tem três delas...
|
|
|
|
|
|
|
|
- Estrutura ~for~: controlada por uma lista de palavras;
|
|
|
|
- Estruturas ~while~ e ~until~: controladas pelo estado de término de um comando.
|
|
|
|
|
|
|
|
Para o que é solicitado no desafio, nós podemos utilizar a estrutura ~for~,
|
|
|
|
também chamado de /loop/ ~for~:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ read nome
|
|
|
|
João da Silva
|
|
|
|
:~$ for n in $nome; do echo $n tem ${#n} caracteres; done
|
|
|
|
João tem 4 caracteres
|
|
|
|
da tem 2 caracteres
|
|
|
|
Silva tem 5 caracteres
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
Nos scripts, o ~for~ pode ser melhor estruturado:
|
|
|
|
|
|
|
|
#+begin_src bash
|
|
|
|
for n in $nome; do
|
|
|
|
echo $n tem ${#n} caracteres
|
|
|
|
done
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
** Alterar caixas de texto (maiúsculas e minúsculas)
|
|
|
|
|
|
|
|
O Bash também é capaz de modificar expansões de modo a alterar a caixa dos
|
|
|
|
caracteres expandidos:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
${VAR^} - Trona maiúsculo o primero caractere expandido.
|
|
|
|
${VAR^^} - Trona maiúsculos todos os caracteres expandidos.
|
|
|
|
${VAR,} - Trona minúsculo o primero caractere expandido.
|
|
|
|
${VAR,,} - Trona minúsculos todos os caracteres expandidos.
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
Por exemplo:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ read nome
|
|
|
|
João da Silva
|
|
|
|
:~$ for n in $nome; do echo ${n^^} tem ${#n} caracteres; done
|
|
|
|
JOÃO tem 4 caracteres
|
|
|
|
DA tem 2 caracteres
|
|
|
|
SILVA tem 5 caracteres
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
** Comparar valores numéricos
|
|
|
|
|
|
|
|
Novamente, se temos que comparar valores, isso terá que ser feito pela avaliação
|
|
|
|
de uma expressão. Utilizando as três implementações do ~test~, nós temos os
|
|
|
|
operadores de comparação numérica:
|
|
|
|
|
|
|
|
- ~-eq~: os números são iguais;
|
|
|
|
- ~-ne~: os números não são iguais;
|
|
|
|
- ~-lt~: o número da esquerda é menor do que o da direita;
|
|
|
|
- ~-le~: o número da esquerda é menor ou igual ao da direita;
|
|
|
|
- ~-gt~: o número da esquerda é maior do que o da direita;
|
|
|
|
- ~-ge~: o número da esquerda é maior ou igual ao da direita.
|
|
|
|
|
|
|
|
Por exemplo:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ a=banana b=pitanga
|
|
|
|
:~$ echo "$a (${#a}), $b (${#b})"
|
|
|
|
banana (6), pitanga (7)
|
|
|
|
:~$ test ${#a} -lt ${#b}; echo $?
|
|
|
|
0
|
|
|
|
:~$ test ${#a} -eq ${#b}; echo $?
|
|
|
|
1
|
|
|
|
:~$ test ${#a} -gt ${#b}; echo $?
|
|
|
|
1
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
#+begin_quote
|
|
|
|
O mesmo vale para os comandos ~[~ e ~[[~.
|
|
|
|
#+end_quote
|
|
|
|
|
|
|
|
As comparações também podem ser feitas com valores numéricos literais:
|
|
|
|
|
|
|
|
#+begin_example
|
|
|
|
:~$ a=banana
|
|
|
|
:~$ [[ ${#a} -lt 10 ]]; echo $?
|
|
|
|
0
|
|
|
|
:~$ [[ 10 -lt ${#a} ]]; echo $?
|
|
|
|
1
|
|
|
|
#+end_example
|
|
|
|
|