Pesquisa de site

Aprenda awk codificando um jogo de "adivinhe o número"


As linguagens de programação tendem a compartilhar muitas características comuns. Uma ótima maneira de aprender um novo idioma é criar um programa familiar. Neste artigo, criarei um jogo de “adivinhe o número” usando o awk para demonstrar conceitos familiares.

Ao aprender uma nova linguagem de programação, é bom se concentrar nas coisas que a maioria das linguagens de programação tem em comum:

  • Variáveis – locais onde as informações são armazenadas
  • Expressões – maneiras de calcular coisas
  • Declarações – os meios pelos quais as mudanças de estado são expressas em um programa

Esses conceitos são a base da maioria das linguagens de programação.

Depois de entender esses conceitos, você pode começar a descobrir o resto. Por exemplo, a maioria das linguagens tem uma "maneira de fazer as coisas" apoiada em seu design, e essas formas podem ser bem diferentes de um programa para outro. Essas formas incluem modularidade (agrupamento de funcionalidades relacionadas), declarativo versus imperativo, orientação a objetos, recursos sintáticos de baixo versus alto nível e assim por diante. Um exemplo familiar para muitos programadores é a “cerimônia”, ou seja, a quantidade de trabalho necessária para definir o cenário antes de resolver o problema. Diz-se que a linguagem de programação Java tem um requisito de cerimônia significativo, decorrente de seu design, que exige que todo o código seja definido dentro de uma classe.

Mas voltando ao básico. As linguagens de programação geralmente compartilham semelhanças. Depois de conhecer uma linguagem de programação, comece aprendendo o básico de outra para apreciar as diferenças dessa nova linguagem.

Uma boa maneira de proceder é criar um conjunto de programas de teste básicos. Com isso em mãos, o aprendizado começa com essas semelhanças.

Um programa de teste que você pode usar é o programa “adivinhe o número”. O computador escolhe um número entre um e cem e pede que você adivinhe o número. O programa faz um loop até que você dê um palpite correto.

O programa “adivinhe o número” exercita diversos conceitos em linguagens de programação:

  • Variáveis
  • Entrada
  • Saída
  • Avaliação condicional
  • rotações

Esse é um ótimo experimento prático para aprender uma nova linguagem de programação.

Nota: Este artigo foi adaptado do artigo de Moshe Zadka sobre como usar essa abordagem no artigo de Julia e Jim Hall sobre como fazer isso no Bash.

Adivinhe o número no awk

Vamos escrever um jogo de “adivinhe o número” como um programa Awk.

Awk é digitado dinamicamente, é uma linguagem de script orientada para transformação de dados e tem suporte surpreendentemente bom para uso interativo. O Awk existe desde 1970, originalmente como parte do sistema operacional Unix. Se você não conhece o Awk, mas adora planilhas, isso é um sinal… vá aprender o Awk!

Você pode começar sua exploração do Awk escrevendo uma versão do jogo "adivinhe o número".

Aqui está minha implementação (com números de linha para que possamos revisar alguns dos recursos específicos):

     1    BEGIN {
     2        srand(42)
     3        randomNumber = int(rand() * 100) + 1
     4        print "random number is",randomNumber
     5        printf "guess a number between 1 and 100\n"
     6    }
     7    {
     8        guess = int($0)
     9        if (guess < randomNumber) {
    10            printf "too low, try again:"
    11        } else if (guess > randomNumber) {
    12            printf "too high, try again:"
    13        } else {
    14            printf "that's right\n"
    15            exit
    16        }
    17    }

Podemos ver imediatamente semelhanças entre as estruturas de controle do Awk e aquelas de C ou Java, mas diferentemente do Python. Em declarações como if-then-else ou while, os comandos then, else e while as partes recebem uma instrução ou um grupo de instruções entre { e }. No entanto, há uma grande diferença no AWk que precisa ser entendida desde o início:

Por design, o Awk é construído em torno de um pipeline de dados.

O que isso significa? A maioria dos programas Awk são trechos de código que recebem uma linha de entrada, fazem algo com os dados e os gravam na saída. Reconhecendo a necessidade de tal pipeline de transformação, o Awk, por padrão, fornece todo o encanamento de transformação. Vamos explorar isso através do programa acima fazendo uma pergunta básica: Onde está a estrutura de 'leitura de dados do console'?

A resposta para isso é – está integrado. Em particular, as linhas 7 a 17 dizem ao Awk o que fazer com cada linha lida. Dado esse contexto, é muito fácil ver que as linhas 1 a 6 são executadas antes de qualquer coisa ser lida.

Mais especificamente, a palavra-chave BEGIN na linha 1 é uma espécie de "padrão", neste caso indicando ao Awk que, antes de ler qualquer dado, ele deve executar o que segue o BEGIN no {…}. Uma palavra-chave END semelhante, não usada neste programa, indica ao Awk o que fazer quando tudo tiver sido lido.

