Ci

Ciclos em Bash

{one: "1 exercício", many: "%{count} exercícios", other: "%{count} exercícios"}

Sobre Ciclos

O Bash tem duas formas de fazer ciclos:

  • Repetir alguns comandos para cada item de uma lista de itens,
  • Repetir alguns comandos enquanto uma condição for verdadeira (ou falsa).

Ciclos for

Para iterar sobre uma lista de itens, usamos o ciclo for.

for varname in words; do COMMANDS; done

O words é expandido através da divisão de palavras e da expansão de nomes de ficheiros (de que falámos no conceito de quoting). Cada palavra é atribuída, por sua vez, à varname, e os COMMANDS são executados.

Eis alguns exemplos que mostram quando o ciclo for pode ser útil:

  • Executar alguns comandos sobre um grupo de ficheiros.

    # source all the bash library files in the current directory
    for file in ./*.bash; do
        source "$file"
    done
    
  • Executar um comando sobre cada um dos parâmetros de um script ou de uma função.

    for arg in "$@"; do
        echo "argument: ${arg}"
    done
    
  • Executar um comando para cada palavra separada por espaços na saída de um comando.

    for i in $(seq 10); do
        echo "${i} squared is $((i * i))"
    done
    

    Vamos ver alguns avisos sobre este estilo mais à frente.

Ciclo for aritmético

O Bash tem, de facto, um ciclo aritmético. Isto parecerá de certo modo familiar a quem conhece outras linguagens de programação.

for ((INITIALIZATION; CONDITION; INCREMENT)); do COMMANDS; done

Os parênteses duplos no Bash representam um contexto aritmético (veremos mais sobre aritmética do Bash num conceito posterior). O exemplo acima com o seq pode escrever-se assim:

for ((i = 1; i <= 10; i++)); do
    echo "${i} squared is $((i * i))"
done

Quando não usar o for

Não leias linhas com o for.

Este é um antipadrão que vais ver muitas vezes: iterar sobre a saída do cat.

for line in $(cat some.file); do 
    do_something_with "$line"
done

Isto está errado por dois motivos.

  1. A substituição de comandos não está entre aspas, por isso está sujeita à divisão de palavras. A divisão de palavras, por predefinição, divide por espaços em branco, não apenas por mudanças de linha. Este ciclo for está a iterar sobre as palavras do ficheiro, não sobre as linhas.
  2. A substituição de comandos não está entre aspas, por isso está sujeita à expansão de nomes de ficheiros. Cada palavra do ficheiro será usada como um padrão glob.

Como aprendemos no conceito de quoting, a divisão de palavras pode ser controlada com a variável IFS, e a expansão de nomes de ficheiros pode ser desativada. Mas isto tende a ser uma solução frágil. O for é o método errado para iterar sobre as linhas de um ficheiro. A forma idiomática de ler um ficheiro é com um ciclo while.

Ciclos while

Usa o while para repetir uma sequência de comandos enquanto uma condição for verdadeira.

while CONDITION_COMMANDS; do COMMANDS; done

Tal como acontece com o if (como aprendemos no conceito de condicionais), não há sintaxe especial para CONDITION_COMMANDS. O estado de saída da lista de comandos determinará "verdadeiro" ou "falso".

Controlar ciclos

O comando break sai do ciclo. O controlo retoma no comando que se segue ao terminador done do ciclo.

O comando continue salta para a iteração seguinte do ciclo.

Ler as linhas de um ficheiro

Como já foi referido, um ciclo while é a forma idiomática de ler um ficheiro.

while IFS= read -r line; do
    do_something_with "$line"
done < some.file
  • O conteúdo do ficheiro é fornecido ao ciclo como entrada, com a redireção <.

  • O comando read devolve "verdadeiro" se conseguir ler uma linha completa da sua entrada. Quando a entrada se esgota, ou se a última linha terminar sem uma mudança de linha final, o read devolve "falso".

    • A forma realmente idiomática de ler as linhas de um ficheiro, mesmo que a última linha não termine com uma mudança de linha, é

      while IFS= read -r line || [[ -n "$line" ]]; do ...
      
  • A opção -r diz ao read para não substituir sequências de barra invertida: uma barra invertida é apenas um caráter normal.

  • IFS= atribui a string vazia ao IFS apenas durante a execução do comando read. Isto desativa temporariamente a divisão de palavras, para que os espaços em branco no início ou no fim da linha de texto recebida sejam preservados.

Until

A construção until repete uma sequência de comandos enquanto uma condição for falsa. O ciclo repete até que a condição se torne verdadeira.

until CONDITION_COMMANDS; do COMMANDS; done

É raro usar o until. É mais comum usar um ciclo "while not".

while ! CONDITION_COMANDS; do ...

Ciclos infinitos

Por vezes queres repetir indefinidamente. Eis duas formas de o fazer.

  1. um ciclo while com uma condição que tem sempre um estado de saída de sucesso

    while true; do ...
    
  2. um ciclo for aritmético com expressões vazias

    for ((;;)); do ...
    

Do-while

Não existe uma construção do-while explícita, mas podes obter o mesmo efeito:

while true; do
    COMMANDS

    if END_CONDITION; then
        break
    fi
done
Editar via GitHub A ligação abre numa nova janela ou separador