Avançar para o conteúdo
Imagem com logotipo, contendo link para a página inicial
  • United Stated of America flag, representing the option for the English language.
  • Bandeira do Brasil, simbolizando a opção pelo idioma Português do Brasil.

Aprenda Programação: Entrada em Linha de Comando (Argumentos de Linha de Comando e Interatividade)

Exemplos de uso de entrada em linha de comando em quatro linguagens de programação: Python, Lua, GDScript e JavaScript.

Créditos para a imagem: Imagem criada pelo autor usando o programa Spectacle.

Pré-Requisitos

Na introdução sobre ambientes de desenvolvimento, indiquei Python, Lua e JavaScript como boas escolhas de linguagens de programação para iniciantes. Posteriormente, comentei sobre GDScript como opção para pessoas que tenham interesse em programar jogos digitais ou simulações. Para as atividades de introdução a programação, você precisará de, no mínimo, um ambiente de desenvolvimento configurado em uma das linguagens anteriores.

Caso queira experimentar programação sem configurar um ambiente, você pode usar um dos editores online que criei:

Contudo, eles não possuem todos os recursos dos interpretadores para as linguagens. Assim, cedo ou tarde, você precisará configurar um ambiente de desenvolvimento. Caso precise configurar um, confira os recursos a seguir.

Assim, se você tem um Ambiente Integrado de Desenvolvimento (em inglês, Integrated Development Environment ou IDE) ou a combinação de editor de texto com interpretador, você está pronto para começar. Os exemplos assumem que você saiba executar código em sua linguagem escolhida, como apresentado nas páginas de configuração.

Caso queira usar outra linguagem, a introdução provê links para configuração de ambientes para as linguagens C, C++, Java, LISP, Prolog e SQL (com SQLite). Em muitas linguagens, basta seguir os modelos da seção de experimentação para adaptar sintaxe, comandos e funções dos trechos de código. C e C++ são exceções, por requerem o uso de ponteiros para acesso à memória.

Automação para o Futuro à Moda Antiga

Para muitas pessoas, a linha de comando é um artefato mágico arcaico; uma herança dos primórdios da Computação. Para usuários e usuários finais, isso pode ser realidade. Para desenvolvedoras e desenvolvedores, não.

Em 2022, a linha de comando continua eficaz e eficiente para a realização de tarefas comuns em programação. Automação, trocas de dados, conversões dados, e operação de servidores são alguns delas. Mesmo em projetos pessoais, a linha de comando é necessária para usar de gerenciadores de pacotes (como os apresentados em Bibliotecas) e sistemas de gerenciamento para controle de versões de código-fonte.

Ao invés de temê-la ou fugir da linha de comando, convém adotá-la como ferramenta de programação. Os esforços necessários para aprendizado tendem a ser recompensados ao longo do tempo. Em particular, a linha de comando permite constatar o quão limitada (e ineficiente) é a operação de programas com interfaces gráficas. A possibilidade de armazenar seqüências de comandos como macros ou em um script permite repetir soluções usando múltiplos programas como se eles fossem subrotinas em linguagens de programação.

A linha de comando, pois, continua um recurso moderno para automação. A partir deste tópico, você poderá integrar seus programas para uso eficiente com linha de comando, ao receber valores como entradas do interpretador (shell) de linha de comando.

Nomenclatura

Programas com interface gráfica são comumente referenciados pela sigla GUI, do inglês, graphical user interface. Programas com interface textual possuem uma sigla similar: TUI, do inglês text-based user interface. Programas operados pela linha de comando são comumente ditos com uma interface de linha de comandos (command-line interface ou CLI).

Filosofia Unix

Quando se trabalha com linha de comando, convém conhecer a filosofia Unix. Citando-se a citação da Wikipedia:

Doug McIlroy, o inventor da canalização e um dos fundadores da tradição do Unix, resumiu a filosofia da seguinte maneira[1]:

"Esta é a filosofia Unix:

Escreva programas que façam apenas uma coisa, mas que a façam bem feita. Escreva programas que trabalhem juntos. Escreva programas que manipulem streams de texto, pois esta é uma interface universal."

Ou, de maneira simples, como: "faça apenas uma coisa e faça bem".

O princípio de restringir um programa a realização eficiente de uma única tarefa é poderoso. Ao invés de pensar em programas com múltiplas funcionalidades, pode-se pensar em um programa como se fosse uma subrotina -- ou um algoritmo com um único propósito. Dado um conjunto de entrada, calcula-se e retorna-se a saída.

Quando se adota a filosofia Unix, pode-se usar compor programas como seqüências de execuções com programas. Ou seja, ao invés de programar-se utilizando-se linguagens de programação, pode-se programar usando-se programas especializados. Ferramentas.

Por exemplo, interpretadores de comando fornecem um operador chamado pipe (ou pipeline, no termo para o mecanismo). A metáfora de um pipe é um cano que redireciona a saída de um programa para a entrada de outro programa. Isso permite executar programas em seqüência, cujas saídas de programas anteriores são resultados intermediários que funcionam como entradas para os próximos comandos.

Por exemplo, echo permite escrever uma mensagem. wc conta linhas, palavras e caracteres. awk permite, dentre outros, extrair campos de um texto. Combinando-se os três programas como comandos, pode-se obter o número de palavras de uma frase. Isso pode ser feito usando-se o pipe, que comumente utiliza uma barra vertical (|) como operador.

echo "Olá, meu nome é Franco" | wc | awk '{print $2}'
5

Adaptando-se o comando, pode-se obter o número de palavras em um arquivo texto.

echo "Olá, meu nome é Franco" > arquivo.txt
cat arquivo.txt | wc | awk '{print $2}'
5
wc < arquivo.txt | awk '{print $2}'
5

No segundo exemplo, usa-se o operador com símbolo de maior (>) para redirecionar a saída padrão para um arquivo. Assim, a primeira linha equivale a criar um arquivo chamado arquivo.txt com o conteúdo Olá, meu nome é Franco. Os comandos da segunda e terceira linha são equivalentes. A primeira versão usa um pipe: cat lê o conteúdo do arquivo, que é passado para wc por outro pipe. Na terceira linha, usa-se o operador com símbolo de menor (<) para redirecionar o conteúdo de um arquivo para a entrada padrão. Assim, é como se uma pessoa digita-se o conteúdo do arquivo como entrada para o programa wc.

A linha de comando, é, pois, uma ferramenta para automação e administração de sistemas. De tópicos anteriores, sabe-se escrever dados como saída para a linha de comando usando-se subrotinas ou comandos como print(). Também sabe-se como solicitar a entrada de dados em um processo (programa em execução); isso é chamado de entrada interativa. Para completar o ciclo, resta aprender a receber entradas da linha de comando que sejam fornecidas de forma não-interativa por usuários finais.

Entrada por Linha de Comando

O tópico Entrada em Console (Terminal) apresentou como ler dados de forma interativa com um programa em execução.

A entrada por linha de comando é uma abordagem diferente para a entrada, na qual se fornece valores ao programa como parte do comando para iniciá-lo. Em programas CLI não-interativos, passa-se os valores quando se inicia o programa, então espera-se o resultado (que é fornecido quando o programa termina).

Por exemplo, um comando como date em Linux fornece a data e a hora atual.

date
qui 10 fev 2022 12:22:33 -03

O comando também fornece uma série de parâmetros para configurar a saída. Por exemplo, caso se passe um valor como --iso-8601, a data é formatada no padrão ISO 8601; com --rfc-3339, a data é formatada segundo a RFC 3339.

date --iso-8601
2022-02-10
date --rfc-3339=seconds
2022-02-10 12:23:45-03:00

Em ambos os casos, o valor passado após o nome do programa serve como um parâmetro que, assim como uma entrada interativa, permite modificar o fluxo de execução do programa.

Caso você não use um IDE para executar seus programas, você fornece como parâmetro para um interpretador o nome do arquivo a ser executado. Por exemplo, lua arquivo.lua. O processo é o mesmo; arquivo.lua é o parâmetro que especifica o arquivo que será usado como entrada. Usar apenas lua acionaria o interpretador em modo REPL.

Em particular, adotando-se essa mesma estratégia em seus próprios programas, é possível criar novas ferramentas para a linha de comando -- e usá-las em conjunto (ou em complemento) a todas as outras fornecidas pelo shell.

JavaScript (Node.js), Python, Lua e GDScript

Um navegador de Internet não é um interpretador de linha de comando. Assim, a versão em JavaScript utiliza Node.js como interpretador para JavaScript. Node.js foi instalado no tópico Bibliotecas.

// node script.js texto 1 1.23

