De acordo com as Leis 12.965/2014 e 13.709/2018, que regulam o uso da Internet e o tratamento de dados pessoais no Brasil, ao me inscrever na newsletter do portal DICAS-L, autorizo o envio de notificações por e-mail ou outros meios e declaro estar ciente e concordar com seus Termos de Uso e Política de Privacidade.
Colaboração: Arnaldo Mandel
Data de Publicação: 7 de abril de 2022
Depois que comecei a jogar o wordle, senti necessidade ocasional de juntar minhas pistas e procurar palavras num dicionário. Dava para escrever tudo em uma linha, mas alguma hora resolvi automatizar. O resultado é um script útil, mas o mais interessante é que em poucas linhas ele junta montes de truques do shell, que podem ser úteis em outros contextos.
O wordle é um jogo bastante popular, em que se tenta adivinhar uma palavra alvo de 5 letras; é encontrado para várias línguas, como o original inglês e o português. Ao longo do jogo se recolhem três tipos de pistas, indicadas por cores:
Já existem vários sites que aceitam de alguma forma essas informações e soltam uma lista de palavras que satisfazem todas as restrições; às vezes, o número de possibilidades é surpreendente. Aqui veremos um utilitários desses para a linha de comando.
Ele será chamado com até três parâmetros:
$ wordle-helper info cinzas amarelas
onde cinzas
e amarelas
são as letras cinzas e as amarelas, justapostas.
info
é formada por 5 blocos, correspondentes às 5 letras, que são de três tipos:
.
indicando que não há informação sobre a posição
[]
.
Por exemplo, se a quarta letra é a
(verde), na segunda posição aparecem amarelas e,n
,
e as letras b,d,u,o
são cinzas, podemos chamar
$ wordle-helper .[en].a. bduo en
A ideia principal é submeter uma lista de palavras a filtros
sucessivos e mostrar quem sobrevive. A lista que uso, peguei na rede
e instalei como $HOME/tmp/enable2k
o importante é ser um arquivo
texto, com uma palavra por linha.
O primeiro filtro usa (quase) o primeiro argumento como expressão
regular para busca no arquivo. Só que a expressão certa requer que o
interior dos colchetes seja negado (é o que eu fazia na linha de
comando) - mas é muito chato ficar digitando ^
. Assim, em vez de
$1
, insiro o chapéu dentro de cada colchete, usando uma expansão
de parâmetro, e o primeiro filtro fica
$ grep -w ${1//[[]/[^} $HOME/tmp/enable2k
onde o parâmetro -w
garante que só serão pegas palavras de 5 letras.
O segundo filtro é mais simples, rejeita as letras cinzas
$ grep -v [$2]
Ops, não! Os colchetes são especiais para o shell, e dentro deles não ocorre expansão de parâmetro; solução: colocar os colchetes entre aspas. De quebra, também, para o caso de não haver segundo parâmetro, vamos transformar o comando em algo inócuo, rejeitando linhas que contenham um asterisco.
$ grep -v "[${2-*}]"
O terceiro é o mais complicado. É possível escrever uma expressão
regular que seleciona as palavras contendo todas as letras amarelas,
mas são expressões muito complicadas. Por exemplo, se as amarelas são
ao
, uma expressão simples seria a.*o|o.*a
; três letras pedem
seis trechos e são 24 para 4 letras - ninguém merece. Melhor exigir uma letra por vez:
$ grep a | grep o
e isso se estende fácil para mais letras. Então é preciso produzir
essa sequência de grep
s a partir das amarelas. Acontece que a
substituição do shell é muito limitada para isso. Mas existe o
sed
e a transformação é bastante simples:
echo $3 | sed -e 's/./| grep &/g'
colocando | grep
na frente de cada letra. Juntando tudo, temos a linha
grep -w ${1//[[]/[^} $HOME/tmp/enable2k | grep -v "[$2]" $(echo $3 | sed -e 's/./| grep &/g')
que colocamos num arquivo, com a linha inicial
#!/usr/bin/bash
tornamos executável, chamamos... e erro! Isso porque a saída do
sed
é interpretada como uma sequência de argumentos do segundo
grep
.
Solução: O shell tem o (perigoso) comando eval
, que recebe um string e faz com que o shell
o interprete como se estivesse no arquivo. Então vamos
criar toda a linha de comando como um string, e
entregar para o comando eval
.
eval "grep -w ${1//[[]/[^} $HOME/tmp/enable2k | grep -v [${2:-*}] $(echo $3|sed -e 's/./| grep &/g')"
Pronto, com esta linha temos pronto o auxiliar para wordle em inglês.
Para português é só fazer uma cópia do programa, procurar uma lista de
palavras (uso br-sem-acentos.txt
), e trocar o nome do arquivo na
cópia. O mesmo valendo para outras línguas.
Mas isso é ruim porque se o programa mudar em algum detalhe, tem que
editar todas as cópias. Bem melhor usar um programa só e escolher a
língua através de um parâmetro. Mas é chato colocar mais um parâmetro
além dos três, mais coisa para digitar. Existe, entretanto um truque
bastante usado: um programa pode verificar o nome como foi chamado e usar esse
nome para mudar seu comportamento. No shell, esse nome é o parâmetro 0.
Ele contém o caminho completo, para ficar só o nome, usamos ${0##*/}
.
Antes disso, vamos criar links simbólicos:
$ ln -s wordle-helper whi; ln -s wordle-helper ``wordle-helper` whp
Vamos também criar uma tabela associativa, para não precisar fazer comparações diretas na hora de escolher dicionário:
$ declare -A dic=( [whi]=$HOME/tmp/enable2k [whp]=$HOME/tmp/br-sem-acentos.txt )
e agora o nome do arquivo pode ser selecionado por ${dic[${0##*/}]}
. O programa fica assim:
#!/usr/bin/bash declare -A dic=( [whi]=$HOME/tmp/enable2k [whp]=$HOME/tmp/br-sem-acentos.txt ) eval "grep -w ${#/[123]/}${1//[[]/[^} ${dic[${0##*/}]} | grep -v [${2:-*}] $(echo $3|sed -e 's/./| grep &/g')"
Note que apareceu um ${#/[123]/}
no primeiro grep. Isso expande para um string vazio
se o programa for chamado com algum parâmetro, mas produz um 0 se
o programa for chamado sem parâmetros; neste caso, como nenhuma palavra contem o caractere
0 [carece de fontes]
o primeiro grep não produz nada,
e o programa termina quieto, sem saída.
Agora, whi
é o auxiliar para inglês, whp
é o auxiliar para
português. Quer francês ou espanhol? Simples: procure uma lista de
palavras dessa língua, crie um link simbólico e acrescente ao dic
uma nova entrada, seguindo o modelo das anteriores.
Terminou? Não! Uma sequência de grep
s pode ser substituída por
uma única chamada do sed
, e o wordle-helper
fica assim:
#!/usr/bin/bash declare -A dic=( [whi]=$HOME/tmp/enable2k [whp]=$HOME/tmp/br-sem-acentos.txt ) sed -E -e "/\b${#/[123]/}${1//[[]/[^}\b/!d;/[${2:-*}]/d$(echo $3|sed -e 's/./;\/&\/!d/g')" ${dic[${0##*/}]}
O que aconteceu:
\b
, para o mesmo efeito (valeria também para o grep
).
!d
para descartar as linhas não selecionadas. Isso é usado no
processamento do primeiro e terceiro parâmetros.
abc
, o sed
que o
processa produz ;/a/!d;/b/!d;/c/!d
. Os ;
separam instruções
sucessivas do sed
e sucessivamente as palavras que não
contém cada uma das três letras são descartadas.
Observações
sed
em vez de vários
grep
s.
This policy contains information about your privacy. By posting, you are declaring that you understand this policy:
This policy is subject to change at any time and without notice.
These terms and conditions contain rules about posting comments. By submitting a comment, you are declaring that you agree with these rules:
Failure to comply with these rules may result in being banned from submitting further comments.
These terms and conditions are subject to change at any time and without notice.
Comentários