Neste post vou explicar um pouco de tudo o que eu sei sobre shell script. Eu comecei a usar shell script porque eu queria automatizar algumas tarefas minhas. E meu gosto pela linguagem começou pela minha fascinação. Eu lia alguns scripts, mas não entendia nada! Além disso eu percebia o potencial da linguagem, as coisas que eu poderia criar com aquilo.
Shell script é uma linguagem com uma sintaxe extranhíssima e muito diferente de qualquer outra linguagem que eu conhecia antes. Eu tinha experiência com linguagens como Python, Java, um pouco de C e outras. E estudando shell script eu aprendi muita coisa que eu não conhecia, e que pode ser aplicado em outras linguagens. Então depois de estudar, sinto que conheço mais sobre programação de forma geral. Estudar shell script também me permitiu conhecer melhor como funciona a arquitetura de programas e processos do Linux. Antes disso, eu não sabia o que era stdout
e stdin
por exemplo. E hoje eu percebo que esse é um conhecimento bem útil e que eu não tinha antes.
Apesar da sintaxe estranha, shell script é uma linguagem forte e capaz de muita coisa. Imagine o seguinte. Em seu computador você tem programas de todos os tipos. Alguns escritos em C, outros que foram escritos em Python, Java, Perl e etc. Agora imagine utilizar o poder de cada programa desses em coletivo para construir algo maior. É isso que você vai fazer. Usando shell script você irá orquestrar chamadas a esses programas, para cumprir um objetivo específico. Uau, isso é poderoso. É claro que você pode fazer isso em qualquer linguagem, mas shell script foi feito para isso!
Se você se interessou até aqui, continue lendo. Garanto que você vai aprender algo interessante. Porém, antes de começar, algumas sugestões:
- Se você nunca aprendeu programação antes, te sugiro fortemente, e enfáticamente que você não comece por shell script. Aprenda Python, C/C++, Java, PHP ou qualquer outra linguagem e depois volte aqui. Sério!
- E para seguir com o que eu vou explicar aqui, você deve estar um pouco acostumado a usar o terminal. Do contrário você vai ter um pouco de dificuldade.
Você irá perceber que muitas vezes eu vou ser breve e sucinto em certos assuntos. Principalmente aqueles que você provavelmente já viu em outras linguagens de programação. Outras vezes será porque não tive tempo de expandir e escrever da melhor forma possível. Se você perceber que eu não abordei algo importante, ou se algum dos exemplos está errado, ou se tem qualquer outra sugestão, por favor, deixe um comentário.
Vamos começar.
Sumário
- Diferentes Shells
- Hello World
- O básico
- Variáveis
- Substituição de comandos
- Condicionais
- Switch case
- Loops
- Argumentos
- Pipe e redireção
- Funções
- Matemática
- Manipulação de texto
- Conclusão
Diferentes Shells
Existem muitas Shells diferentes. Muitas mesmo! E cada uma delas tem uma linguagem de script diferente. Apesar disso a maioria das Shells apresentam algum nível de compatibilidade com a Bourne Shell (sh
), que foi uma das primeiras a existir. Algumas das Shells mais usadas são:
- sh (Bourne shell)
- bash (Bourne-Again shell)
- dash (Debian Almquist shell)
- fish (friendly interactive shell. minha favorita)
- zsh (Z shell. Muita gente gosta dessa para usar no dia-a-dia)
- ksh (Korn shell)
- csh (C shell)
- … ad infinitum.
A Shell mais popular provavelmente é o Bash. Se você usa Ubuntu e não sabe qual é a sua Shell, ela provavelmente é o Bash. Ainda assim, algumas pessoas preferem outras Shells para usar interativamente. Eu prefiro o Fish, por exemplo, e conheço muitas pessoas que preferem utilizar o Zsh.
Apesar de todas as variações, a maioria delas possuem um subconjunto de operações e de sintáxe iguais ao do sh
. Por isso vou ensinar sh
puro aqui. Variações como o bash
adicionam algumas coisas legais à linguagem, mas que não são compatíveis com as outras shells. Se você se restringir a sh
puro, seu código vai funcionar na maioria das outras shells. (Não todas. O fish
, por exemplo, não é compatível. Já sofri muito com isso. T.T)
No Ubuntu, quando você usa o sh
, na verdade você está usando o dash
. Mas… Whatever. O dash
foi feito apenas para ser um versão mais rápida e leve da Bourne Shell, e é totalmente compatível com a Bourne shell (até onde eu sei).
Hello World
Se você nunca mexeu com shell script antes, me acompanhe para fazer um Hello World. Abra o terminal (Ctrl+Shift+T
), e use seu editor de texto favorito para criar um novo arquivo arquivo de texto. Aqui eu vou usar o nano
.
1 | $ nano script.sh # Nano é um editor de texto, use qualquer um que você prefira |
Usando seu editor de texto, digite o seguinte código:
1 |
|
A primeira linha é um shebang (#!
) e identifica o tipo de script que estamos criando, vou explicar melhor sobre o shebang depois. E na segunda linha, echo
irá imprimir “Hello World”. Agora, salve e feche o editor de texto. (No nano
você usa Ctrl+O
(letra Ó) para salvar, e Ctrl+X
para sair.) Depois, digite o comando seguinte no mesmo terminal e você verá o Hello World
:
1 | $ sh script.sh |
Voilà! Agora você já sabe como criar e executar um shell script. Você já pode pegar seu certificado de “Shell Script Noob” na recepção e ir embora, ou continuar com o resto do guia para ganhar o certificado de “Shell Script Master”. ;)
O básico
No seu uso mais básico, shell script é usado para executar um comando após o outro. Igual que você estivesse usando o terminal, e digitando um comando após o outro. Mas colocando esses comandos em um script, você pode automatizar suas tarefas.
Por exemplo, depois de formatar meu computador eu tenho vários scripts que instalam algumas coisas que eu costumo usar no dia-a-dia, exemplo:
1 |
|
Quando eu executar o script acima, todos esses programas serão instalados pelo apt-get
. Isso me poupa o trabalho de ter que me lembrar e de ter que digitar manualmente cada um desses comandos.
Você também pode executar mais de um comando na mesma linha, separando os comandos por ;
. O ;
é opcional no final de uma linha.
1 | echo -n "Hello"; echo -n "World"; echo "!"; |
E a indentação também não importa.
1 | echo -n "Hello" |
Mas, por favor, indente seu código de forma intuitiva e organizada. Não é só porque você está usando a linguagem mais feia já inventada que você precisa escrever o código mais feio já inventado.
Shebang (#!)
No linux, é muito comum você colocar um shebang (#!
) na primeira linha de um script. Ele serve para que seu computador identifique qual programa roda aquele script. Ele só funciona se for a primeira linha do arquivo. Aqui estão alguns exemplos:
1 | #!/bin/sh |
Agora você já percebeu que ele toma o formato #!
+ <executável>
. Sendo que o executável é o programa que vai rodar o seu script. Se você não colocar um shebang no seu script, você vai precisar de executar ele da seguinte forma:
1 | $ sh script.sh |
Se você colocar o devido shebang você pode fazer:
1 | $ chmod +x script.sh # Apenas da primeira vez, para transformar o arquivo em executável |
Eu costumo colocar o shebang e rodar o script com sh script.sh
mesmo, porque ficar fazendo chmod +x
para cada script que eu crio é muito enjoado. Mas é uma boa prática colocar o shebang, então não deixe de colocar.
Comandos úteis
Alguns comandos você irá usar com mais frequência do que outros. É importante que você saiba que alguns deles existam, para consultá-los quando você precisar. Alguns deles são:
ls
: Lista os arquivos do diretório atual.cd
: Troca o diretório atual.rm
: Apaga um arquivo ou diretório.mv
: Move um arquivo ou diretório.cp
: Copia um arquivo ou diretório.echo
: Escreve um texto na tela.test
: Veremos na parte de condicionais.grep
: Imprime linhas que correspondem à um padrão.sed
: Modifica e filtra texto.tr
: Troca ou deleta caracteres.read
: Lê um texto digitado pelo usuário e salva numa variável.pwd
: Imprime o diretório local.find
: Busca arquivos.
Manuais
Quando você tiver dúvida sobre como um comando funciona, ou qual a sua interface, você deveria consultar sua página no manual: man <comando>
. Por exemplo, estou com dúvida no ls
, então eu digito man ls
.
Você deve se acostumar a ler os manuais. Acostume-se a encontrar os subcomandos existentes e as opções (e.g. --opcao
) que você precisa. E busque compreender mais ou menos a seção SYNOPSIS
das páginas dos manuais.
E tenha em mente que as páginas dos manuais podem ser diferentes dependendo da shell que você está usando. A maioria dos programas é a mesma coisa, porém alguns comandos como read
, por exemplo, podem funcionar um pouco diferente dependendo da Shell. Eu uso o fish
diáriamente, e, quando estou fazendo um script, vez ou outra, tenho que entrar no bash
só para consultar o manual de algum comando. Isso já me deu dor de cabeça algumas vezes. Se você usa o bash
, você não deve ter muitos problemas com isso, mas fica esperto.
Wildcards (Globs)
Globs são um tipo de Wildcard, e são usados para selecionar arquivos em sistemas Unix. São caracteres que representam uma sequência genérica de caracteres. ?
representa qualquer caractere. E *
, qualquer quantidade de qualquer caractere.
Vamos supor que você tem um diretório com os seguintes arquivos: script.pl
, script.sh
, script.pl
, script.test.sh
, test.c
e test.java
. Se você entrar nesse diretório e usar o comando rm test.*
, você irá remover os arquivos test.c
e test.java
, mas os outros arquivos ficarão intactos.
Isso funciona por uma expansão que ocorre antes mesmo do programa ser executado. No caso anterior, rm test.*
será expandido para rm test.c test.java
, e então o programa é invocado com estes dois argumentos.
Outro exemplo. Se usamos o comando mv *.txt textfiles/
moveremos todos os arquivos com a extensão .txt
para o diretório textfiles/
.
1 | rm script.* # Apaga arquivos tipo: 'script.py', 'script.sh', 'script.pl', e etc. |
Globs são muito úteis, e você deveria aprender a usar bem, pelo menos, o *
.
Variáveis
Chega daquelas discussões sobre o que é melhor: tipagem forte, ou tipagem fraca. Ao contrário da maioria das linguagens de programação, em que você tem vários tipos de variáveis (integer, string, boolean, etc), em shell script você tem apenas um tipo de variável… Strings!
Criar uma variável é bem simples:
1 | variavel="Conteúdo da variável" |
Importante: não coloque espaços ao redor do =
; não comece com números; não use hífen -
; não use caracteres especiais como ç
, á
, 火災
e nem emojis 😀
, 😂
.
1 | # Não use nomes que nem esses: |
Agora, na hora de usar as variáveis é que tem uma pegadinha. Você deve usar $
para acessar o valor de qualquer variável. Então a variável fruta=maçã
deve ser acessada como $fruta
.
1 | texto="Hello World" |
Quando você estiver escrevendo strings, você pode usar '
ou "
. Usando "
o valor das variáveis são colocados no lugar dos seus nomes. Usando '
o nome dá variável fica do jeito que está na string. O '
ignora a existência de variáveis.
1 | echo '$HOME' # Imprime: $HOME |
Para ler uma variável do stdin
(mais tarde eu explico sobre File Descriptors), ou seja, algum valor que o usuário tenha digitado, use o comando read
. Exemplo:
1 | echo -n "Digite seu nome: " |
Além das variáveis que você cria dentro do script, já existem algumas prontas para você usar. Nós chamamos elas de variáveis de ambiente (environment variables). Você pode abrir um terminal, digitar $
e apertar tab duas vezes, e uma lista de variáveis vai aparecer para você. Algumas das mais úteis são: $HOME
, $HOSTNAME
, $LANG
, $RANDOM
, $PWD
, $PATH
, $SHELL
, $USER
, $USERNAME
.
Você também pode passar variáveis de ambiente temporárias para um script, invocando ele como VARIAVEL1=whatever VARIAVEL2=whatever ./script.sh
.
Também é uma convenção usar nomes de variáveis em letras maiúsculas. E geralmente é uma ótima ideia seguir as convenções. Mas você vai perceber que eu não tô nem aí pra essa convenção especificamente… kkk. Isso porque eu acho que escrever o nome das minhas variáveis em minúsculo diferencia elas melhor das variáveis de ambiente.
Substituição de comandos
As vezes é útil guardarmos a saída de algum programa. Ao invés de imprimir na tela, gostaríamos de pegar esse valor e, por exemplo, guardar em uma variável. Para isso, usamos substituição de comandos: $(prog)
, ou `prog`
. Até onde eu sei, não há diferença entre as duas formas. Eu, particularmente, prefiro o segundo.
1 | echo `pwd` # Imprime o diretório atual |
Condicionais
Esse é o if
mais feio que você provavelmente vai escrever na sua vida. Mas vamos nessa.
1 |
|
Eu avisei.
No script acima, o usuário digita sua idade. Se ele tiver menos que 18 anos, imprimimos que ele é menor de idade. Caso contrário, imprimimos que ele é maior de idade.
Note que os espaços entre [
e ]
são necessários. Não use [$idade -lt 18]
, use [ $idade -lt 18 ]
. Se você não colocar os espaços, você terá um erro.
Agora vamos olhar como esse if
funciona de verdade. Na Bourne Shell, quando escrevemos [ $idade -lt 18 ]
, isso é a mesma coisa que test $idade -lt 18
. Na verdade, verdade verdadeira, ele usa um comando para avaliar a nossa expressão. Os valores $idade
, -lt
e 18
estão sendo passados como argumentos para o programa test
, e ele vai avaliar a expressão. Se a expressão for verdadeira, test
termina a execução com o valor 0
(exit 0
). Se a expressão for falsa, ele termina com um valor diferente (geralmente 1
).
Agora que você já sabe que o comando test
está sendo usado, você já pode consultar o manual para saber que tipos de condições você pode construir: man test
. O test
pode realizar vários tipos de comparações, aqui estão algumas operações possíveis.
1 | test 42 -eq 42 # eq: equal = os inteiros são iguais |
Podemos reescrever o condicional do código anterior usando o test
explicitamente. Dá na mesma coisa:
1 | if test $idade -lt 18; then |
Todo comando que é executado retorna algum valor. Se tudo ocorreu bem, retorna 0
. Se deu algum erro, retorna 1
ou outro valor. Em shell script, 0
significa verdadeiro, e os outros valores significam falso. E como eu disse, todo comando retorna um valor para a shell, inclusive o seu script! Você usa exit
para especificar que valor deve ser retornado. Se você não especificar, o retorno do último comando do seu script será usado.
E existe uma variável especial, chamada $?
que guarda o retorno do último comando executado. Pode ser útil, às vezes.
1 | if rm saci-pererê.txt; then echo "Saci foi apagado."; fi |
Switch case
Outra estrutura com uma sintaxe curiosa. Se você achava o if
estranho, é melhor se sentar. Surpreendentemente ele é mais útil do que parece, pela forma como ele trata as strings. Você pode usar wildcards (*
e ?
) para enriquecer as expressões usadas no switch.
1 |
|
Repare que você fecha o case
usando esac
(case
ao contrário). E não me pergunte porquê você tem que colocar ;;
no final de cada caso! Deixo isso como um dever de casa para você. Boa sorte.
Loops
Temos duas opções de loop aqui: while
e for
. Cada um é útil em uma situação específica.
For
Na sua forma mais simples, você descrimina uma série de valores para o for
, e a iteração acontecerá em cima desses valores. Veja o exemplo abaixo:
1 |
|
Abaixo está um exemplo levemente mais útil. O script apagará todos os arquivos do diretório atual, exceto o arquivo critico.txt
.
1 |
|
A variável $arquivos_txt
é uma string que contém o nome de todos os arquivos .txt
do diretório atual, separados por espaço. O for
irá quebrar essa string em múltiplos pedaços, separando pelos espaços e pelas quebras de linha (\n
). Esses múltiplos pedaços serão passados para a variável $arquivo
, um de cada vez.
Você também pode usar as palavras chaves continue
para pular uma iteração e continuar do começo, e break
para sair do loop.
Quando você desejar iterar sob uma sequência de números, você pode usar o comando seq
. Exemplo: seq 3
irá imprimir 1 2 3
(separados por \n
, na verdade) e seq 0 3
irá imprimir 0 1 2 3
. No exemplo abaixo, nós criamos uma sequência de 0 à 10, e elevamos cada um dos números ao quadrado, e imprimimos.
1 |
|
While
1 |
|
No exemplo, nós constantemente verificamos se o arquivo saci-pererê.txt
existe. Enquanto o arquivo não existir, nós continuamos no loop imprimindo uma mensagem. Assim que o arquivo é criado, o loop termina, e nós apagamos o arquivo do Saci.
Se você quiser trollar um amigo que usa Ubuntu, execute o script seguinte no computador dele. Coloque o arquivo em sua $HOME
com o nome de dull-boy.sh
(o nome é importante).
1 |
|
O script irá imprimir "All work and no play makes Jack a dull boy"
indeterminadamente. E a cada mil iterações, irá instanciar um novo terminal executando o mesmo script. Então seguindo uma curva exponencial, em pouco tempo você terá dezenas ou centenas de terminais na tela, todos imprimindo "All work and no play makes Jack a dull boy"
ininterruptamente.
Você pode tentar parar cada script com Ctrl+C
. Porém chega uma hora que a única solução razoável é esperar que o sistema operacional congele os processos, ou, mais fácil, reiniciar o computador.
Se você quiser trollar seu amigo em dobro, coloque uma linha cd $HOME; sh dull-boy.sh
no final do arquivo .bashrc
(se ele usa o Bash), assim o script irá executar toda vez que ele abrir o terminal. Mas cuidado, pois você pode não ter um amigo depois disso.
Argumentos
A maioria dos programas de linha de comando recebem e processam argumentos que são passados pelo usuário que está invocando o programa.
1 | $ comando arg1 arg2 arg3 arg4 |
E para acessarmos os argumentos, usamos as variáveis $1
, $2
, $3
e etc para acessarmos o primeiro argumento, o segundo argumento e daí em diante. $0
é o nome do seu script (até que faz sentido, né?). E $@
representa todos os argumentos juntos e em sequência (sem o $0
). E $#
é o número de argumentos recebidos (também sem o $0
).
1 | echo "\$0: $0" # Imprime o nome do script. |
Se o seu script tiver recebido apenas dois argumentos e você tentar acessar, digamos, o argumento $3
, o $3
será substituído por uma string vazia. Para evitar isso, você pode testar se $3
não é uma string vazia: test -n "$3"
(as aspas são importantes nesse caso).
Quando um argumento tem a forma -e
ou --exemplo
, ele é chamado de uma opção, e geralmente é… opcional na chamada de um programa. Você usou opções este tempo inteiro (echo -n
, rm -rf
, etc), deve saber como elas funcionam. Mas só para o caso de você não saber, vou explicar um pouquinho.
Algumas opções são chamadas de forma isolada, como --quiet
, e --help
, e outras devem ser acompanhadas de um valor como: --garrafas=12
, --garrafas 12
, -g12
. Ou: --arquivo="file.txt"
, --arquivo "file.txt"
. As opções podem ser usadas, não importa a ordem: cmd --input "i.txt" --output "o.txt"
deveria ser a mesma coisa que cmd --output "o.txt" --input "i.txt"
. E as opções geralmente são misturadas com argumentos: echo "hello" -n
(n
é uma opção e "hello"
, um argumento). E muitas opções que são escritas por extenso também tem uma forma abreviada, como: --help
é equivalente à -h
; e --quiet
é equivalente à -q
. Quando você usa a forma abreviada, muitas vezes você também pode aglutinar as formas abreviadas, por exemplo rm -rf
é equivalente à rm -r -f
.
É claro que nem todos os programas vão seguir essas regras para suas interfaces, mas essas são regras que você vai observar na maioria dos programas de linha de comando. Existem exceções, como exemplo, o programa java
não têm opções abreviadas e as opções extensas usam um único hífen, tipo: java -version
ou java -help
. (Para o caso de dúvida, programas feitos em Java podem ter qualquer interface que eles quiserem. Eu só quis dizer que o executável java
funciona dessa forma.)
E repare que apesar de --arquivo "file.txt"
ser uma única opção, na shell eles são visto como dois argumentos separados. Shell script não diferencia argumentos de opções por você. Para o shell script, tudo é argumento.
Se você quiser iterar sobre todos os argumentos, você pode usar um for
para isso:
1 |
|
Outra opção semelhante é usar o comando shift
juntamente com um while
. Quando você usa shift
, o $2
será colocado no lugar do $1
; o $3
no lugar do $2
; e assim em diante. Dessa forma você pode ler o primeiro argumento através do $1
; fazer um shift
, ler o segundo argumento através do $1
; shift
, ler terceiro argumento através do $1
também; e etc.
1 |
|
Se você for criar um script que tenha uma interface extensa e complexa, e que precisa diferenciar e tratar argumentos e opções, você terá que usar altos recursos imaginísticos para alcançar seus objetivos. Há algum tempo atrás eu tive que fazer isso, e tive sucesso usando um while
com um case
e o comando shift
. Se você precisar de inspiração, consulte o meu repositório “pokemonsay” no Github.
Pipe e redireção
Essa é provavelmente a coisa mais interessante que você pode fazer em shell script. É usando Pipes (literalmente, canos, ou tubos) e redireção que você vai conseguir libertar os verdadeiros poderes do shell script.
Antes de falar sobre isso, eu tenho que explicar uma coisa mais básica: File Descriptors. No mundo do Unix e Linux existe o que nós chamamos de “file descriptor”. Qualquer programa têm três file descriptors: Standard Input, Standard Output, e Standard Error. Comumente abreviados: stdin
, stdout
e stderr
. O programa irá ler dados do stdin
, irá escrever em stdout
, e irá escrever os erros para stderr
. Muitas vezes, o texto vindo de stdin
será o texto digitado pelo usuário no teclado. Mas muitas outras vezes, esse texto será recebido de forma programática.
Quando usamos pipe |
, nós estamos conectando o stdout
do comando à esquerda, com o stdin
do comando à direita. Também é possível fazer vários pipes em sequência. Você deve imaginar que o texto está fluindo da esquerda para a direita, e que cada comando está modificando o texto, ou agindo de alguma forma sobre ele. Vamos à um exemplo simples:
1 | # Imprime "laranja_123" (minúsculo) |
O tr
é um programa que troca alguns caracteres por outros (man tr
). Perceba que o echo
escreveu o texto em seu stdout
(normalmente seria impresso, mas não foi devido ao pipe), e seu stdout
foi redirecionado para o stdin
de tr
. E o tr
, após manipular o texto (substituir maiúsculas por minúsculas), escreveu o resultado em seu stdout
, que por sua vez foi impresso na tela.
1 | # Imprime "laranja" |
Mais um pipe agora. Dessa vez, o stdout
do primeiro tr
não é impresso, mas é redirecionado para o stdin
do segundo tr
. O segundo tr
irá ler o texto de seu stdin
, modificá-lo (remover os números e underline _
), e escrever em seu stdout
. Como não há mais nenhuma redireção, seu stdout
será impresso.
Também podemos fazer redireções usando arquivos. >
é usado para redirecionar o stdout
para um arquivo, porém apaga o conteúdo do arquivo se ele já existir. >>
faz o mesmo que >
, porém não apaga o conteúdo original do arquivo. Ao final do script abaixo, teremos um arquivo com três frutas: Caju, Mamão e Pêra.
1 | # Escreve "Banana" no arquivo "frutas.txt". |
Equivalente ao >
temos o <
que faz exatamente o contrário. Ele serve para “puxarmos” o texto de um arquivo e fornecer como entrada para o stdin
de um programa. Exemplo:
1 | # Transforma o texto de 'frutas.txt' para maiúsculas. |
O texto contido em frutas.txt
será direcionado para o stdin
de tr
, que irá modificar o texto e imprimir na tela.
Também é comum usarmos <
, >
e |
tudo junto. É um pouco difícil de se acostumar com a leitura. Mas é algo comum e útil. Veja o exemplo abaixo.
1 | # Transforma o texto de 'frutas.txt' para maiúsculas, remove números e '_' |
Pêra! (huehue) Se existe >
e >>
, deve existir também <<
, já que existe <
. Sim, senhor. E o nome disso é “Here Document”. Ao invés de ler de um arquivo (como <
) o texto será lido do próprio script.
1 | tr '[:lower:]áãçó' '[:upper:]ÁÃÇÓ' << EOF |
Perceba que depois do <<
temos um token (EOF
) que abre o texto do lerolero.com, e em seguida o mesmo token deverá ser repetido em sua própria linha, para fechar o texto. O texto que está entre os dois EOF
(end of file, fim de arquivo), será usado como entrada de dados para o tr
, que por usa vez imprimirá o texto inteiro em letras maiúsculas. É comum usarmos a sigla EOF
como token, mas pode ser qualquer palavra, como LEROLERO
, ou HELLO_WORLD!
.
Agora um pequeno exercício mental. Tente entender que parte de texto está servindo de entrada para qual comando. Boa sorte.
1 |
|
Cada file descriptor tem um número associado: stdin
, 0
; stdout
, 1
; e stderr
, 2
. É comum redirecionarmos o stderr
de um programa para o stdout
do mesmo programa. Fazemos isso usando 2>&1
. Isso é muito útil quando temos um programa que escreve coisas importantes para stderr
, porém nós queremos gravar em um arquivo, por exemplo. Para isso fazemos prog 2>&1 > meu.log
. Ou ainda podemos gravar o stdout
e o stderr
em diferentes arquivos: prog 1> meu.log 2> erros.log
.
E se quisermos direcionar o stdout
para stderr
, usamos 1>&2
. Você pode usar isso para escrever em stderr
no seu script através de echo 1>&2
.
1 | echo "Hello" # Imprime através do `stdout` |
Redireções também funcionam com estruturas como for
e while
. Quando você chega nesse nível, as coisas podem ficar extremamente confusas. O exemplo abaixo, lê as linhas de um arquivo lower.txt
, colocando cada uma delas na variável $linha
, que é “echo
ada” para tr
, que transforma tudo em maiúsculas. Porém o stdout
de tr
vai para um segundo tr
que apaga as vogais do texto. E em seguida, o resultado é escrito em UPPER.txt
. Loucura.
1 |
|
Era possível escrever o script acima de forma mais simples. Mas eu quis fazer assim, para você exercitar seu poderoso cérebro.
Funções
Funções funcionam como mini-scripts contidos no seu script. Elas são declaradas como foo() { ... }
e são invocadas como qualquer outro comando: foo arg1 arg2 arg3 ...
.
1 |
|
Perceba que dentro da função $1
e $2
são argumentos recebidos pela função, e não pelo script. Do lado de fora da função, nós estamos usando o $1
que é o primeiro argumento do nosso script. Veja que as duas coisas não se misturam.
Cuidado: as funções podem alterar variáveis do escopo global:
1 | troll() { |
Eu acho que isso é o que tem de mais importante para falar sobre as funções em shell script. Acho que você deve saber o que fazer a partir daqui. Mas me sinto culpado de não colocar um exemplo um pouco mais complexo. Então abaixo está uma função que calcula o fatorial de um número…
1 | fatorial() { |
Matemática
De vez em quando precisamos fazer uma conta ou outra em shell script. A forma como fazemos isso é usando qualquer comando que faça contas. Alguns dos mais úteis são expr
e bc
.
O mais básico é o expr
. Ele serve para fazer contas simples, mas deixa a desejar para contas mais complexas e de número flutuante. E ele é um pouco chato quanto aos espaços. Você precisa separar cada um dos números e operadores, pois eles devem ser recebidos como diferentes argumentos. E você precisa ter cuidado com o *
de multiplicação, para que ele não seja interpretado como um wildcard antes mesmo de ser recebido pelo expr
, então use \*
ou '*'
. O mesmo vale para os parênteses, use \( ... \)
, ou '(' ... ')'
1 | expr 2 + 2 # "4" |
Como você pode ver, expr
apenas gosta de números inteiros. Além disso, expressões mais complexas ficam extremamente longas, já que você tem que colocar espaços ao redor de tudo.
Para contas um pouco mais complexas, ou quando você quiser usar números decimais, recomendo usar o bc
. Porém este programa possui outro inconveniente: você tem que passar as contas para ele por redirecionamento, pois ele não processa contas pelos argumentos. E para contas com muitas casas decimais, use bc -l
.
1 | echo "2 + 2" | bc # "4" |
Eu nunca usei muito o bc
, mas parece que ele é capaz de fazer bem mais. Se você precisar de expressões matemáticas complexas, dê uma olhada na sua documentação com carinho.
Manipulação de texto
Umas das coisas mais comuns que você vai fazer em shell script é manipular texto. Por isso é bom que você saiba fazer isso bem. Minha sugestão é que você aprenda bem, um dos seguintes programas: sed
, awk
ou perl
. Eu costumo usar o sed
. Porém, explicar como ele funciona é um tutorial à parte. Mas veja algumas coisas básica que você pode fazer com o sed
.
1 | # Imprime todo o texto recebido, porém substituindo "banana" por "maçã" |
Infelizmente não tem como eu explicar aqui com detalhes como funciona o sed
. Mas, pelo menos, o primeiro exemplo você deve ter entendido. Eu, pessoalmente, aprendi o que sei de sed
(30% do total, talvez) usando uma página na internet que parecia ter sido feita no período jurássico. Sinta-se livre para buscar qualquer fonte que possa te ajudar.
Conclusão
Essa é a despedida. Depois de tudo isso, eu agora espero que você consiga usar shell script para resolver seus problemas. Se ficou faltando alguma coisa, ou não deu para entender alguma parte, pode postar o seu feedback aqui nos comentários.
É isso aí. Abraço.