let argumentos = process.argv
for (let indice = 0; indice < argumentos.length; ++indice) {
    let argumento = argumentos[indice]
    console.log(indice, argumento)
}
# python script.py texto 1 1.23

import sys

indice = 0
argumentos = sys.argv
for argumento in argumentos:
    print(indice, argumento)
    indice += 1
-- lua script.lua texto 1 1.23

local argumentos = arg
for indice, argumento in ipairs(argumentos) do
    print(indice, argumento)
end
# godot -s script.gd texto 1 1.23

extends SceneTree

func _init():
    var argumentos = OS.get_cmdline_args()
    var indice = 0
    for argumento in argumentos:
        printt(indice, argumento)
        indice += 1

    quit()

A primeira linha de cada programa sugere um comando para teste. Pode-se alterar os valores para quaisquer outros.

Para facilitar a comparação entre as implementações, todos os argumentos recebidos da linha de comando foram atribuídos à variável argumentos. Não é necessário criá-la; poder-se-ia usar a variável corresponde em cada linguagem de programação.

Em JavaScript com Node.js, os argumentos recebidos são salvos no vetor process.argv (documentação). Em Python, os argumentos são salvos na lista sys.argv (documentação). Em Lua, os argumentos são salvos na tabela global args (documentação). Em GDScript, os argumentos são acessados usando OS.get_cmdline_args() (documentação). Em GDScript, deve-se tomar cuidado para não escolher os nomes dos argumentos usados para o editor (documentação). A versão em GDScript usa SceneTree ao invés de Node (ou outro tipo) para permitir a execução do programa em um interpretador de linha de comando sem a necessidade de um projeto. Isso foi comentado anteriormente em configurações de ambiente de desenvolvimento para GDScript (Godot) e no tópico Ponto de Entrada e Estrutura de Programa.

O nome argv é popular por ser tradicional na linguagem C. Em C, a função main() pode receber dois parâmetros: argv, que é um vetor de cadeias de caracteres que armazena os argumentos recebidos da linha de comando, e argc, que corresponde ao tamanho de argv. Embora seja possível trocar os nomes das variáveis, argv e argc são os nomes adotados por padrão.

A leitura de valores da linha de um comando é simples. Após identificar a variável que armazena os valores recebidos, basta processá-la como qualquer outro vetor. A posição do primeiro parâmetro recebido pode variar entre interpretadores e linguagens de programação. É comum que a primeira posição do vetor contenha o nome do programa executado (ou o caminho para o programa executado). Logo, nem sempre a primeira posição será o primeiro valor recebido como argumento.

Para usar os programas, é necessário fornecer valores durante a inicialização. Em outras palavras, não se deve clicar duas vezes para iniciar o programa, mas usar um interpretador de linha de comando para executar o programa (ou usar um atalho ou script que defina os parâmetros). Quando se inicia um programa usando um interpretador de linha de comando, escreve-se o nome do programa (ou caminho para o programa). Quaisquer valores escritos após o nome do programa são parâmetros que o programa recebe como argumentos de linha de comando. De forma genérica:

./nome_programa parâmetro_1 parâmetro_2 outros_parametros

Para linguagens interpretadas, nome_programa será o nome do interpretador. O primeiro parâmetro passado será o arquivo a ser interpretado (ou alguma flag para configuração, indicando que se trata de um script). Todos os demais parâmetros são passados como valores de entrada para o programa (exceto sejam flags para o interpretador).

Os exemplos a seguir ilustram as saídas para os programas definidos como exemplos. Pode-se perceber que a saída tende a variar entre interpretadores, assim como o índice que armazena o primeiro valor de parâmetro.

node script.js texto 1 1.23
0 /usr/bin/node
1 /home/franco/tmp/js/script.js
2 texto
3 1
4 1.23
python script.py texto 1 1.23
0 script.py
1 texto
2 1
3 1.23
lua script.lua texto 1 1.23
1       texto
2       1
3       1.23
godot -s script.gd texto 1 1.23
Godot Engine v3.4.2.stable.arch_linux - https://godotengine.org
OpenGL ES 3.0 Renderer: NVIDIA GeForce GTX 1080/PCIe/SSE2
OpenGL ES Batching: ON

0       -s
1       script.gd
2       texto
3       1
4       1.23

Para GDScript, caso não se queira abrir uma janela, pode-se passar --no-window antes de -s. Por exemplo, godot --no-window -s script.gd texto.

É importante notar que todo valor é recebido pelo programa como uma cadeia de caracteres. Para obter o valor como outro tipo, basta convertê-lo para o tipo de dados esperado. Isso foi abordado anteriormente, por exemplo, em Tipos de Dados.

Além disso, caso se queira usar espaços, pode-se fornecer um valor entre aspas (normalmente interpretador de linha de comando aceitam aspas duplas ou simples, embora o suporte possa variar dependendo do programa). Da mesma forma, é possível usar uma contrabarra como caractere de escape para caracteres ou valores considerados como especiais pelo interpretador de linha de comando. Por exemplo:

node script.js "Olá, meu nome é Franco\!"
0 /usr/bin/node
1 /home/franco/tmp/js/script.js
2 Olá, meu nome é Franco!
python script.py "Olá, meu nome é Franco\!"
0 script.py
1 Olá, meu nome é Franco
lua script.lua "Olá, meu nome é Franco\!"
1       Olá, meu nome é Franco!
godot -s script.gd "Olá, meu nome é Franco\!"
Godot Engine v3.4.2.stable.arch_linux - https://godotengine.org
OpenGL ES 3.0 Renderer: NVIDIA GeForce GTX 1080/PCIe/SSE2
OpenGL ES Batching: ON

0       -s
1       script.gd
2       Olá, meu nome é Franco!

Com a alteração, passa-se a mensagem Olá, meu nome é Franco! como um único valor (ao invés de considerar-se um valor a cada espaço).

Parâmetros de Linha de Comando em IDEs

IDEs comumente fornecem recursos para ajustar parâmetros de linha de comando que serão fornecidos ao programa. Isso varia de IDE para IDE; portanto, convém consultar a documentação.

No IDE Thonny para Python, pode-se habilitar a opção em Visualizar (View), depois Argumentos de programa (Program arguments). A opção adicionará um campo de texto chamado Argumentos do programa: (Program arguments:) ao IDE. Pode-se escrever os parâmetros desejados no campo (por exemplo, texto 1 1.23).

No IDE ZeroBrane Studio para Lua, existem algumas opções para o ajuste, descritas na documentação. Uma das mais simples é acessar Projeto (Project), depois escolher a opção Parâmetros da Linha de Comando... (Command Line Parameters...). No diálogo que aparecer, deve-se escrever os argumentos (por exemplo, texto 1 1.23) e confirmar.

Para GDScript, pode-se configurar o motor Godot em Projeto (Project), Configurações do Projeto (Project Settings), Editor (Editor), Main Run Args (Main Run Args). A forma mais fácil de encontrar a opção é usar a busca nas configurações de projeto. Após encontrada, deve-se escrever o valor desejado (por exemplo, texto 1 1.23) e fechar a configuração. Isso permite usar argumentos em _ready().

extends Node

func _ready():
    var argumentos = OS.get_cmdline_args()
    var indice = 0
    for argumento in argumentos:
        printt(indice, argumento)
        indice += 1

Deve-se notar que os argumentos são para o programa; então os valores serão iguais para todos os usos, independentemente do script.

JavaScript para Navegadores: Parâmetros de URL

Embora não seja possível usar parâmetros de linha de comando em JavaScript, existe uma alternativa. Em navegadores, é possível passar parâmetros ao endereço de uma requisição, por meio do Uniform Resource Locator (URL). A forma mais simples de fazer isso consiste no uso do método de requisição GET. O uso de parâmetros de requisições é comumente usado para a criação de formulários em páginas da Internet. Com um pouco de criatividade, pode-se explorá-lo para simular parâmetros de linha de comando em URLs de navegadores.

O exemplo em JavaScript define um arquivo chamado script.js e o código HTML em um arquivo qualquer (por exemplo, index.html, usado a seguir). Para usar a página, deve-se escrever o endereço como: index.html?texto=texto&inteiro=1&real=1.23. texto=, inteiro= e real= são os nomes desejados para consultar o valor do parâmetro recebido. Pode-se escolhê-los de acordo com a necessidade, usando-se o padrão nome=valor. Separa-se múltiplos valores usando-se um & (e-comercial).

let parametros_texto = window.location.search
console.log(parametros_texto)

const parametros = new Proxy(
    new URLSearchParams(window.location.search),
                        {
                            get: (alvo, nome) => alvo.get(nome),
                        })
