Ubuntu Laços

Um laço (em inglês, loop) repete comandos. É ele que transforma "renomear uma foto" em "renomear as 300 fotos da pasta". O Bash tem três laços: for, while e until.

Você vai aprender
  • A estrutura do for, parte por parte, e cinco jeitos de montar a lista que ele percorre.
  • Como percorrer arquivos com nomes que têm espaço sem quebrar nada.
  • Quando usar while e until, e como ler um arquivo linha por linha.
  • Como interromper (break), pular (continue) e escapar de um laço infinito.

Antes, leia Variáveis e Argumentos e Condicionais. Os exemplos rodam na máquina fictícia notebook, com a usuária ana.


A ideia: "para cada item, faça"

Pense numa lista de compras. Você pega o primeiro item, coloca no carrinho, pega o próximo, coloca no carrinho… até a lista acabar. O for faz isso com qualquer lista de palavras.

for VARIÁVEL in LISTA; do
  comandos que usam $VARIÁVEL
done
for fruta
"Para cada…": cria a variável fruta, que vai receber um item por vez.
in banana maçã uva
A lista. Cada palavra separada por espaço é um item.
; do
"Faça": começa o bloco que será repetido.
echo "Eu gosto de $fruta"
O comando repetido. $fruta vale "banana", depois "maçã", depois "uva".
; done
"Feito": fim do bloco.
$ for fruta in banana maçã uva; do echo "Eu gosto de $fruta"; done
Eu gosto de banana                                  « 1ª volta: fruta=banana
Eu gosto de maçã                                    « 2ª volta: fruta=maçã
Eu gosto de uva                                     « 3ª volta: fruta=uva

Cada repetição se chama volta ou iteração. Quando a lista acaba, o Bash segue para o que vem depois do done.

Em scripts, escrevemos o mesmo laço em várias linhas, com o bloco recuado:

#!/usr/bin/env bash
for fruta in banana maçã uva; do
  echo "Eu gosto de $fruta"
done

Cinco formas de montar a lista do for

1. Palavras escritas à mão

$ for servidor in web1 web2 banco; do echo "Verificando $servidor"; done
Verificando web1
Verificando web2
Verificando banco

2. Sequência com chaves {1..5}

As chaves geram a lista antes do laço começar (veja touch e mkdir):

$ for i in {1..5}; do echo "Volta $i"; done
Volta 1
Volta 2
Volta 3
Volta 4
Volta 5

3. Arquivos com curinga *

É o uso mais comum. O *.jpg vira a lista de arquivos que terminam em .jpg (veja Curingas):

$ ls fotos
 festa.jpg  'minha foto.jpg'   praia.jpg   serra.jpg
