Módulo 1: A linguagem
Aula 2: Me chame pelo meu nome
Conteúdo da aula
Problema 2
Escreva um script que receba um NOME
pela linha do comando de sua invocação e
imprima: “Salve, NOME!”.
Entendendo o problema
O ponto central desta aula está em descobrir como podemos passar dados para um script na sua invocação pela linha do comando. Posto de outra forma, visto que um script, na verdade, é executado por um processo do shell, nós teremos que responder:
- O que é um processo?
- O que é um processo do shell?
- Como ele é iniciado?
- Como ele pode receber dados?
- Como esses dados podem ser usados em comandos?
Mais uma vez, porém, não há como responder essas questões sem darmos mais uma olhada em como o sistema operacional lida com a execução de programas e onde o shell se encaixa “no grande esquema das coisas”.
2.1 – Programas e processos
Quando falamos de processos, é comum ficarmos com a impressão de que eles são só programas em execução: afinal, sempre que executamos um programa, um novo processo, associado a ele, é iniciado – contudo, uma associação não é o mesmo que uma definição! É por isso que vamos respirar fundo e dar um passo atrás para, antes de qualquer coisa, entendermos o que é um programa.
O que são programas
Do ponto de vista do sistema, programas são os arquivos binários que contêm instruções, dados e diversas outras informações requeridas pelo sistema para que, primeiro, seus conteúdos sejam validados como programas executáveis e, segundo, todas as suas partes sejam transferidas corretamente para regiões bem definidas na memória do computador. Cada sistema operacional deve especificar o que será considerado um formato binário executável válido, ou seja, como o conteúdo binário do arquivo deverá ser disposto e a quais informações esses dados deverão corresponder.
Neste aspecto, o arquivo de um programa não difere muito do arquivo de uma imagem PNG ou de um áudio OGG, no sentido de que todo arquivo possui uma forma padronizada e predefinida para dizer ao sistema como os seus conteúdos deverão ser tratados.
Exibindo o formato binário de uma imagem:
:~$ file -b ~/Imagens/figura1.png | cut -d, -f1 PNG image data
Exibindo o formato binário de um arquivo de vídeo:
:~ $ file -b ~Vídeos/exemplo.mkv | cut -d, -f1 Matroska data
No caso dos arquivos binários executáveis de programas (que podemos chamar apenas de “binários”), os formatos mais comuns são:
- GNU/Linux: ELF
- macOS: Mach-O
- Windows: PE
Algumas implementações do FreeBSD e do NetBSD também oferecem suporte ao formato Mach-O.
Exibindo o formato binário do programa Bash:
:~$ file -b /usr/bin/bash | cut -d, -f1 ELF 64-bit LSB pie executable
Carga do programa na memória
Quando um programa é executado, o kernel carrega cada parte do programa (as suas seções) na memória segundo uma estrutura de dados padronizada dividida em segmentos. Essa estrutura, também chamada de layout de memória, é definida como uma representação virtual e contínua da verdadeira localização dos dados de um programa na memória: a chamada memória virtual. Deste modo, todos os programas terão acesso limitado e compartilhado à memória principal do sistema, mas trabalharão com a “ilusão” de terem acesso a toda a memória apenas para si mesmos.
O diagrama abaixo, representa (muito superficialmente) a memória virtual que o kernel associa à execução de um programa. Para facilitar o entendimento, imagine a memória virtual como uma estante, onde cada segmento de dados é colocado em uma prateleira.
As “prateleiras” mais baixas (os endereços mais baixos), receberão o código
das instruções em linguagem de máquina (segmento .text
) e os dados que forem
definidos em variáveis no programa (segmentos .data
e .bss
). A região
central da “estante” é reservada para a alocação temporária de dados trabalhados
dinamicamente ao longo da execução do programa (heap) e para a carga de
códigos oriundos de bibliotecas ligadas dinamicamente ao programa.
Bibliotecas são arquivos de programas criados com o propósito de serem utilizados por outros programas, o que pode acontecer de forma estática, quando a biblioteca é compilada junto com o arquivo binário do programa, ou dinâmica, quando a biblioteca é carregada na memória apenas quando o programa é executado.
Finalmente, nas “prateleiras” mais altas da memória virtual do programa, nós temos a pilha (ou stack, em inglês), onde, como o nome sugere, dados poderão ser “empilhados” e “desempilhados” pelo programa, quando necessário. É como numa pilha de pratos, só que invertida: o prato na base da pilha está no endereço mais alto da memória, enquanto que novos pratos serão adicionados em endereços cada vez mais baixos.
O que é um processo, afinal?
É exatamente essa estrutura de dados na memória, junto com outras estruturas de dados que o kernel utiliza para administrar a execução de programas, que nós chamamos de processo! Ou seja…
Processo é uma estrutura de dados (na memória) associada à execução de um programa.
É fundamental que se entenda desta forma, mesmo para nós, que não estamos visando desenvolver sistemas, porque há muito mais num processo do que a simples cópia do binário de um programa para a memória e a sua execução: o GNU/Linux é um sistema multitarefas e multiusuário, e tudo isso precisa ser cuidadosamente orquestrado pelo kernel. Como, por padrão, os processos serão iniciados (e administrados) a partir do shell, as implicações para a criação de scripts são inevitáveis!
No terceiro módulo (o sistema), nós exploraremos detalhes mais aprofundados sobre a administração de processos. No momento, porém, o que nos interessa é saber como os processos se relacionam com o shell e a criação e execução de scripts. Portanto, vamos relembrar como os processos são iniciados a partir do shell:
A novidade é que, quando o shell faz chamada de sistema execve
, ele precisa
passar algumas informações. Segundo as definições dessa chamada de sistema na
biblioteca C padrão (man execve
), o shell tem que passar três argumentos:
execve(CAMINHO, ARGV, ENV)
Onde CAMINHO
é o caminho completo do binário do programa, ARGV
é um vetor
contendo todas as palavras utilizadas na invocação do programa, incluindo seus
argumentos, e ENV
(envp
, na verdade, por se tratar de um ponteiro), que é o
ponteiro para um vetor que contém todas as variáveis, no formato NOME=VALOR
,
definidas como “exportadas” no shell. Nós já vimos onde, no processo, esses
dois vetores vão parar…
Vetor de ambiente
Quando o shell é iniciado (e nós já veremos como isso acontece), diversas
variáveis são definidas e incluídas num vetor interno do shell chamado de
environment (ambiente, em inglês), onde cada elemento desse vetor é uma
string no formato NOME=VALOR
seguida de um caractere nulo (valor ASCII 0
).
Quando um processo é iniciado pela chamada de sistema execve
, o endereço desse
vetor na memória (ou seja, seu ponteiro) é copiado na pilha do novo processo.
Para que uma variável seja incluída no vetor de ambiente do shell, ela terá que ser exportada, o que pode ser feito:
- Pelo comando interno
export
; - Pelo comando interno do Bash
declare
com a opção-x
; - Pela atribuição de variáveis antes da invocação de um programa.
Exportando variáveis para qualquer novo processo iniciado pelo shell corrente:
:~$ fruta=banana bicho=zebra :~$ export fruta bicho
Desta forma, as variáveis fruta
e bicho
serão passadas para a pilha de
qualquer processo que venha a ser iniciado nesta sessão do shell. O mesmo
poderia ser feito com o comando interno declare
:
:~$ fruta=banana bicho=zebra :~$ declare -x fruta bicho
De um modo ou de outro, o atributo de exportação das duas variáveis poderia ser
conferido, no Bash, com a opção -p
do comando declare
:
:~$ declare -p fruta bicho declare -x fruta="banana" declare -x bicho="zebra"
Se, a partir de algum momento, nós quiséssemos remover variáveis do vetor ambiente, bastaria executar o comando export
com a opção -n
:
:~$ export -n fruta bicho
Conferindo…
:~ $ declare -p fruta bicho declare -- fruta="pera" declare -- bicho="tigre"
Exportando variáveis apenas para um novo processo específico:
:~$ fruta=banana bicho=zebra ./meu-script.sh
Como vimos, quem executa scripts é o shell, logo, as variáveis fruta
e bicho
só serão exportadas para o novo processo do shell que for iniciado para executar
os comandos no script.
O comando declare -x
e o utilitário env
, ambos sem mais argumentos, listam
todas as variáveis exportadas na sessão corrente do shell.
Antes de passarmos para o elemento seguinte na pilha de um novo processo, vale a pena fazer algumas observações…
- Apesar do uso comum na linguagem coloquial, tecnicamente, não existem “variáveis de ambiente”: o que existem são variáveis exportadas, variáveis do/no [vetor] ambiente ou vetor [de] ambiente, se estivermos falando daquilo que está na pilha de um processo.
- Embora todos os programas recebam o vetor ambiente nos seus processos, não é obrigatório que todos esses dados sejam utilizados, ou seja, não basta exportar variáveis para scripts e programas: se for o caso, eles terão que ser programados para fazer algo com esses dados.
- Esta pode ser uma pista para uma possível solução do nosso problema!
Quantidade e Vetor de argumentos
Todas as palavras na linha do comando que corresponderem à invocação de um
programa, incluindo seus argumentos, serão passadas para a chamada de sistema
execve
. Quando executada, a lista dos argumentos (também separada por um
caractere nulo) será incluída na pilha do novo processo acima do vetor de
ambiente, enquanto a quantidade total de palavras será registrada no topo de
tudo (o topo da pilha está embaixo, não se esqueça).
Para o nosso uso em linhas de comandos e scripts, o shell possibilita expandir os dados dos argumentos do seu processo na forma de parâmetros posicionais e dos parâmetros especiais: *
, @
e #
.
Parâmetros do shell relacionados com argumentos
Parâmetro | O que expande |
---|---|
0 |
A palavra utilizada para iniciar o processo do shell. |
1 … N |
Cada argumento da invocação do shell, se houver. |
# |
Quantidade de argumentos da invocação do shell (zero, se não houver). |
* |
Todas as palavras utilizadas como argumento da invocação do shell. |
@ |
Todas as palavras utilizadas como argumento da invocação do shell. |
2.2 – Parâmetros do shell
Para o shell, todas as entidades que identificam valores são parâmetros, que podem ser um nome, um número ou um caractere gráfico – se o identificador for um nome, nós teremos uma variável, caso contrário, poderá ser um parâmetro posicional (identificado por um número) ou um parâmetro especial (identificado por um caractere gráfico).
Parâmetros do shell
Parâmetro | Formação |
---|---|
Variável | Caracteres alfabéticos da tabela ASCII, o caractere sublinhado e dígitos numéricos, desde que não sejam o primeiro caractere do nome. |
Parâmetro posicional | Qualquer combinação de números. Se houver mais de um dígito, o número deve ser escrito entre chaves: {N} . |
Parâmetro especial | Caracteres gráficos # , * , @ , - , $ , ? e ! . |
Portanto, podemos dizer que toda variável é um parâmetro, mas nem todo parâmetro é uma variável.
Parâmetros especiais
Parâmetro | O que expande |
---|---|
# |
Quantidade de argumentos da invocação do shell (zero, se não houver). |
* |
Todas as palavras utilizadas como argumentos da invocação do shell. |
@ |
Todas as palavras utilizadas como argumentos da invocação do shell. |
- |
As opções de início do shell na forma de flags. |
$ |
O PID do processo corrente do shell. |
? |
O valor retornado ao término do último comando executado. |
! |
O PID do último processo executado em segundo plano (job). |
O manual do Bash inclui o parâmetro 0
(zero) na lista dos parâmetros
especiais, mas eu prefiro deixá-lo entre os parâmetros posicionais em nome da
coerência conceitual: afinal, zero é um número!
Outra diferença importante entre as variáveis e os demais parâmetros é que, de modo geral, nós podemos criar variáveis, mas apenas o shell pode definir e gerenciar os parâmetros posicionais e especiais. A exceção, é quando o próprio shell define variáveis internamente como imutáveis, ou seja, de modo que elas só poderão ser expandidas (read only).
Ordem de processamento de comandos
Uma coisa que geralmente passa despercebida, é que o shell realiza diversos processamentos nas nossas linhas de comandos antes delas serem, de fato, executadas. Esse processamento ocorre numa ordem bem específica de cinco etapas. Na primeira, os apelidos são expandidos e, respeitando as regras de citação, todas as palavras e metacaracteres são coletados. Em seguida, de acordo com as combinações de palavras e operadores encontradas, o shell monta os comandos e determina se eles são comandos simples, complexos (listas de comandos e pipes) ou compostos (se houver palavras reservadas).
As expansões só serão processadas na terceira etapa, também seguindo uma ordem específica de acordo com os tipos de expansões encontradas na linha do comando. Depois das expansões, já na quarta etapa, se houver algum, o shell providenciará os recursos necessários os redirecionamentos, como a criação de arquivos, por exemplo. Finalmente, na quinta e última etapa, o shell verificará se precisa monitorar o estado de término (sucesso ou erro) dos comandos encontrados. Só depois de processar essas cinco etapas, é que a linha do nosso comando será executada: e, neste ponto, ela poderá ser bem diferente daquilo que nós digitamos.
É fundamental termos sempre em mente a ordem em que cada uma das etapas de processamento acontece: é muito fácil escrevermos sintaxes aparentemente válidas, mas que nunca resultarão no que esperamos. Portanto, aqui está um esquema geral:
Expansão de parâmetros
O Bash é capaz de processar sete tipos de expansões: e nós veremos todas elas ao longo do curso. Por enquanto, porém, vamos nos concentrar naquele tipo que é chamado de expansão de parâmetros, terminologia que engloba tanto a expansão de variáveis quanto a dos demais parâmetros do shell. A sintaxe geral da expansão de parâmetros é:
${IDENTIFICADOR}
Caso IDENTIFICADOR
seja um parâmetro ou o nome de uma variável escalar (um
nome que identifica apenas um valor), as chaves são opcionais: desde que os
caracteres seguintes não causem confusão quanto ao que realmente queremos
expandir.
Vejamos alguns exemplos…
:~$ fruta=banana :~$ echo ${fruta} banana :~$ echo $fruta banana
Com ou sem chaves, o resultado foi o mesmo: o shell processou a expansão da
variável fruta
, que passou a ser o argumento do comando echo
– isso, após as
cinco etapas de processamento da linha do nosso comando.
Mas as chaves seriam necessárias neste outro caso, onde queremos concatenar as palavras expandidas para formar a frase “banana ou bananada?”:
:~$ fruta=banana :~$ echo $fruta ou $frutada? banana ou ? :~$ echo $fruta ou ${fruta}da? banana ou bananada?
No primeiro caso, a expansão de fruta
foi seguida de caracteres que formam
nomes válidos para variáveis (d
e a
), resultando na expansão do nome de uma
variável inexistente: =frutada+. Aliás, por padrão, o shell não produzirá nenhum
erro quando tentarmos expandir uma variável inexistente: o resultado será a
expansão de uma string vazia (na prática, a expansão de nada).
As expansões de parâmetros também podem receber símbolos que modificarão os
dados expandidos: nesses casos, o uso das chaves deixa de ser opcional! Por
exemplo, nós poderíamos querer que a primeira letra da frase fosse expandida em
caixa alta (maiúscula), o que poderia ser feito com o modificador ^
antes do
nome da variável:
:~$ fruta=banana :~$ echo ${^fruta} ou ${fruta}da? Banana ou bananada?
O Bash oferece diversos modificadores para as expansões de parâmetros, todos muito úteis e poderosos como recursos de programação, mas é melhor irmos com calma, priorizando sempre a descoberta dos contextos em que eles seriam aplicáveis.
Afinal de contas, os problemas sempre vêm antes das soluções!
Como as citações afetam as expansões de parâmetros
Como já vimos de passagem na primeira aula, as regras de citação afetam como as palavras serão separadas. Agora, nós descobrimos que esta separação ocorre na primeira etapa de processamento da linha do nosso comando, mas isso não é tudo: todas as expansões podem ser afetadas pelas citações!
Observe este exemplo:
:~$ bicho=zebra :~$ echo A expansão $bicho resulta em $bicho. :~$ A expansão zebra resulta em zebra.
Aqui, todas as expansões da variável bicho
foram processadas, mas estava claro
que a minha intenção era que a primeira não fosse expandida. Neste caso, nós
temos algumas opções, como:
echo A expansão \$bicho resulta em $bicho. echo A expansão '$bicho' resulta em $bicho. echo 'A expansão $bicho resulta em' $bicho.
Nessas três soluções, a cadeia de caracteres impressa pelo echo
seria:
A expansão $bicho resulta em zebra.
Funcionou porque, na primeira solução, o $
foi citado pela barra invertida
(\
) e, nas outras duas, o cifrão estava entre os caracteres citados pelas
aspas simples.
Mas, entre aspas duplas, a situação seria diferente, veja:
:~$ echo "A expansão $bicho resulta em $bicho." :~$ A expansão zebra resulta em zebra.
Desta vez, o significado do cifrão não foi removido e todas as expansões foram processadas. Isso porque, com aspas duplas, nós temos quatro exceções para a remoção de significados especiais:
- O caractere
$
, quando iniciar uma expansão; - O acento grave (
`
), na forma obsoleta de uma substituição de comandos; - A barra invertida (
\
), se vier antes de$
,`
,\
ou!
. - E a exclamação (
!
), se a expansão do histórico estiver habilitada.
Mas as aspas duplas têm outro efeito sobre as expansões, observe o exemplo:
:~$ nome='Luis Carlos' :~$ set -- $nome :~$ echo $# 2
O comando interno set
tem a seguinte descrição no help
:
:~$ help -d set set - Set or unset values of shell options and positional parameters.
Define ou remove valores das opções do shell e de parâmetros posicionais.
No exemplo, depois dos dois traços (--
), eu utilizei a expansão da variável
nome
para definir as palavras que passariam a ser os parâmetros posicionais do
shell corrente. Como eu não usei aspas, duas palavras foram expandidas: Luis
e
Carlos
, resultando numa contagem de dois parâmetros posicionais, que foi o que
eu expandi com a cerquilha (#
). Para conferir, vejamos o que expandem os
parâmetros posicionais 1
, 2
e 3
(este último, em tese, inexistente):
:~$ echo $1, $2, $3 Luis, Carlos,
Mas, expandindo nome entre aspas (duplas, é claro)…
:~$ set -- "$nome" :~$ echo $# 1 :~$ echo $1, $2, $3 Luis Carlos, ,
Os traços (--
), na linha do comando interno set
, separam o fim das
definições de opções do shell e o início das definições dos parâmetros
posicionais.
Resumindo o que foi demonstrado até aqui, podemos dizer que as aspas duplas, além de manterem o significado especial do cifrão, possibilitando as expansões que iniciem com ele, fazem com que os caracteres expandidos formem apenas uma palavra na linha do comando.
Muita atenção, as aspas duplas não possibilitam o processamento de qualquer expansão, mas apenas daquelas que são iniciadas com o cifrão!
Parâmetros especiais '@' e '*'
Se você não ficou confuso com as minhas descrições dos parâmetros especiais @
e *
, deveria…
Parâmetro | O que expande |
---|---|
* |
Todas as palavras utilizadas como argumentos da invocação do shell. |
@ |
Todas as palavras utilizadas como argumentos da invocação do shell. |
No mínimo, você deveria estar se perguntando: “será que eu posso expandir qualquer um dos dois para obter todas as palavras utilizadas como argumentos na invocação do shell?” – e eu diria que, em princípio, é isso mesmo… Mas não se a expansão for feita entre aspas duplas!
Veja, sem aspas, nós temos o mesmo resultado:
:~$ nome='Luis Carlos' :~$ set -- $nome :~$ echo $# 2 :~$ printf '%s\n' $@ Luis Carlos :~$ printf '%s\n' $* Luis Carlos
Aqui, eu utilizei o builtin printf
com a string de formato '%s\n'
, que sempre será o primeiro argumento que o printf
receber. A função da string de formato é definir como cada um dos demais argumentos, representados pelo especificador %s
, serão exibidos. No caso, eu determinei que uma quebra de linha, escrita com a notação ANSI-C \n
, deveria ser incluída depois de cada um dos outros argumentos. Como tanto o @
quanto o *
expandiram duas palavras, que são os dois parâmetros na sessão do shell, elas foram impressas em linhas separadas.
A expansão do '@' entre aspas
Quando expandido entre aspas, porém, o @
mantém a separação original dos parâmetros posicionais, como se eles fossem expandidos assim…
"$@" → "$1" "$2" "$3" …
Isso pode ser melhor demonstrado com outro exemplo:
:~$ set – Maria José 'Luis Carlos' :~$ echo $# 3 :~$ printf '%s\n' $@ Maria José Luis Carlos :~$ printf '%s\n' "$@" Maria José Luis Carlos
A expansão do '*' entre aspas
Com o *
expendido entre aspas, os parâmetros da sessão também terão a sua
separação original mantida, mas eles serão concatenados com o primeiro caractere
definido em IFS
, que é uma variável do shell onde são definidos os caracteres
que serão usados para separar palavras após as expansões iniciadas com o $
e
da substituição de processos:
O resultado seria equivalente a este:
"$*" → "$1caractere$2caractere$3" …
Por padrão, a variável IFS
é definida com os caracteres espaço, tabulação
(\t
) e quebra de linha (\n
). Como o primeiro caractere é o espaço, nós
teremos:
"$*" → "$1 $2 $3" …
Mas IFS
pode ser mudada para outros caracteres, por exemplo:
:~$ IFS='|'
Assim, o primeiro (e único) caractere passaria a ser a barra vertical (|
), e
isso vai nos ajudar a visualizar o que acontece na expansão do *
entre aspas:
:~$ set – Maria José 'Luis Carlos' :~$ echo $# 3 :~$ printf '%s\n' $* Maria José Luis Carlos :~$ printf '%s\n' "$*" Maria José Luis Carlos :~$ IFS='|' :~$ printf '%s\n' "$*" Maria|José|Luis Carlos
Repare que a barra (|
) foi interpolada entre cada um dos parâmetros do shell, mas o espaço original em Luis Carlos
foi mantido.
Para restaurar IFS
, nós podemos expandir uma citação de caracteres ANSI-C:
:~$ IFS=$' \t\n'
2.3 – Possíveis soluções
Com o que vimos até aqui, nós temos pelo menos duas abordagens que podem nos levar à solução do problema: exportar uma variável para o script ou passar o nome como um argumento na invocação do script. Então, bora testar os dois conceitos!
Exportando uma variável para outra sessão do shell
Como a execução de um script sempre resulta no início de uma sessão do shell, vamos demonstrar o conceito invocando o Bash na linha de comandos:
:~$ nome='Luis Carlos' :~$ bash :~$ echo "Salve, $nome" Salve, :~$ exit
Como a variável nome
foi definida, mas não foi exportada, a nova sessão do shell, iniciada com a invocação do binário bash
, não a recebeu no ambiente de seu processo e nada foi expandido.
Se este é o problema, vamos tentar novamente, desta vez exportando nome
:
:~$ export nome='Luis Carlos' :~$ bash :~$ echo "Salve, $nome" Salve, Luis Carlos :~$ exit
Sucesso! Então vamos fazer tudo novamente, só que numa linha de comando:
:~$ nome='Luis Carlos' bash :~$ echo "Salve, $nome" Salve, Luis Carlos :~$ exit
Sensacional, nós já temos a primeira possível solução para o problema e já
podemos criar o script, digamos, nome-export.sh
:
:~$ cat << 'FIM' >> nome-export.sh > #!/bin/bash > > echo "Salve, $nome!" > FIM :~$ chmod +x nome-export.sh :~$ nome='Maria Antonieta' ./nome-export.sh Salve, Maria Antonieta!
Funciona que é uma beleza!
Aproveitando a empolgação, vamos testar o segundo conceito…
Passagem de argumentos para outra sessão do shell
Novamente, vamos testar invocando o executável do Bash. O problema é que, por padrão, se eu passar um nome para ele, este argumento será interpretado como o nome de um arquivo.
Sendo assim, eu preciso passar o nome com a opção -s
, como podemos ler no manual (man bash
) em tradução livre:
Esta opção permite que parâmetros posicionais sejam definidos na invocação de um shell interativo ou na leitura de uma entrada via pipe.
Então, vamos testar…
:~$ bash -s 'Luis Carlos' :~$ echo "Salve, $*" Salve, Luis Carlos :~$ exit
Massa demais!
Será que dá para passar vários argumentos e imprimir com o printf
?
:~$ bash -s Maria José 'Luis Carlos' :~ $ printf 'Salve, %s\n' "$@" Salve, Maria Salve, José Salve, Luis Carlos :~$ exit
E não é que deu!
Então, vamos colocar isso num script, desta vez, nome-args.sh
:
:~$ cat << 'FIM' >> nome-args.sh > #!/bin/bash > > printf 'Salve, %s!\n' "$@" > FIM :~$ chmod +x nome-args.sh :~$ ./nome-args.sh Maria José 'Luis Carlos' Salve, Maria! Salve, José! Salve, Luis Carlos!
Passando apenas um argumento…
:~$ ./nome-args.sh 'Maria Antonieta' Salve, Maria Antonieta!
Aí está, segundo problema solucionado!
Exercícios
1. Expansões do shell
Pesquise, analise e explique por que as expansões abaixo não funcionam.
Expansão de chaves:
:~$ echo {1..5} 1 2 3 4 5 ← Resultado esperado… :~$ var=5 :~$ echo {1..$var} {1..5} ← Não era isso que eu queria!
Expansão do til:
:~$ echo ~ /home/blau ← Resultado esperado… :~$ echo "Meu diretório é ~" Meu diretório é ~ ← Não era isso que eu queria!
Expansão de parâmetros:
:~$ var=banana :~$ echo $var-prata banana-prata ← Resultado esperado… :~$ echo $var_prata ← Não era isso que eu queria!
2. Variáveis exportadas
Escreva um comando que permita afirmar, visualmente, se as variáveis abaixo são exportadas ou não:
HOME
IFS
USER
PATH
PPID
BASH_VERSION
:~$
3. Parâmetros posicionais
Dado o comando abaixo…
:~$ set -- banana laranja abacate
Escreva comandos que exibam:
- O número total de argumentos;
- O nome utilizado na invocação do shell;
- O terceiro argumento passado para o shell;
- Todos os argumentos passados para o shell (um por linha).
:~$ :~$ :~$ :~$
4. Utilitários do sistema
Pesquise e descreva o que fazem os seguintes utilitários:
man -k
:cut
:file
:stat
:
5. Parâmetros especiais
Utilizando os parâmetros especiais, escreva comando que informem…
Se a sessão corrente do shell é interativa:
:~$
O PID do processo do shell corrente:
:~$
6. Comandos internos do Bash
Defina e elabore pelo menos dois exemplos de uso de cada um dos seguintes comandos internos do Bash:
export
:declare
:typeset
:unset
:shift
:
7. Fulano gosta de frutas, mas…
Escreva um script que imprima a mensagem: “NOME gosta de FRUTA, mas preferiu um DOCE”.
Exemplo de uso do script:
:~$ ./script.sh NOME FRUTA DOCE
8. Expansões de parâmetros
Pesquise e descubra uma forma para que o script anterior imprima “pudim”, caso o
terceiro argumento (DOCE
) não seja passado na linha do comando.
9. Mais expansões de parâmetros
Com a mesma solução encontrada para o exercício anterior, faça com que seja impressa a mensagem “Você gosta de banana, mas preferiu um pudim.”, caso não seja passado nenhum argumento na invocação do seu script.
10. Exportação de variáveis para o script
Crie uma versão do seu script para receber dados por exportação de variáveis em vez de argumentos.
Importante: ele terá que se comportar do modo que ficou após o exercício 9!