console.log(parametros.texto, parametros.inteiro, parametros.real)
console.log(parametros.texto, Number(parametros.inteiro), Number(parametros.real))
<!DOCTYPE html>
<html lang="pt-BR">
  <head>
    <meta charset="utf-8">
    <title>Parâmetros de URL para JavaScript</title>
    <meta name="author" content="Franco Eusébio Garcia">
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <script src="./script.js"></script>
  </body>
</html>

Os valores são fornecidos como uma cadeia de caracteres armazenada em window.location.search (documentação). Em navegadores modernos, uma forma de extrair os valores obtidos consiste no uso de Proxy (documentação). Para acessar o valor, cria-se uma instância de Proxy, depois usa-se o nome escolhido para cada consulta.

Exemplos

Programas CLI são comuns, especialmente como ferramentas para programadoras e programadores. Assim, existem bibliotecas para facilitar a criação desses programas em várias linguagens de programação. As bibliotecas podem oferecem recursos como facilidades para extração de valores fornecidos como parâmetros ou apresentação de valores com texto formato (por exemplo, com cores diferentes). Duas opções tradicionais incluem são getopt e argp, do Projeto GNU.

GNU também define padrões para CLI, que são comuns em vários programas para linha de comando. Um exemplo é -h ou --help, usando para obter ajudar. Por exemplo, python --help (ou python -h), lua --help (ou lua -h), node --help (ou node -h), e godot --help (ou godot -h) imprimem as opções de ajuda de cada dos programas anteriores, listando possíveis parâmetros que podem ser usados ao iniciar o programa. Os padrões GNU são comuns em sistemas Linux; outros sistemas podem ter outras convenções. Por exemplo, em Windows, é comum que programas (especialmente mais antigos) utilizem o padrão /opcao como código para uma opção (seria o equivalente a --opcao). Por exemplo, a opção de ajuda é comumente definida como /?.

Também existem bibliotecas que permitem criar interfaces "gráficas" usando texto. Uma das mais tradicionais chama-se ncurses. O nome significa new curses, por ser uma versão melhorada da biblioteca curses. ncurses permite criar programas interativos TUI para a linha de comando com interfaces sofisticadas (para padrões de texto).

Como sempre, convém saber como as bibliotecas implementam funcionalidades básicas.

Configuração Usando Chave e Valor

Para programas complexos, o uso de bibliotecas que facilitam a implementação de CLI pode ser recomendável. Para programas simples, selecionar valores combinando-se operações de cadeias de caracteres com estruturas de condição pode ser suficiente. Pode-se, pois, considerar a segunda situação para fins de aprendizado.

Em programas de linha de comando, duas funcionalidades são comuns para configuração:

  • Flags para habilitar ou desabilitar recursos, ou exibir uma opção em particular. Por exemplo, -h para mostrar a ajuda e sair.

  • Pares de chave e valor, para configurar ou ajustar valores que permitem configuração. O formato típico tende a ser uma de duas possibilidades:

    1. --nome=valor ou -n=valor;
    2. --nome valor ou -n valor.

    No caso de Windows, os pares podem seguir o formato /nome valor.

    Para pares de chave e valor, o primeiro caso assume que o valor esteja após um caractere específico (delimitador) como igual. Como argumentos são comumente recebidos como vetores pelo programa, basta analisar um único índice do vetor para extrair o valor.

    O segundo caso assume que o valor seja fornecido após um delimitador como espaço. Nesse caso, deve-se consultar o índice da opção para identificar a chave, e o próximo índice identificar o valor.

    Em ambos os casos, caso o valor contenha espaços, deve-se passar o texto entre aspas (simples ou duplas).

Para um exemplo simples, pode-se considerar um programa que gera uma saudação. O programa pode possuir opções como:

  1. --saudacao ou -s. O padrão pode ser Olá!. Caso se use algo como --saudacao=Oi, o programa usará o valor Oi ao invés de Olá!;
  2. --mensagem ou -m. O padrão pode ser Meu nome é Franco.. Caso se use algo como --mensagem="Meu nome é Ana", o programa usará a mensagem definida ao invés da padrão;
  3. --pula-linha ou -p. O padrão pode ser escrever o texto em uma única linha. Caso especificado, pula-se uma linha entre a saudação e a mensagem. Caso omitido, exibe-se ambas os valores na mesma linha;
  4. --ajuda ou -a. Exibe uma mensagem de ajuda e termina o programa.

Em geral, pode-se passar parâmetros para a linha de comando em qualquer ordem. Assim, não se pode assumir qual ocorrerá primeiro. Também é comum que programas definam qual será o último parâmetro esperado. Isso facilita o uso de pipes, assim como de interpretadores para programação (após o arquivo com o script, eventuais opções remanescentes são para o programa, ao invés de para o interpretador). Evidentemente, cada programa possui liberdade para definir as próprias convenções; basta documentá-las.

// Exemplos de uso:
// node script.js
// node script.js --pula-linha --saudacao=Oi. --mensagem="Meu nome é X"
// node script.js --mensagem="Meu nome é X"
// node script.js --saudacao=Oi. --pula-linha

const EXIT_FAILURE = 1

let pula_linha = false
let saudacao = "Olá!"
let mensagem = "Meu nome é Franco."

for (argumento of process.argv.slice(2)) {
    if ((argumento.startsWith("--ajuda")) || (argumento.startsWith("-a"))) {
        console.log("Uso:")
        console.log("--saudacao=...\tUsa o valor escolhido como saudação.")
        console.log("--mensagem=...\tUsa o valor escolhido como mensagem.")
        console.log("--pula-linha  \tPula uma linha entre a saudação e a mensagem.")
        console.log("--ajuda       \tExibe este texto e termina o programa.")
        process.exit()
    }

    if ((argumento.startsWith("--saudacao=")) || (argumento.startsWith("-s="))) {
        saudacao = argumento.slice(argumento.indexOf("=") + 1)
    } else if ((argumento.startsWith("--mensagem=")) || (argumento.startsWith("-m="))) {
        mensagem = argumento.slice(argumento.indexOf("=") + 1)
    } else if ((argumento == "--pula-linha") || (argumento == "-p")) {
        pula_linha = true
    } else {
        console.log("Erro:")
        console.log("Opção desconhecida: " + argumento)
        process.exit(EXIT_FAILURE)
    }
}

if (pula_linha) {
    console.log(saudacao)
    console.log(mensagem)
} else {
    console.log(saudacao + " " + mensagem)
}
# Exemplos de uso:
# python script.py
# python script.py --pula-linha --saudacao=Oi. --mensagem="Meu nome é X"
# python script.py --mensagem="Meu nome é X"
# python script.py --saudacao=Oi. --pula-linha

import sys

EXIT_FAILURE = 1

pula_linha = False
saudacao = "Olá!"
mensagem = "Meu nome é Franco."

for argumento in sys.argv[1:]:
    if ((argumento.startswith("--ajuda")) or (argumento.startswith("-a"))):
        print("Uso:")
        print("--saudacao=...\tUsa o valor escolhido como saudação.")
        print("--mensagem=...\tUsa o valor escolhido como mensagem.")
        print("--pula-linha  \tPula uma linha entre a saudação e a mensagem.")
        print("--ajuda       \tExibe este texto e termina o programa.")
        sys.exit()

    if ((argumento.startswith("--saudacao=")) or (argumento.startswith("-s="))):
        saudacao = argumento[argumento.find("=") + 1:]
    elif ((argumento.startswith("--mensagem=")) or (argumento.startswith("-m="))):
        mensagem = argumento[argumento.find("=") + 1:]
    elif ((argumento == "--pula-linha") or (argumento == "-p")):
        pula_linha = True
    else:
        print("Erro:")
        print("Opção desconhecida: " + argumento)
        sys.exit(EXIT_FAILURE)

if (pula_linha):
    print(saudacao)
    print(mensagem)
else:
    print(saudacao + " " + mensagem)
-- Exemplos de uso:
-- lua script.lua
-- lua script.lua --pula-linha --saudacao=Oi. --mensagem="Meu nome é X"
-- lua script.lua --mensagem="Meu nome é X"
-- lua script.lua --saudacao=Oi. --pula-linha

function starts_with(texto, valor)
    return (string.find(texto, valor, 1, true) == 1)
end

local EXIT_FAILURE = 1

local pula_linha = false
local saudacao = "Olá!"
local mensagem = "Meu nome é Franco."

