anotações da aula 2
This commit is contained in:
parent
3c8a611211
commit
2e6573f7b2
1 changed files with 720 additions and 1 deletions
721
02/README.org
721
02/README.org
|
@ -24,7 +24,8 @@ de saudação.
|
|||
Exemplo:
|
||||
|
||||
#+begin_example
|
||||
:~$ Olá, qual é a sua graça?
|
||||
:~$ ./salve.sh
|
||||
Olá, qual é a sua graça?
|
||||
> Blau
|
||||
Salve, Blau!
|
||||
#+end_example
|
||||
|
@ -53,3 +54,721 @@ Alterar o estágio anterior de modo a imprimir ~caractere~ ou ~caracteres~ de
|
|||
acordo com a quantidade de caracteres de ~<palavra>~.
|
||||
|
||||
|
||||
* 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
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue