Um shell é um programa que permite que você digite comandos para interagir com o sistema operacional. Os shells podem ser gráficos; o ambiente de área de trabalho que você usa com o mouse ou o trackpad é um shell. Vamos focar nas interfaces de linha de comando, em que você digita comandos e os resultados são (normalmente) impressos no seu terminal.
Existem muitos shells de linha de comando diferentes. O Windows oferece o cmd e o powershell. O Mac e o Linux oferecem os "shells Unix", dos quais existem muitos. O shell padrão do Mac é o zsh. O csh é um shell legado, com sintaxe parecida com a de C. O bash é um dos shells mais populares, principalmente no Linux.
Um dos primeiros shells que existiram é o Bourne shell, o sh. Com o tempo, esse shell foi padronizado para estar disponível em todo sistema do tipo Unix. Muitos shells são baseados no Bourne shell e costumam oferecer recursos interativos ou de programação adicionais.
O Bash é baseado no Bourne shell. É por isso que ele é chamado de "Bourne-again shell". Ele é o shell padrão em muitos sistemas Linux. O Bash oferece muitos recursos que fazem dele um shell interativo agradável, incluindo:
Esta trilha não é sobre como usar o bash como shell interativo. Ela é sobre aprender a escrever programas usando bash. A ênfase será usar só o bash para resolver problemas, embora não haja restrições a chamar ferramentas externas.
O Bash lê comandos da sua entrada (que, normalmente, é um terminal ou um arquivo). Cada linha de entrada que ele lê é tratada como um comando, ou seja, uma instrução a ser executada. (Existem alguns casos avançados, como comandos que ocupam várias linhas, que serão discutidos mais adiante.)
O Bash divide cada linha em palavras delimitadas por um caractere de espaço em branco (espaços e tabulações). A primeira palavra da linha é o nome do comando a ser executado. Todas as palavras restantes se tornam argumentos desse comando (opções, nomes de arquivos etc.).
Nos exemplos abaixo, vamos mostrar alguns comandos interativos que você pode executar.
# inicia um comentário.
Quaisquer palavras depois do comentário são ignoradas pelo shell; o comentário serve apenas para leitura.$ indica um prompt do shell.
Se você estiver copiando o comando, não inclua o prompt.# Note: It is always a good idea to run `ls` before `rm`
# to make sure you know what you are deleting.
$ rm * # Remove all files in the current directory.
# Make sure you run this in a temp directory or you can lose files!
$ ls # List files in the current directory (no output, no files).
$ touch a b c # Create files 'a', 'b' and 'c'.
$ ls # List files again; this time the outputs: 'a', 'b' and 'c'.
a b c
O rm é um aplicativo que remove os arquivos que recebe.
O * é um caractere glob, um padrão "curinga" que corresponde a todos os arquivos do diretório atual.
Vamos falar mais sobre globs mais adiante.
Você reparou que há vários espaços entre a e b, e apenas um entre b e c?
A quantidade de espaços em branco entre os argumentos não importa!
Por exemplo:
$ echo This is a test.
This is a test.
$ echo This is a test.
This is a test.
O echo é um comando que imprime seus argumentos na saída padrão (que, no nosso caso, é o terminal).
O Bash divide o comando em cinco palavras e invoca o comando echo com quatro argumentos.
Os espaços extras no segundo exemplo não fazem diferença.
O echo não sabe como os espaços estavam distribuídos originalmente entre as palavras.
Se quisermos manter os espaços extras, precisamos passar a frase como um único argumento. Podemos fazer isso usando aspas:
$ echo "This is a test."
This is a test.
As aspas agrupam tudo o que está dentro delas em um único argumento.
O argumento é This is a test., incluindo esses espaços específicos.
Repare que as aspas não fazem parte do argumento: o bash as remove antes de entregar esse único argumento ao echo.
Os argumentos são separados do nome do comando e uns dos outros por espaço em branco. É importante lembrar disso. Por exemplo, o seguinte está errado:
$ [-f file]
bash: [-f: command not found
A intenção aqui é testar a existência de um arquivo chamado file.
É razoável supor que os espaços em branco em volta de [ e ] não importam, mas [ na verdade é um comando, e ele exige que seu último argumento seja ].
(Vamos abordar o comando [ com mais detalhes mais adiante.)
Portanto, precisamos separar [ de -f e ] de file.
Caso contrário, o bash vai achar que estamos tentando executar um comando chamado [-f com um único argumento file].
O comando correto separa todos os argumentos com espaço em branco:
$ [ -f file ]
(Vemos muita gente confusa com esse comportamento; acham que [...] é "só sintaxe" e que podem omitir os espaços; [ é um comando e o espaço em branco é obrigatório.)
Se o nome do nosso arquivo contiver espaços em branco ou outros caracteres especiais, ele também deve vir entre aspas:
$ [ -f "my file" ]
O Bash entende vários tipos diferentes de comandos: aliases, funções, builtins, palavras-chave e executáveis. Todos eles são comandos, e todos se comportam de maneira muito parecida.
Por padrão, os aliases só ficam ativados em shells interativos, não em scripts de shell. Não vamos abordá-los aqui.
Uma função contém comandos de shell e se comporta de maneira muito parecida com um pequeno script; ela pode até receber argumentos e criar variáveis locais. Quando uma função é chamada, os comandos dentro dela são executados. As funções serão abordadas em profundidade mais adiante.
O Bash tem alguns comandos básicos embutidos, como: cd (mudar de diretório), echo (escrever saída) e assim por diante.
Eles podem ser vistos como funções que já vêm prontas.
As palavras-chave são como os builtins, com a principal diferença de que as palavras-chave são, na verdade, sintaxe do bash e podem ser interpretadas segundo regras especiais.
Por exemplo, [ é um builtin do bash, enquanto [[ é uma palavra-chave do bash; ambos são usados para testar uma série de condições.
Mas a palavra-chave [[ tem um pouco mais de funcionalidade.
Por exemplo, ela oferece o operador =~, que faz teste de expressão regular.
$ [[ "hello world" =~ e.*o ]] # returns a "success" status
$ [ "hello world" =~ e.*o ]
bash: [: =~: binary operator expected
Os operadores de comparação de string < e > são outro exemplo de como as palavras-chave interpretam o código de forma diferente.
Dentro de [[...]], esses operadores não precisam de nenhum cuidado especial.
$ if [[ "$USER" < "n" ]]; then
echo "$USER is in the first half of the alphabet"
fi
# => glennj is in the first half of the alphabet
Mas dentro de [...] você precisa colocá-los entre aspas ou escapá-los para impedir que o shell os interprete como operadores de redirecionamento.
$ if [ "$USER" < "n" ]; then
echo "$USER is in the first half of the alphabet"
fi
# => bash: n: No such file or directory
$ if [ "$USER" \< "n" ]; then
echo "$USER is in the first half of the alphabet"
fi
# => glennj is in the first half of the alphabet
O último tipo de comando que o bash pode executar é um executável.
São arquivos executáveis (programas) no sistema de arquivos do seu computador que você pode rodar.
(Os executáveis também podem ser chamados de comandos externos ou aplicativos.)
Os executáveis costumam ser invocados digitando apenas o nome deles; normalmente não é preciso informar o caminho completo do arquivo.
Isso é possível porque o bash usa uma variável predefinida que o ajuda a encontrar a localização dos arquivos executáveis comuns.
Essa variável se chama PATH.
Ela é um conjunto de nomes de diretórios separados por dois-pontos (por exemplo, /usr/bin:/bin).
Quando um comando é especificado (por exemplo, myprogram ou ls) sem um caminho de arquivo (e não é um alias, uma função, um builtin ou uma palavra-chave), o bash procura nos diretórios de PATH.
Os diretórios são pesquisados em ordem, da esquerda para a direita, para ver se contêm um executável com o nome informado.
Se o executável estiver fora de um caminho conhecido, será preciso especificar o caminho do arquivo do executável.
Para o executável myprogram no diretório atual, use ./myprogram; se ele estiver no diretório /opt/somedirectory, use /opt/somedirectory/myprogram.
Dica: o comando type pode ser usado para obter detalhes sobre o comando:
$ type rm
rm is hashed (/bin/rm)
# or it might return: `rm is /bin/rm`
$ type cd
cd is a shell builtin
Um script é uma sequência de comandos guardada em um arquivo. O Bash lê o arquivo e processa os comandos em ordem. Ele só passa para o próximo comando quando o atual termina. A exceção é quando um comando foi especificado para rodar de forma assíncrona (em segundo plano). Não se preocupe muito com esse caso por enquanto; vamos aprender como isso funciona mais adiante.
Qualquer exemplo de linha de comando deste guia também pode ser usado em um script.
Criar um script é fácil. Comece criando um novo arquivo e coloque isto na primeira linha:
#!/bin/bash
Essa linha é chamada de diretiva de intérprete (também é chamada de hashbang ou shebang).
Ela especifica que /bin/bash deve ser usado como intérprete quando o arquivo for usado como executável em um comando.
Quando o kernel do sistema operacional executa um arquivo não binário, ele lê a primeira linha do arquivo.
Se a linha começar com #!, o kernel usa a linha para determinar para qual intérprete repassar o arquivo.
(Existem outras formas válidas de fazer isso, veja abaixo.)
O #! precisa estar no início absoluto do arquivo, sem espaços nem linhas em branco antes dele.
Os comandos do nosso script aparecerão em linhas separadas abaixo disso.
Dica: em vez de #!/bin/bash , você pode usar
#!/usr/bin/env bash
O env procura em $PATH o executável indicado pelo seu primeiro argumento (nesse caso, "bash").
Para uma explicação mais detalhada dessa técnica e de como ela difere do bom e velho #!/bin/bash, veja esta pergunta no StackOverflow.
Não se deixe enganar por scripts ou exemplos na internet que usam /bin/sh como intérprete.
sh não é bash!
O próprio Bash é um shell "compatível com sh", o que significa que ele consegue rodar a maioria dos scripts sh e carrega boa parte da mesma sintaxe.
No entanto, o contrário não é verdade; alguns recursos do bash quebram ou causam comportamento inesperado no sh.
Na verdade, não existe um único shell sh.
Existem muitas implementações de shell que suportam a sintaxe POSIX-sh.
Dependendo do seu sistema operacional, quando você roda sh pode estar executando, na verdade, o bash, o ash, o dash ou algum outro shell.
Depois que o arquivo do script for criado, ele pode ser executado assim:
$ bash myscript
Neste exemplo, executamos o comando bash e dizemos a ele para ler o script que passamos como argumento.
Quando fazemos assim, a linha #! é apenas um comentário.
Como alternativa, podemos dar permissões de execução aos nossos scripts. Com esse método, podemos executar o script como qualquer outro executável, em vez de chamar o bash manualmente:
$ chmod +x myscript # Mark the file as executable.
$ ./myscript # Now, myscript can be executed directly
# instead of having to specify bash.
Quando rodamos o comando dessa forma, o sistema operacional (SO) usa a linha #! para determinar qual intérprete usar para rodar o seu script.
O conteúdo desta lição foi retirado de [Comandos e argumentos], no Bash Guide.