for _, argumento in ipairs(arg) do
    if ((starts_with(argumento, "--ajuda")) or (starts_with(argumento, "-a"))) then
        print("Uso:")
        print("--saudacao=...\tUsa o valor escolhido como saudação.")
        print("--mensagem=...\tUsa o valor escolhido como mensagem.")
        print("--pula-linha  \tPula uma linha entre a saudação e a mensagem.")
        print("--ajuda       \tExibe este texto e termina o programa.")
        os.exit()
    end

    if ((starts_with(argumento, "--saudacao=")) or (starts_with(argumento, "-s="))) then
        saudacao = string.sub(argumento, string.find(argumento, "=", 1, true) + 1, #argumento)
    elseif ((starts_with(argumento, "--mensagem=")) or (starts_with(argumento, "-m="))) then
        mensagem = string.sub(argumento, string.find(argumento, "=", 1, true) + 1, #argumento)
    elseif ((argumento == "--pula-linha") or (argumento == "-p")) then
        pula_linha = true
    else
        print("Erro:")
        print("Opção desconhecida: " .. argumento)
        os.exit(EXIT_FAILURE)
    end
end

if (pula_linha) then
    print(saudacao)
    print(mensagem)
else
    print(saudacao .. " " .. mensagem)
end
# Exemplos de uso:
# godot -s script.gd
# godot -s script.gd --pula-linha --saudacao=Oi. --mensagem="Meu nome é X"
# godot -s script.gd --mensagem="Meu nome é X"
# godot -s script.gd --saudacao=Oi. --pula-linha

extends SceneTree

const EXIT_FAILURE = 1

func _init():
    var pula_linha = false
    var saudacao = "Olá!"
    var mensagem = "Meu nome é Franco."
    var sucesso = true
    var sair = false

    var argumentos = Array(OS.get_cmdline_args())
    argumentos = argumentos.slice(2, argumentos.size())

    for argumento in argumentos:
        if ((argumento.begins_with("--ajuda")) or (argumento.begins_with("-a"))):
            print("Uso:")
            print("--saudacao=...\tUsa o valor escolhido como saudação.")
            print("--mensagem=...\tUsa o valor escolhido como mensagem.")
            print("--pula-linha  \tPula uma linha entre a saudação e a mensagem.")
            print("--ajuda       \tExibe este texto e termina o programa.")
            sair = true

        if ((argumento.begins_with("--saudacao=")) or (argumento.begins_with("-s="))):
            saudacao = argumento.substr(argumento.find("=") + 1)
        elif ((argumento.begins_with("--mensagem=")) or (argumento.begins_with("-m="))):
            mensagem = argumento.substr(argumento.find("=") + 1)
        elif ((argumento == "--pula-linha") or (argumento == "-n")):
            pula_linha = true
        else:
            print("Erro:")
            print("Opção desconhecida: " + argumento)
            sucesso = false

    if (sucesso):
        if (not sair):
            if (pula_linha):
                print(saudacao)
                print(mensagem)
            else:
                print(saudacao + " " + mensagem)

        quit()
    else:
        quit(EXIT_FAILURE)

Após os tópicos anteriores, a implementação deve ser simples. O único detalhe especial é que, em algumas linguagens, iniciou-se a iteração na posição que efetivamente armazena o primeiro argumento de linha de comando recebido.

Em caso de erro, termina-se a execução com um valor diferente de zero, como comentando sobre EXIT_FAILURE em Arquivos e Serialização (Marshalling). Quando se usa de programas em linha de comando, isso é útil para cancelar a execução de programas em seqüência caso um deles falhe. A versão em JavaScript utiliza process.exit() (documentação) para terminar o processo.

A versão com espaço é similar, mas requer iteração sobre os índices do vetor para a leitura do valor seguinte (quanto necessário). Tente implementá-la.

Além disso, em uma leitura atenta, é possível constatar que o código para extração de valores de parâmetros repete-se. Para evitar a repetição, poder-se-ia implementar uma função que extraísse o valor caso ele coincidisse com o esperado para a chave. Bastaria introduzir a função e refatorar o código.

Correção para Parâmetros Ajustados pelo Editor em GDScript

A versão em GDScript possui algumas particularidades. Ela usa -n ao invés de -p, pois -p é uma opção padrão do motor Godot. Além disso, caso se ajuste os parâmetros de linha de comando no editor, pode ser necessário acertar o índice do primeiro argumento, e montar manualmente cadeias de caracteres separadas por espaço, mas agrupadas por aspas. Para isso, ao invés de copiar-se o parâmetro recebido, monta-se a cadeia de caracteres até o final das aspas.

extends Node
const EXIT_FAILURE = 1

func _ready():    var pula_linha = false
    var saudacao = "Olá!"
    var mensagem = "Meu nome é Franco."
    var sucesso = true
    var sair = false

    var argumentos = []    var novo_argumento = true    var argumento_com_espacos = null    for argumento in OS.get_cmdline_args():        if (novo_argumento):            if (argumento.find("=\"") == -1):                argumentos.append(argumento)            else:                argumento_com_espacos = argumento.replace("=\"", "=")                novo_argumento = false        else:            argumento_com_espacos += " " + argumento            novo_argumento = argumento.ends_with("\"")            if (novo_argumento):                argumentos.append(argumento_com_espacos.substr(0, len(argumento_com_espacos) - 1))
    for argumento in argumentos:
        if ((argumento.begins_with("--ajuda")) or (argumento.begins_with("-a"))):
            print("Uso:")
            print("--saudacao=...\tUsa o valor escolhido como saudação.")
            print("--mensagem=...\tUsa o valor escolhido como mensagem.")
            print("--pula-linha  \tPula uma linha entre a saudação e a mensagem.")
            print("--ajuda       \tExibe este texto e termina o programa.")
            sair = true

        if ((argumento.begins_with("--saudacao=")) or (argumento.begins_with("-s="))):
            saudacao = argumento.substr(argumento.find("=") + 1)
        elif ((argumento.begins_with("--mensagem=")) or (argumento.begins_with("-m="))):
            mensagem = argumento.substr(argumento.find("=") + 1)
        elif ((argumento == "--pula-linha") or (argumento == "-n")):
            pula_linha = true
        else:
            print("Erro:")
            print("Opção desconhecida: " + argumento)
            sucesso = false

    if (sucesso):
        if (not sair):
            if (pula_linha):
                print(saudacao)
                print(mensagem)
            else:
                print(saudacao + " " + mensagem)

        get_tree().quit()    else:
        get_tree().quit(EXIT_FAILURE)

O exemplo considera a montagem apenas para aspas duplas. Para considerar também aspas simples, basta modificá-lo. Além disso, para uma implementação melhor, padrões intermediários como \" devem ser ignorados, dado que são aspas com caractere de escape.

Integração com Linha de Comando

Em Bibliotecas, apresentou-se um programa chamado Cowsay. O programa fornecia como saída um desenho em ASCII Art de uma vaca (cow, em Inglês) dizendo uma frase passada fornecida como parâmetro.

Para ilustrar uma abordagem híbrida de entrada por parâmetros de linha de comando e entrada interativa, pode-se considerar um programa semelhante com Cowsay. Ao invés de uma vaca, pode-se fazer o desenho de coelhos.

Como GDScript não permite a leitura de valores via terminal, a implementação para a linguagem usará apenas o parâmetro de linha de comando. Nas demais linguagens, caso não se forneça um parâmetro, o programa solicitará a entrada de uma mensagem.

Caso se use leitor de telas para ouvir o conteúdo da página, os comandos ou subrotinas para o desenho do coleto não farão muito sentido. Eles utilizam barras, parênteses, aspas e underscores para desenhar o coelho.

// Exemplos de uso:
// node script.mjs
// mpde script.mjs "Oi! Tudo bem?"

import * as readline from "node:readline/promises"
import { stdin, stdout } from "node:process"

let mensagem = ""
if (process.argv.length < 3) {
    const rl = readline.createInterface({input: stdin, output: stdout})
    mensagem = await rl.question("Digite uma mensagem: ")
    rl.close()
} else {
    mensagem = process.argv[2]
}

if (mensagem === "") {
    mensagem = "Olá, meu nome é Franco!"
}

console.log('')
console.log('        ' + mensagem)
console.log('       /  ')
console.log('(\\_/) /')
console.log('( ^_^)')
console.log('c(")(")')
# Exemplos de uso:
# python script.py
# python script.py "Oi! Tudo bem?"

import sys

mensagem = ""
if (len(sys.argv) < 2):
    mensagem = input("Digite uma mensagem: ")
else:
    mensagem = sys.argv[1]

if (mensagem == ""):
    mensagem = "Olá, meu nome é Franco!"

print('')
print('        ' + mensagem)
print('       /  ')
print('(\\_/) /')
print('( ^_^)')
print('c(")(")')
-- Exemplos de uso:
-- lua script.lua
-- lua script.lua "Oi! Tudo bem?"

local mensagem = ""
if (#arg < 1) then
    io.write("Digite uma mensagem: ")
    mensagem = io.read("*line")
else
    mensagem = arg[1]
end

if (mensagem == "") then
    mensagem = "Olá, meu nome é Franco!"
end

print('')
print('        ' .. mensagem)
print('       /  ')
print('(\\_/) /')
print('( ^_^)')
print('c(")(")')
# Exemplos de uso:
# godot -s script.gd
# godot -s script.gd "Oi! Tudo bem?"

extends SceneTree

func _init():
    var mensagem = "Meu nome é Franco."
    if (len(OS.get_cmdline_args()) > 2):
        mensagem = OS.get_cmdline_args()[2]

    print('')
    print('        ' + mensagem)
    print('       /  ')
    print('(\\_/) /')
    print('( ^_^)')
    print('c(")(")')

    quit()

Uma implementação mais robusta poderia verificar se mensagem possui apenas espaços em branco ao invés de ser uma cadeia de caracteres vazia, como feito em ocasiões anteriores. Contudo, o intuito da implementação é demonstrar que a leitura interativa ocorre apenas caso não se passe um valor pela linha de comando. Isso é feito verificando-se o número de argumentos recebidos da linha de comando pelo programa.

A versão em JavaScript usando Node.js precisa ter extensão .mjs (por exemplo, script.mjs). Caso contrário, deve-se usar require() ao invés de import(). De qualquer forma, a leitura em terminal em Node.js utiliza readline (documentação).

A versão em GDScript funciona apenas em linha de comando. Para configurá-la no editor, precisar-se-ia corrigir os índices recebidos, assim como mencionado na seção anterior.

Para melhorar o programa, pode-se adicionar pares de chaves e valores, ou opções para habilitar ou desabilitar. Por exemplo, poder-se-ia definir parâmetros para personalizar os olhos, desenhar-se um balão contornando a mensagem, ou alterar o desenho feito em ASCII Art.

O próximo bloco apresenta algumas sugestões de variações para o desenho do coelho.

(\)(/)    (\_/)     (\_/)     (\_/)
( ^_^)    ( o_o)    ( ._.)    ( ^.^)
c(")(")   c(")(")   c(")(")   c(")(")

Deve-se lembrar de usar \\ para se escrever uma contrabarra.

Usando o Programa Com Pipes

Terminado o programa, pode-se usá-lo em conjunto com outras ferramentas de linha de comando do sistema. Por exemplo, em um sistema Linux, poder-se-ia fazer:

uname -a | python script.py
Digite uma mensagem:
        Linux darkstar-6700k 5.16.5-arch1-1 #1 SMP PREEMPT Tue, 01 Feb 2022 21:42:50 +0000 x86_64 GNU/Linux
       /
(\_/) /
( ^_^)
c(")(")

Para que o coelho diga a data e horário em um sistema Linux:

date | lua script.lua
Digite uma mensagem:
        qui 10 fev 2022 16:32:40 -03
       /
(\_/) /
( ^_^)
c(")(")

Para usar as versões escritas para outras linguagens, bastaria alterar a chamada.

Sistemas Unix-like como Linux seguem a filosofia Unix. Assim, existem inúmeras possibilidades para integrar programas com entrada de linha de comando com as demais ferramentas CLI.

Em outros sistemas, como Windows e macOS, basta adaptar os exemplos anteriores com comandos equivalentes. Por exemplo, o interpretador cmd para Windows fornece os comandos date e time para se obter, respectivamente, a data e a hora do sistema. date /t e time /t fornecem a data e hora sem solicitar novos valores para alterar os atuais. Assumindo-se que lua (ou lua.exe) esteja disponível no PATH, o próximo bloco fornece um exemplo de uso.

date /t | lua script.lua
Digite uma mensagem:
         12/02/2022
       /
(\_/) /
( ^_^)
c(")(")

Para combinar data e hora, poder-se-ia usar variáveis em cmd e concatenar os resultados intermediários. Outra possibilidade é usar echo com %date% e %time, que são variáveis que fornecem a data e o tempo atual, respectivamente.

echo %date% %time% | lua script.lua
Digite uma mensagem:
         12/02/2022 12:21:39,12
       /
(\_/) /
( ^_^)
c(")(")

Alternativamente, pode-se usar Get-Date em PowerShell (ao invés de cmd).

Adicionando um Modo Silencioso

Programas de linha de comando por vezes oferecem um modo chamado quiet, inibindo a escrita dados na saída padrão (stdout). A opção costuma chamar-se --quiet ou -q. Por curiosidade, o contrário da opção chama-se modo verboso ou verbose, com opção --verbose; -v costuma ser igual à --version para versão.

Para uma versão em Português a título de exemplo, pode-se optar por nomear a opção como --silencioso (ou -s). Um modo silencioso real não faz sentido para o programa, já que o propósito é escrever uma mensagem em conjunto com ASCII Art. Contudo, a idéia é aplicável em um ponto particular. Na versão original, a entrada via pipe utiliza a entrada padrão (stdin), e, portanto, apresenta a mensagem que seria destinada à pessoa que usa o programa.

Embora seja possível remover a mensagem, outra alternativa seria adicionar um parâmetro como uma flag que, caso ativa, não escreva o texto de solicitação de entrada. O exemplo não será implementado para GDScript, pois a linguagem não permite leitura de valores em terminal.

// Exemplos de uso:
// node script.mjs
// node script.mjs --silencioso
// mpde script.mjs "Oi! Tudo bem?"

import * as readline from "node:readline/promises"
import { stdin, stdout } from "node:process"

let exibir_mensagem = true

let mensagem = ""
let argc = process.argv.length
if (argc >= 3) {
    let argumento = process.argv[2]
    if ((argumento === "--silencioso") || (argumento === "-s")) {
        exibir_mensagem = false
        if (argc > 3) {
            mensagem = process.argv[3]
        }
    } else {
        mensagem = argumento
    }
}

if (mensagem === "") {
    if (exibir_mensagem) {
        let rl = readline.createInterface({input: stdin, output: stdout})
        mensagem = await rl.question("Digite uma mensagem: ")
        rl.close()
    } else {
        let rl = readline.createInterface({input: stdin})
        mensagem = await rl.question("")
        rl.close()
    }
}

if (mensagem === "") {
    mensagem = "Olá, meu nome é Franco!"
}

console.log('        ' + mensagem)
console.log('       /  ')
console.log('(\\_/) /')
console.log('( ^_^)')
console.log('c(")(")')
# Exemplos de uso:
# python script.py
# python script.py --silencioso
# python script.py "Oi! Tudo bem?"

import sys

exibir_mensagem = True

mensagem = ""
argc = len(sys.argv)
if (argc >= 2):
    argumento = sys.argv[1]
    if ((argumento == "--silencioso") or (argumento == "-s")):
        exibir_mensagem = False
        if (argc > 2):
            mensagem = sys.argv[2]
    else:
        mensagem = argumento

if (mensagem == ""):
    if (exibir_mensagem):
        print("Digite uma mensagem: ", end="")

    mensagem = input()

if (mensagem == ""):
    mensagem = "Olá, meu nome é Franco!"

print('        ' + mensagem)
print('       /  ')
print('(\\_/) /')
print('( ^_^)')
print('c(")(")')
-- Exemplos de uso:
-- lua script.lua
-- lua script.lua --silencioso
-- lua script.lua "Oi! Tudo bem?"

local exibir_mensagem = true

local mensagem = ""
local argc = #arg
if (argc >= 1) then
    local argumento = arg[1]
    if ((argumento == "--silencioso") or (argumento == "-s")) then
        exibir_mensagem = false
        if (argc >= 2)  then
            mensagem = arg[2]
        end
    else
        mensagem = argumento
    end
end

if (mensagem == "") then
    if (exibir_mensagem) then
        io.write("Digite uma mensagem: ")
    end

    mensagem = io.read("*line")
end

if (mensagem == "") then
    mensagem = "Olá, meu nome é Franco!"
end

print('        ' .. mensagem)
print('       /  ')
print('(\\_/) /')
print('( ^_^)')
print('c(")(")')

A versão em JavaScript omite a configuração de output para não ecoar a mensagem no modo silencioso. Agora, bastaria usar o parâmetro --silencioso ou -s para ocultar a mensagem quando se usa o pipe.

echo "O silêncio passou por aqui..." | lua script.lua --silencioso
        O silêncio passou por aqui...
       /
(\_/) /
( ^_^)
c(")(")

echo "O silêncio passou por aqui..." | python script.py --silencioso
        O silêncio passou por aqui...
       /
(\_/) /
( ^_^)
c(")(")

echo "O silêncio passou por aqui..." | node script.mjs --silencioso
        O silêncio passou por aqui...
       /
(\_/) /
( ^_^)
c(")(")

A adição de novas configurações funciona de forma semelhante. Convém observar que a mensagem foi deixada como último parâmetro por conveniência.

Interface Interativa para Terminal (Console) com ncurses

Para um exemplo diferente, o próximo bloco implementa um programa simples usando a biblioteca curses em Python (documentação). Para outra opção de biblioteca similar, pode-se consultar Urwird.

Embora existam versões da biblioteca para outras linguagens, o módulo curses está disponível para uso em Python sem configurações adicionais em sistemas Linux e macOS. Em sistemas Windows, pode-se instalar as dependências necessárias usando-se pip.

pip install windows-curses

O único requisito para uso é usar um terminal (console); o programa não funcionará em um IDE como Thonny. Para um Olá, mundo! usando curses, pode-se criar o seguinte programa:

import curses

def main(tela):
    tela.clear()

    tela.addstr(12, 30, "Olá, meu nome é Franco!", curses.A_REVERSE)
    tela.addstr(curses.LINES - 1, 0, f"A tela possui {curses.LINES - 1} linhas e {curses.COLS - 1} colunas.")

    tela.refresh()
    tela.getkey()

curses.wrapper(main)

A janela (window) ou tela é a região do terminal na qual se pode desenhar. No código:

  • wrapper() é um recurso fornecido pela implementação para Python para inicializar a biblioteca com maior facilidade;
  • clear() limpa a tela;
  • addstr() escreve uma cadeia de caracteres iniciada na linha do primeiro parâmetro, coluna do segundo;
  • LINES fornece o número de linhas da janela e COLS fornece o número de colunas;
  • refresh() atualiza a tela, redesenhando a interface;
  • getkey() bloqueia o fim do programa por meio da espera pela entrada de uma tecla. Assim que se pressionar qualquer tecla (não é necessário apertar enter), o programa terminará. A abordagem é conhecida como modo cbreak.

Cores e Animações com ncurses

A biblioteca fornece recursos adicionais para desenhos em terminal Por exemplo, é possível desenhar linhas ou usar cores. Cores podem ser definidas por pares em init_pair() e escolhidas usando-se color_pair().

import curses
import time

def main(tela):
    curses.curs_set(0)
    # Texto branco, fundo vermelho.
    curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_RED)

    linha = 0
    coluna = 0
    tempo_espera_segundos = 0.3
    repeticoes = 100
    mensagem = "Franco"
    for i in range(repeticoes):
        tela.clear()
        tela.addstr(linha, coluna, mensagem, curses.color_pair(1))
        tela.refresh()

        time.sleep(tempo_espera_segundos)

        # Atualização de posições para animação.
        linhas, colunas = tela.getmaxyx()

        linha += 1
        if (linha >= linhas):
            linha = 0

        coluna += 1
        if (coluna >= colunas - len(mensagem)):
            coluna = 0

    tela.getkey()

curses.wrapper(main)

Assim como feito para o Jogo da Vida (Conway's Game Of Life), pode-se criar animações limpando-se a tela e redesenhando-a (após alterações de valores). Ao invés de uma matriz, pode-se pensar a tela como uma matriz. Alterando-se as posições, a mensagem mudará de posição a cada desenho. Caso se queira considerar eventuais mudanças no tamanho da tela, pode-se usar getmaxyx() para a obtenção do valor atual.

Entrada com ncurses

Tanto getkey() quanto getch() podem ser usadas para a leitura de teclas do teclado. getkey() retorna uma cadeia de caracteres representando o valor lido. getch() retorna um inteiro o código da tecla.

Usando uma das duas funções, é possível alterar o exemplo anterior para controlar o texto de mensagem. Para isso, basta comparar o valor obtido com um possível valor esperado, incrementando ou decrementando a posição conforme necessário.

import curses
import time

def main(tela):
    curses.curs_set(0)
    # Texto branco, fundo vermelho.
    curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_RED)

    linha = 0
    coluna = 0
    tempo_espera_segundos = 0.1
    fim = False
    mensagem = "Franco"
    while (not fim):
        tela.clear()
        tela.addstr(linha, coluna, mensagem, curses.color_pair(1))
        tela.refresh()

        time.sleep(tempo_espera_segundos)

        # Atualização de posições para animação.
        tecla = tela.getch()

        incremento_linha = 0
        incremento_coluna = 0
        if (tecla == curses.KEY_UP):
            incremento_linha -= 1
        elif (tecla == curses.KEY_DOWN):
            incremento_linha += 1
        elif (tecla == curses.KEY_LEFT):
            incremento_coluna -= 1
        elif (tecla == curses.KEY_RIGHT):
            incremento_coluna += 1
        elif (chr(tecla) == "q"):
            fim = True

        linhas, colunas = tela.getmaxyx()

        linha += incremento_linha
        if (linha >= linhas):
            linha = linhas - 1
        elif (linha < 0):
            linha = 0

        coluna += incremento_coluna
        if (coluna >= colunas - len(mensagem)):
            coluna = colunas - len(mensagem) - 1
        elif (coluna < 0):
            coluna = 0

        if (mensagem == "Franco"):
            mensagem = "FRANCO"
        else:
            mensagem = "Franco"

curses.wrapper(main)

No exemplo atualizado, a entrada é bloqueante (blocking). Ou seja, o programa interrompe a execução enquanto aguarda pela entrada. Caso se aperte uma das quatro setas do teclado (para cima, para baixo, para a esquerda ou para a direita), move-se o texto de mensagem na direção escolhida. Caso se aperte q, o programa interromperá o laço para terminar.

Entrada Não-Bloqueante com ncurses

É possível usar nodelay() para alternar para entrada não-bloqueante (non-blocking). Com a configuração, getch() retorna -1 caso não leia nenhum valor.

import curses
import time

def main(tela):
    curses.curs_set(0)
    # Texto branco, fundo vermelho.
    curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_RED)

    tela.nodelay(True)
    linha = 0
    coluna = 0
    tempo_espera_segundos = 0.1
    fim = False
    mensagem = "Franco"
    while (not fim):
        tela.clear()
        tela.addstr(linha, coluna, mensagem, curses.color_pair(1))
        tela.refresh()

        time.sleep(tempo_espera_segundos)

        # Atualização de posições para animação.
        tecla = tela.getch()

        incremento_linha = 0
        incremento_coluna = 0
        if (tecla != -1):            if (tecla == curses.KEY_UP):
                incremento_linha -= 1
            elif (tecla == curses.KEY_DOWN):
                incremento_linha += 1
            elif (tecla == curses.KEY_LEFT):
                incremento_coluna -= 1
            elif (tecla == curses.KEY_RIGHT):
                incremento_coluna += 1
            elif (chr(tecla) == "q"):
                fim = True

        linhas, colunas = tela.getmaxyx()

        linha += incremento_linha
        if (linha >= linhas):
            linha = linhas - 1
        elif (linha < 0):
            linha = 0

        coluna += incremento_coluna
        if (coluna >= colunas - len(mensagem)):
            coluna = colunas - len(mensagem) - 1
        elif (coluna < 0):
            coluna = 0

        if (mensagem == "Franco"):
            mensagem = "FRANCO"
        else:
            mensagem = "Franco"

curses.wrapper(main)

Com as duas alterações anteriores, é possível criar a primeira simulação interativa. O laço continua em execução mesmo caso não se pressione uma tecla. De fato, a mensagem do exemplo alterna entre Franco e FRANCO a cada iteração do laço, com ou sem entrada.

Sistemas interativos de tempo real funcionam dessa forma. Em outras palavras, agora é possível começar a criar simulações e jogos digitais interativos de tempo real para terminais (ou consoles). Não existe mais a restrição da criação de jogos interativos em turnos ou de simulações não-interativas de tempo real que eram impostas pelo bloqueio decorrente da leitura de dados. Com uma biblioteca como ncurses, é possível criar simulações e jogos interativos com os conceitos aprendidos até agora.

Área de Texto com ncurses

Ela também provê uma área de texto para entrada de texto, descrito no tutorial. O código do tutorial está adaptado no próximo exemplo, que define uma área de texto dentro da janela criada.

import curses
import curses.textpad

import locale
locale.setlocale(locale.LC_ALL, "") # "pt_BR.utf8"
code = locale.getpreferredencoding()

def validator(key):
    return key

def main(tela):
    instrucoes = [
            "Instruções:",
            "- Ctrl G: sair",
            "- Ctrl H: apagar último caractere",
            "- Ctrl L: atualizar tela",
    ]
    instrucoes_linhas = len(instrucoes)
    linha = 0
    for instrucao in instrucoes:
        tela.addstr(linha, 0, instrucao)
        linha += 1

    area_texto_linhas = curses.LINES - 6
    area_texto_colunas = curses.COLS - 4
    area_texto = curses.newwin(area_texto_linhas, area_texto_colunas, instrucoes_linhas + 1, 1)
    curses.textpad.rectangle(tela, instrucoes_linhas, 0, area_texto_linhas + 5, area_texto_colunas + 2)
    tela.refresh()

    box = curses.textpad.Textbox(area_texto)
    box.edit(validator)
    mensagem = box.gather()

    # Exibida após pressionar-se Ctrl G.
    tela.clear()
    tela.addstr(0, 0, mensagem)
    tela.refresh()

    tela.getkey()

curses.wrapper(main)

A área para entrada de texto possui uma limitação. A inspeção do código-fonte revela que o editor de texto pré-definido aceita apenas caracteres codificados em ASCII. A próxima subseção demonstra uma forma de exibir um caractere correspondente sem o acento.

Trocando Caracteres UTF-8 por ASCII Sem Acentos

Apenas como curiosidade, com um pouco de criatividade e conhecimento sobre codificação UTF-8, pode-se processar os valores recebidos como entrada para remover os acentos. Isso permitir exibir caracteres não acentuados ao invés dos recebidos na Textbox.

import curses
import curses.textpad
import struct

import locale
locale.setlocale(locale.LC_ALL, "") # "pt_BR.utf8"
code = locale.getpreferredencoding()

REMOVER_ACENTOS = {
    "á": ord("a"),
    "à": ord("a"),
    "ã": ord("a"),
    "é": ord("e"),
    "ê": ord("e"),
    "í": ord("i"),
    "ó": ord("o"),
    "ò": ord("o"),
    "ô": ord("o"),
    "õ": ord("o"),
    "ü": ord("u"),
    "ç": ord("c"),
    "Á": ord("A"),
    "À": ord("A"),
    "Ã": ord("A"),
    "É": ord("E"),
    "Ê": ord("E"),
    "Í": ord("I"),
    "Ó": ord("O"),
    "Ò": ord("O"),
    "Ô": ord("O"),
    "Õ": ord("O"),
    "Ü": ord("U"),
    "Ç": ord("C"),
}

caractere_nao_ascii = []

def validator(key):
    if (key >= 128):
        # Valor não pertence à tabela ASCII.
        global caractere_nao_ascii
        caractere_nao_ascii.append(key)
        if (len(caractere_nao_ascii) == 1):
            return None
        else:
            # <H é um unsigned short (inteiro de 2 bytes) em ordem little-endian.
            valor_utf8 = struct.pack("<H", caractere_nao_ascii[1] * 256 + caractere_nao_ascii[0]).decode("utf8")
            caractere_nao_ascii = []

            if (valor_utf8 in REMOVER_ACENTOS):
                return REMOVER_ACENTOS[valor_utf8]
            else:
                # NOTE O ideal seria registrar a omissão para adicionar o valor
                # à tabela.
                return None

    return key

def main(tela):
    instrucoes = [
            "Instruções:",
            "- Ctrl G: sair",
            "- Ctrl H: apagar último caractere",
            "- Ctrl L: atualizar tela",
    ]
    instrucoes_linhas = len(instrucoes)
    linha = 0
    for instrucao in instrucoes:
        tela.addstr(linha, 0, instrucao)
        linha += 1

    area_texto_linhas = curses.LINES - 6
    area_texto_colunas = curses.COLS - 4
    area_texto = curses.newwin(area_texto_linhas, area_texto_colunas, instrucoes_linhas + 1, 1)
    curses.textpad.rectangle(tela, instrucoes_linhas, 0, area_texto_linhas + 5, area_texto_colunas + 2)
    tela.refresh()

    box = curses.textpad.Textbox(area_texto)
    box.edit(validator)
    mensagem = box.gather()

    # Exibida após pressionar-se Ctrl G.
    tela.clear()
    tela.addstr(0, 0, mensagem)
    tela.refresh()

    tela.getkey()

curses.wrapper(main)

A lista de caracteres acentuados não é completa, mas serve como exemplo. O princípio da solução é obter os valores lidos do teclado como bytes e montar o caractere UTF-8 correspondente à seqüencia de bytes. Em seguida, converte-se o valor obtido para o valor ASCII sem acento.

A rigor, um caractere UTF-8 pode ter até quatro bytes. A solução atual funciona apenas para caracteres até dois bytes. Para que a solução fosse aplicável para qualquer caractere, dever-se-ia considerar valores iniciais e de continuidade. Caso se quisesse, também seria possível evitar o uso da variável global.

A melhor forma de resolver o problema, contudo, seria criar uma implementação própria para um campo de texto ou modificar o código original de curses.textpad.Textbox. Ao invés de tentar contornar o problema com gambiarras, a abordagem é corrigir o problema original com uma solução limpa.

Gambiarras podem resolver um problema, mas tendem a criar outros. No exemplo, a alteração compromete o funcionamento da funcionalidade de apagar o último caractere. Embora seja possível tratar o caso, seria melhor corrigir o código de Textbox. Por exemplo, curses possui métodos para trabalhar com wide char, que teria suporte para acentos.

De qualquer forma, o exemplo serve como prova de conceito. Quando se conhece os fundamentos da programação, pode-se criar alternativas para limitações de bibliotecas explorando-se as definições e conceitos fundamentais.

Exportando Seu Programa para o PATH

Para usar seus programas de linha de comando com maior conveniência, isto é, sempre precisar troca de diretório ou usar o caminho absoluto para o arquivo com o script, você pode gerar shell scripts e adicioná-los ao PATH de seu sistema. Um shell script é um arquivo de código para uso com interpretadores de linha de comando. Instruções para configuração do PATH estão disponíveis em Instalação de Programas.

Uma forma prática de exportar shell scripts consiste em criar um diretório para seus scripts. Em seguida, basta adicionar o diretório ao PATH. Por exemplo, o diretório para programas de linha de comando escritos em Python poderia ser C:\Users\Franco\Scripts. Esse seria o diretório para se adicionar ao PATH. O diretório não precisa ser o mesmo do código-fonte (por exemplo, o código poderia estar em C:\Users\Franco\Python\).

O próximo passo é criar o shell script. Em Windows, pode-se criar um arquivo com a extensão .bat como a seguir:

@ECHO OFF

REM Troque pelo diretório desejado.
set PATH="C:\Program Files\Python39";%PATH%
python "C:\Users\Franco\Python\script.py"

Em seguida, pode-se clicar duas vezes sobre o arquivo .bat criado para executar o programa. Caso se adicionasse o diretório com o script ao PATH, seria possível usar o comando nome_script.bat para executar o programa de qualquer diretório em um interpretador de linha de comando.

O mesmo pode ser feito em sistemas Linux e macOS. Por exemplo, em Linux, pode-se criar um script .sh:

#!/bin/bash

# Troque pelo diretório desejado.
export PATH="/home/franco/bin/:$PATH"
python "/home/franco/Python/script.py"

Em sistemas Linux, é provável que programas instalados usando gerenciadores de pacotes já estejam disponíveis no PATH. Para um exemplo mais genérico, o script anterior assume que o programa necessário esteja em /home/franco/bin/.

Com os passos anteriores, seu script torna-se um comando do sistema. Para torná-los mais sofisticados, é possível passar argumentos de linha de comando ao programa. Isso pode variar de acordo como o interpretador de linha de comando adotado.

Caso tenha interesse, pesquise em como fazê-lo. A próxima subseção apresenta exemplos para Linux e Windows. A versão para Linux provavelmente funcionará em macOS, caso se use BASH como interpretador de linha de comando.

Exemplo: Bunnysay

Em sistemas Linux usando BASH, pode-se usar o exemplo do texto dito pelo coelho para criar o programa bunnysay (em analogia ao cowsay: bunny significa coelho em Inglês). O exemplo utiliza a versão em Lua. O primeiro passo é criar um arquivo chamado bunnysay.sh.

#!/bin/bash

lua "/home/franco/Lua/script.lua" "$@"

O exemplo assume que o código do programa chama-se script.lua e esteja no diretório /home/franco/Lua/. O parâmetro $@ repassa os valores recebidos pelo shell script como argumentos de linha de comando para o interpretador lua.

Em seguida, deve-se adicionar permissão de execução para o arquivo:

chmod u+x bunnysay.sh

O uso do comando chmod anterior adicionar permissão de execução para a conta de usuário atual ao arquivo bunnysay.sh. Caso não se forneça a permissão, não se pode executar o arquivo como um programa.

O programa está pronto para uso. Caso se queira deixá-lo disponível no PATH, pode-se adicionar o diretório com o shell script ao PATH. Caso contrário, é possível exportar o diretório com o shell script localmente em uma sessão do interpretador de comando.

export PATH="/home/franco/Lua/:$PATH"
bunnysay.sh "Olá, meu nome é Franco\!"
date | bunnysay.sh --silencioso
uname-a | bunnysay.sh --silencioso

O resultado é um programa acessível no sistema (ou, no caso anterior, dentro da sessão do terminal). Para se escrever apenas bunnysay ao invés de bunnysay.sh, bastaria alterar o arquivo original ou criar um link simbólico como atalho.

Bunnysay em Windows

A versão para Windows é similar. Neste caso, cria-se um arquivo como bunnysay.bat.

@ECHO OFF

set PATH="C:\Users\Franco\Desktop\lua";%PATH%
lua54.exe "C:\Users\Franco\script.lua" %*

O exemplo anterior assume que o interpretador Lua esteja no arquivo lua54.exe, armazenado no diretório lua da Área de Trabalho da conta de usuário Franco. O código do programa está no diretório pessoal do mesmo usuário.

No script, pode-se escrever lua54.exe ou lua54; caso se omita a extensão, o Windows procura pelas duas possibilidades. O parâmetro %* repassa todos os argumentos recebidos pelo script bunnysay.bat para o programa escrito em Lua.

A forma de usar o programa varia de acordo com o interpretador de comando. Assumindo-se que o interpretador esteja no diretório do programa:

  • Em cmd, pode-se usar bunnysay.bat ou bunnysay;
  • Em PowerShell, pode-se usar .\bunnysay.bat ou \.bunnysay.

A passagem de parâmetros é feita como anteriormente.

Assim como em Linux, pode-se adicionar o diretório do programa ao PATH. Alternativamente, pode-se exportar o diretório como parte de outro script, ou chamar o bunnysay.bat pelo caminho absoluto como parte de um novo script. A última possibilidade pode ser interessante porque permite executar diretamente o script criado. O próximo exemplo assume que bunnysay.bat esteja na Área de Trabalho da conta de usuário Franco.

@ECHO OFF

call "C:\Users\Franco\Desktop\bunnysay.bat" --silencioso "Olá, meu nome é Franco!"
pause

Salvando-se o código anterior em um arquivo como teste.bat, pode-se clicar duas vezes sobre o script para executá-lo. O resultado será apresentado na janela que abrir. Na implementação, usa-se call para executar o código do script. Para evitar que a janela feche após a execução, usa-se pause para aguardar a entrada de uma tecla.

Novos Itens para Seu Inventário

Ferramentas:

  • Interpretadores de linha de comando;
  • Pipe em console (terminal);
  • Redirecionamento de entrada e saída em console (terminal);
  • Seus próprios shell scripts e programas.

Habilidades:

  • Passagem de parâmetros para programas de linha de comando;
  • Tratamento de entrada recebida como argumentos de linha de comando;
  • Criação de simulação de tempo real.

Conceitos:

  • Entrada interativa;
  • Entrada não-interativa;
  • Pipe;
  • Graphical User Interface (GUI);
  • Text-based User Interface (TUI);
  • Command-line interface (CLI);
  • Filosofia Unix;
  • Requisição para Web;
  • Parâmetros de URL;
  • Sistema interativo de tempo real;
  • Entrada bloqueante;
  • Entrada não-bloqueante.

Recursos de programação:

  • Uso de argumentos de linha de comando;
  • Shell script;
  • Entrada não-bloqueante.

Pratique

  1. Escreva um calculadora simples para linha de comando. Ela deve receber três valores da linha de comando:

    1. O primeiro valor do cálculo;
    2. O operador (por exemplo, +, -, * e / para as quatro operações aritméticas básicas);
    3. O segundo valor do cálculo.

    Um exemplo de uso seria calculadora 1 + 1, com 2 com saída esperada.

    Dica. Lembre-se de converter os valores para números. Os argumentos serão recebidos como cadeias de caracteres.

  2. Aprimore a calculadora. Caso ela seja usada como calculadora, solicite os parâmetros necessários pela entrada padrão (stdin). Caso sejam fornecidos um ou dois valores, solicite apenas os restantes. Caso se forneçam mais valores, realize o cálculo com todos os valores. Por exemplo, calculadora 1 + 2 + 3 + 4 deve fornecer 10 como resultado. Defina regras de prioridade para operadores.

  3. Utilize pipe para combinar operações da sua calculadora.

  4. Crie um diário para linha de comando. O diário deve armazenar valores recebidos em um arquivo. Opções de parâmetros para uso podem incluir:

    • a ou --arquivo: o arquivo que armazenará o valor recebido no restante do comando. Caso omitido, pode-se usar um arquivo padrão;
    • -n ou --novo: adiciona uma mensagem ao diário;
    • -u ou --ultima: exibir a última mensagem adicionada (por exemplo, pode ser a última linha do arquivo);
    • -l ou --lista: mostrar todas as mensagens salvas no diário.
  5. Um programa tradicional de linha de comando chama-se fortune. O programa funciona como um simulador de mensagens de biscoito da sorte. Ao usar o comando fortune, ele exibe uma mensagem aleatória. Além disso, o programa possui parâmetros que permitem filtrar mensagens por categoria (para conhecer algumas delas, pode-se ler o manual; em inglês).

    Crie sua própria versão de fortune com algumas categorias.

  6. Utilize o exemplo do coelho para escrever frases em ASCII Art com as saídas de um dos programas anteriores (ou qualquer outro de sua preferência). Por exemplo, você pode criar o "coelho da sorte".

  7. Tente criar um shell script para um dos programas de linha de comando que você escreveu.

Próximos Passos

Este tópico sobre parâmetros para linha de comando possivelmente não será um dos mais populares deste website. Contudo, ele certamente recompensará as pessoas que dedicarem tempo para aprendê-lo. De certa forma, programas CLI são como bibliotecas de programação para o sistema operacional. Em potencial, cada novo programa que se aprende a usar em linha de comando soma-se a todos os outros que já se conhece. Isso pode permitir resolver tarefas complexas como combinações de comandos mais simples.

Muitas pessoas associam a linha de comando ao sistema operacional Linux. De fato, Linux possui ótimos interpretadores e recursos para uso de linha de comando. Para pessoas que desejam expandir o conhecimento sobre linha de comando, o Guia Foca é uma referência tradicional escrita em Português. Ele possui versões para pessoas iniciantes, intermediárias e avançadas em sistemas Linux. Assim, se você nunca usou uma distribuição Linux, mas gostaria de saber mais, você pode começar pela versão básica do Guia. Para acompanhá-lo, você pode escolher uma das distribuições como as sugeridas a seguir:

Entretanto, todos os sistemas operacionais populares para computadores de mesa fornecem acesso a uma linha de comando. Particularmente, o autor prefere trabalhar com ferramentas GNU mesmo em sistemas que não sejam Linux. Por exemplo, versões recentes do Windows facilitaram o uso de um ambiente Linux (dentro no Windows) por meio do Subsistema Windows para Linux. Assim, é possível usar um ambiente Linux dentro do Windows. A instalação de git para Windows também fornece um ambiente de linha de comando com ferramentas compatíveis ou equivalentes com as fornecidas pelo projeto GNU.

De qualquer forma, muitas linguagens de programação possuem um recurso básico ainda inexplorado: operações bit-a-bit. Embora elas raramente sejam usadas em programação em alto nível, elas são úteis para programação em baixo nível. Convém, pois, conhecê-las.

  1. Introdução;
  2. Ponto de entrada e estrutura de programa;
  3. Saída (para console ou terminal);
  4. Tipos de dados;
  5. Variáveis e constantes;
  6. Entrada (para console ou terminal);
  7. Aritmética e Matemática básica;
  8. Operações relacionais e comparações;
  9. Operações lógicas e Álgebra Booleana;
  10. Estruturas de condição (ou condicionais ou de seleção);
  11. Subrotinas: funções e procedimentos;
  12. Estruturas de repetição (ou laços ou loops);
  13. Vetores (arrays), cadeias de caracteres (strings), coleções (collections) e estruturas de dados;
  14. Registros (structs ou records);
  15. Arquivos e serialização (serialization ou marshalling);
  16. Bibliotecas;
  17. Entrada em linha de comando;
  18. Operações bit-a-bit (bitwise operations);
  19. Testes e depuração.
  • Informática
  • Programação
  • Iniciante
  • Pensamento Computacional
  • Aprenda a Programar
  • Python
  • Lua
  • Javascript
  • Godot
  • Gdscript
  • Fofo