$ for f in fotos/*.jpg; do echo "Achei: $f"; done
Achei: fotos/festa.jpg
Achei: fotos/minha foto.jpg                         « o espaço não atrapalhou
Achei: fotos/praia.jpg
Achei: fotos/serra.jpg

4. A saída de um comando $(…)

$ for n in $(seq 1 3); do echo "linha $n"; done
linha 1
linha 2
linha 3

Cuidado: $(…) quebra a saída em cada espaço. Funciona bem com números e palavras simples, mas não com nomes de arquivo. Veja "Quando não funciona", mais abaixo.

5. Estilo C: contador com início, condição e passo

for ((
Abre o laço em estilo C (parênteses duplos).
i = 0;
Início: a variável começa em 0.
i < 10;
Condição: continua enquanto i for menor que 10.
i += 3
Passo: soma 3 depois de cada volta.
)); do … done
Fecha os parênteses e define o bloco.
$ for ((i = 0; i < 10; i += 3)); do echo "i vale $i"; done
i vale 0
i vale 3
i vale 6
i vale 9                                            « 12 não é menor que 10: o laço para

Missão: use um for para criar os arquivos nota1.txt até nota5.txt. Dica: for i in {1..5}; do touch "nota$i.txt"; done. Depois rode for f in *.txt; do echo "arquivo: $f"; done e veja o laço percorrer os arquivos que você criou.


Exemplo prático: renomear várias fotos

A câmera salvou as fotos como .jpeg e você quer .jpg. Uma por uma, seriam dezenas de mv. Com um laço, é um script de 4 linhas:

#!/usr/bin/env bash
# Troca a extensão .jpeg por .jpg em todos os arquivos da pasta fotos

for arquivo in fotos/*.jpeg; do
  novo="${arquivo%.jpeg}.jpg"
  mv -v "$arquivo" "$novo"
done
for arquivo in fotos/*.jpeg
Percorre cada arquivo .jpeg da pasta.
novo="${arquivo%.jpeg}.jpg"
${arquivo%.jpeg} é o nome sem o final ".jpeg"; depois colamos ".jpg".
mv -v "$arquivo" "$novo"
Renomeia e mostra o que fez. As aspas protegem nomes com espaço.
$ ./renomear.sh
renomeado 'fotos/festa.jpeg' -> 'fotos/festa.jpg'
renomeado 'fotos/minha foto.jpeg' -> 'fotos/minha foto.jpg'
renomeado 'fotos/praia.jpeg' -> 'fotos/praia.jpg'
renomeado 'fotos/serra.jpeg' -> 'fotos/serra.jpg'
Teste antes de fazer de verdade

Troque o mv -v por echo mv na primeira vez. O laço só mostra o que faria, sem mexer em nada. Se a lista estiver certa, volte o mv.


while: repetir enquanto for verdade

O while ("enquanto") não percorre uma lista: ele repete enquanto um comando ou teste der certo. Use quando você não sabe de antemão quantas voltas serão.

while CONDIÇÃO; do
  comandos
done
#!/usr/bin/env bash
n=3
while [ "$n" -gt 0 ]; do
  echo "Faltam $n..."
  n=$((n - 1))
  sleep 1
done
echo "Pronto!"
while [ "$n" -gt 0 ]
Enquanto n for maior que 0…
n=$((n - 1))
…diminui 1. Sem essa linha, o laço nunca terminaria.
sleep 1
Espera 1 segundo entre as voltas.
$ ./contagem.sh
Faltam 3...
Faltam 2...
Faltam 1...
Pronto!

Ler um arquivo linha por linha

Este é o uso mais importante do while. O read lê uma linha por vez, e o laço acaba quando o arquivo termina.

#!/usr/bin/env bash
numero=1
while IFS= read -r linha; do
  echo "$numero: $linha"
  numero=$((numero + 1))
done < frutas.txt
IFS=
Não corta os espaços do começo e do fim da linha.
read -r linha
Lê uma linha para a variável linha. O -r não trata a barra \ como especial.
done < frutas.txt
O arquivo entra no laço inteiro pelo < (veja Redirecionamento).
$ cat frutas.txt
banana
maçã
laranja
$ ./ler.sh
1: banana
2: maçã
3: laranja

until: repetir até ficar verdade

until ("até que") é o contrário do while: repete enquanto a condição for falsa.

$ n=1; until [ $n -gt 3 ]; do echo "n=$n"; n=$((n+1)); done
n=1
n=2
n=3

Um uso real: until ping -c 1 192.168.0.1 > /dev/null; do sleep 5; done; echo "A rede voltou" espera a rede voltar.


Controlar o laço: break e continue

ComandoO que faz
breakSai do laço na hora ("quebra" o laço).
continuePula o resto desta volta e vai para a próxima.
$ for i in {1..10}; do if [ $i -eq 4 ]; then break; fi; echo $i; done
1
2
3                                                   « no 4, o break encerrou o laço
$ for i in {1..5}; do if [ $i -eq 3 ]; then continue; fi; echo $i; done
1
2
4                                                   « o 3 foi pulado
5

Laço infinito (de propósito)

while true nunca termina sozinho. É útil para monitorar algo de tempos em tempos. Para parar, aperte Ctrl+C.

$ while true; do date +%T; sleep 2; done
10:30:00
10:30:02
10:30:04
^C                                                  « Ctrl+C interrompe

Formas diferentes de processar vários arquivos

Tarefa: mostrar o nome de cada .jpg, inclusive em subpastas, sem quebrar nomes com espaço.

FormaExemploMelhor quando…
for com curingafor f in fotos/*.jpg; do …; doneOs arquivos estão numa pasta só. É a forma mais simples e segura.
for com **shopt -s globstar; for f in **/*.jpg; do …; doneIncluir subpastas, ainda com for.
find + while readfind . -name "*.jpg" -print0 | while IFS= read -r -d "" f; do …; doneMuitas pastas e filtros do find (data, tamanho).
find -execfind . -name "*.jpg" -exec echo {} \;Um comando só por arquivo, sem laço (veja find).
xargsfind . -name "*.jpg" -print0 | xargs -0 ls -lPassar muitos arquivos de uma vez a um comando (veja Pipes e xargs).
$ find fotos -name "*.jpg" -print0 | while IFS= read -r -d "" f; do echo "-> $f"; done
-> fotos/serra.jpg
-> fotos/praia.jpg
-> fotos/minha foto.jpg
-> fotos/festa.jpg