Voltando às linhas 7 a 17, vemos que eles criam um bloco de código {…} que é semelhante, mas não há nenhuma palavra-chave na frente. Como não há nada antes do { para o Awk corresponder, ele aplicará esta linha a cada linha de entrada recebida. Cada linha de entrada será inserida como estimativa do usuário.

Vejamos o código que está sendo executado. Primeiro, o preâmbulo que acontece antes de qualquer entrada ser lida.

Na linha 2, inicializamos o gerador de números aleatórios com o número 42 (se não fornecermos um argumento, o relógio do sistema será usado). 42? Claro, 42. A linha 3 calcula um número aleatório entre 1 e 100, e a linha 4 imprime esse número para fins de depuração. A linha 5 convida o usuário a adivinhar um número. Observe que esta linha usa printf, não print. Assim como C, o primeiro argumento de printf é um modelo usado para formatar a saída.

Agora que o usuário sabe que o programa espera uma entrada, ele pode digitar uma estimativa no console. O Awk fornece essa suposição ao código nas linhas 7 a 17, conforme mencionado anteriormente. A linha 18 converte o registro de entrada em um número inteiro; $0 indica todo o registro de entrada, enquanto $1 indica o primeiro campo do registro de entrada, $2 o segundo e assim por diante. Sim, o Awk divide uma linha de entrada em campos constituintes, usando o separador predefinido, cujo padrão é espaço em branco. As linhas 9 a 15 comparam a estimativa com o número aleatório, imprimindo respostas apropriadas. Se a estimativa estiver correta, a linha 15 sai prematuramente do pipeline de processamento da linha de entrada.

Simples!

Dada a estrutura incomum dos programas Awk como trechos de código que reagem a configurações específicas de linha de entrada e fazem coisas com os dados, vamos dar uma olhada em uma estrutura alternativa apenas para ver como funciona a parte de filtragem:

     1    BEGIN {
     2        srand(42)
     3        randomNumber = int(rand() * 100) + 1
     4        print "random number is",randomNumber
     5        printf "guess a number between 1 and 100\n"
     6    }
     7    int($0) < randomNumber {
     8        printf "too low, try again: "
     9    }
    10    int($0) > randomNumber {
    11        printf "too high, try again: "
    12    }
    13    int($0) == randomNumber {
    14        printf "that's right\n"
    15        exit
    16    }

As linhas 1 a 6 não mudaram. Mas agora vemos que as linhas 7 – 9 são o código que é executado quando o valor inteiro da linha é menor que o número aleatório, as linhas 10 – 12 são o código que é executado quando o valor inteiro da linha é maior que o número aleatório , e as linhas 13 a 16 são o código que é executado quando os dois correspondem.

Isso deveria parecer "legal, mas estranho" – por que calcularíamos repetidamente int($0), por exemplo? E com certeza seria uma maneira estranha de resolver o problema. Mas esses padrões podem ser maneiras realmente maravilhosas de separar o processamento condicional, já que podem empregar expressões regulares ou qualquer outra estrutura suportada pelo Awk.

Para completar, podemos usar esses padrões para separar cálculos comuns de coisas que se aplicam apenas a circunstâncias específicas. Aqui está uma terceira versão para ilustrar:

     1    BEGIN {
     2        srand(42)
     3        randomNumber = int(rand() * 100) + 1
     4        print "random number is",randomNumber
     5        printf "guess a number between 1 and 100\n"
     6    }
     7    {
     8        guess = int($0)
     9    }
    10    guess < randomNumber {
    11        printf "too low, try again: "
    12    }
    13    guess > randomNumber {
    14        printf "too high, try again: "
    15    }
    16    guess == randomNumber {
    17        printf "that's right\n"
    18        exit
    19    }

Reconhecendo que, independentemente do valor da entrada, ele precisa ser convertido em um número inteiro, criamos as linhas 7 – 9 para fazer exatamente isso. Agora, os três grupos de linhas, 10 – 12, 13 – 15 e 16 – 19, referem-se à variável estimativa já definida, em vez de converter a linha de entrada a cada vez.

Vamos voltar à lista de coisas que queríamos aprender:

  • variáveis – sim, o Awk tem essas; podemos inferir que os dados de entrada vêm como strings, mas podem ser convertidos em um valor numérico quando necessário
  • entrada – Awk apenas envia entrada através de sua abordagem de “pipeline de transformação de dados” para ler coisas
  • saída – usamos os procedimentos print e printf do Awk para escrever coisas na saída
  • avaliação condicional – aprendemos sobre o if-then-else do Awk e filtros de entrada que respondem a configurações específicas de linha de entrada
  • loops – hein, imagine isso! Não precisamos de um loop aqui, mais uma vez, graças à abordagem de “pipeline de transformação de dados” adotada pelo Awk; o loop "simplesmente acontece". Observe que o usuário pode sair do pipeline prematuramente enviando um sinal de fim de arquivo para o Awk (um CTRL-D ao usar uma janela de terminal Linux)

Vale a pena considerar a importância de não precisar de um loop para lidar com as entradas. Um dos motivos pelos quais o Awk permaneceu viável por tanto tempo é que os programas Awk são compactos, e um dos motivos pelos quais eles são compactos é que não há necessidade de um padrão para ler o console ou um arquivo.

Vamos executar o programa:

$ awk -f guess.awk
random number is 25
guess a number between 1 and 100: 50
too high, try again: 30
too high, try again: 10
too low, try again: 25
that's right
$

Uma coisa que não abordamos foram os comentários. Um comentário Awk começa com # e termina com o fim da linha.

Embrulhar

Awk é incrivelmente poderoso e este jogo de “adivinhe o número” é uma ótima maneira de começar. No entanto, não deve ser o fim da sua jornada. Você pode ler sobre a história do Awk e do Gawk (GNU Awk), uma versão expandida do Awk e provavelmente aquela que você tem no seu computador se estiver executando o Linux, ou ler tudo sobre o original de seus desenvolvedores iniciais.

Você também pode baixar nossa folha de dicas para ajudá-lo a acompanhar tudo o que aprende.