O find não ordena: a ordem depende de como os arquivos estão gravados no disco.


Quando não funciona

Nomes com espaço viraram dois itens
$ for f in $(ls fotos); do echo "[$f]"; done
[festa.jpg]
[minha]                                             « "minha foto.jpg" foi partido
[foto.jpg]
[praia.jpg]
[serra.jpg]

O que aconteceu: $(ls …) devolve um texto, e o Bash corta esse texto em cada espaço.

Como resolver: nunca use $(ls) num laço. Use o curinga direto: for f in fotos/*; do echo "[$f]"; done. E sempre "$f" entre aspas dentro do laço.

O laço rodou uma vez com "*.pdf"
$ for f in *.pdf; do echo "Achei: $f"; done
Achei: *.pdf

O que aconteceu: não havia nenhum PDF. Quando o curinga não encontra nada, o Bash entrega o texto *.pdf como está, e o laço roda uma vez com ele.

Como resolver: no começo do script, ative shopt -s nullglob (curinga sem resultado vira lista vazia). Outra opção é testar dentro do laço: [ -e "$f" ] || continue.

A última linha do arquivo sumiu
$ printf "a\nb" > semfim.txt
$ while read -r l; do echo "[$l]"; done < semfim.txt
[a]

O que aconteceu: a última linha não termina com "Enter" (quebra de linha). O read leu o "b", mas devolveu código 1, e o while parou antes de usá-lo. Acontece com arquivos criados no Windows ou em alguns editores.

Como resolver: while read -r l || [ -n "$l" ]; do …; done < arquivo processa também essa última linha.

A variável "perdeu" o valor depois do laço
$ cat frutas.txt | while read -r f; do total=$((total+1)); done; echo "total=$total"
total=

O que aconteceu: com |, o while roda num processo separado (um "subshell"). O total que ele contou some quando o laço termina.

Como resolver: mande o arquivo com < em vez de cat |:

$ total=0; while read -r f; do total=$((total+1)); done < frutas.txt; echo "total=$total"
total=3
"erro de sintaxe próximo ao token inesperado `done'"
$ for i in 1 2 3 do echo $i; done
bash: erro de sintaxe próximo ao token inesperado `done'

O que aconteceu: faltou o ; antes do do. O Bash achou que "do" e "echo" eram mais itens da lista.

Como resolver: for i in 1 2 3; do echo $i; done.

O laço não para

O que aconteceu: num while, a condição nunca fica falsa, normalmente porque você esqueceu de mudar a variável (o n=$((n - 1)) do exemplo).

Como resolver: Ctrl+C interrompe. Se o laço estiver rodando em segundo plano, encontre e encerre o processo (veja Encerrar Programas).


Referência rápida

EstruturaSintaxe
for em listafor x in a b c; do …; done
for em sequênciafor i in {1..10}; do …; done
for em arquivosfor f in *.txt; do …; done
for estilo Cfor ((i=0; i<10; i++)); do …; done
whilewhile COND; do …; done
Ler linhaswhile IFS= read -r l; do …; done < arq
untiluntil COND; do …; done
Infinitowhile true; do …; done
Sair / pularbreak / continue
Curinga vazioshopt -s nullglob

Exercícios

1. Completar o for

Complete para mostrar os números de 1 a 10:

for i in ; do echo $i; done

2. Palavras que faltam

Complete as duas palavras-chave do laço:

for f in *.log; gzip "$f";

3. Ler linha por linha

Complete para ler lista.txt linha por linha:

while IFS= read -r linha; do echo "$linha"; done lista.txt

4. Por que for f in $(ls) é perigoso?

5. O que o continue faz dentro de um laço?


Resumo

  • for percorre uma lista: palavras, {1..10}, arquivos com * ou a saída de $(…).
  • Para arquivos, use o curinga direto e "$f" entre aspas; nunca $(ls).
  • while repete enquanto der certo; while IFS= read -r l … < arquivo lê linha por linha.
  • until repete até dar certo; break sai, continue pula.
  • Ctrl+C interrompe um laço que não para.