como já expliquei em meu outro tópicoInstrução While: Não é possível fazer com que o composto de múltiplas condições funcioneEstou tentando analisar os argumentos de entrada de uma recursive descent
forma.
Infelizmente, embora os participantes tivessem muito mais conhecimento do que eu e dessem bons conselhos, o problema ainda persiste.
Além disso, vasculhei um pouco mais o console, sem sucesso.
Tenho duas while
instruções a seguir em meu script, que são problemáticas, porque obviamente a endOfInput
função não tem influência no encerramento do loop while.
Eu fuçava e às vezes nem entrava no loop.
Então o exterior while-statement
é o seguinte:
while [ $current_token_index -le $ARGC ] && [[ "$current_token" =~ ^[a-zA-Z0-9_-]+$ ]] || ! [[ "$current_token" =~ ^[0-9]{1,2}(\.[0-9]{,2})*$ ]]; do
Além disso, novamente, aqui está o interiorwhile-statement
while [[ "$current_token_index" -le "$ARGC" ]] && [[ "$current_token" =~ ^[0-9]{1,2}(\.[0-9]{,2})*$ ]]; do
Também os métodos auxiliares:
isWord? () {
local pattern="^[a-zA-Z0-9_-]+$"
if [[ $1 =~ $pattern ]]; then
echo true
else
echo false
fi
}
isVersionNumber? () {
local pattern="^[0-9]{1,2}(\.[0-9]{,2})*$"
if [[ $1 =~ $pattern ]]; then
echo true
else
echo false
fi
}
EO_ARGS=false
function endOfInput? {
if [ $current_token_index -ge $ARGC ]; then
EO_ARGS=true
fi
echo $EO_ARGS
}
Além disso, a eat!
função que nos dá o próximo token de uma cópia do posicional
eat! () {
current_token=${ARGV[$current_token_index]}
((current_token_index += 1))
current_char=${current_token:0:1}
}
E acima disso eu declaro
# as set by eat!()
current_token=""
current_char=""
current_token_index=0
curent_char_index=0
Minha pergunta refere-se à possibilidade de que endOfInput
(originalmente: endOfInput?
mas eu apaguei o ?
como aprendi, no bash ele tem um significado curinga que pode levar a problemas. Em Ruby você pode escolher a maioria dos caracteres especiais como identificadores sem problemas. Obviamente existem muitas advertências no bash se você vier de outras linguagens de programação) ... para que o valor verdade da endOfInput
função não seja avaliado corretamente por meio de múltiplas definições da função junto com diferentes testes ou comparação da sintaxe em uma instrução while com suas próprias exigências para avaliação se uma condição é válida ou não. Portanto, há três coisas na equação que juntas são responsáveis pela formulação da constelação condicional.
O problema com as while
cabeças de loop postadas acima é que elas não são inseridas ou não param. Dependendo de como eu testo endOfInput
ou como eu agrupo.
Se eu substituir ! endOfInput && ...
apenas por, ! false && ...
por exemplo, o loop while será inserido, no outro caso não.
É por isso que presumo que deve haver um problema com a função. Não está avaliado corretamente. O problema pode ser 1) a definição de endOfInput
ou 2) como ele é testado, ou seja,
2.1 comque teste(coisas como
-eq
comparação de strings como=
operadores aritméticos como==
)2.2o queé testado. Ou seja
- 2.2.1 Uma string "verdadeiro"/"falso"
- 2.2.2
true
efalse
como literais - 2.2.3
0
paratrue
e1
parafalse
3 Como esse valor é retornado corretamente?
- 3.1 por
return
- 3.2 por
exit
- 3.3 por
echo
- 3.1 por
4 por qual construção de teste o valor retornado é comparado a outra coisa?
- 4.1 nenhum, apenas executando a função
- 4.2 colchetes (
[
ou[[
comando) - 4.3 parênteses aritméticos
((...))
- 4.4 expansão aritmética
$((...))
- 4,5 um ou vários desses combinados
Então, por favor, dê uma olhada na definição endOfInput
e como ela é usada no cabeçalho da while
declaração. Qual poderia ser o problema, por que existe um loop infinito? Quero dizer, as outras duas funções isWord?
funcionam.
Como deve ser a definição da minha função endOfInput
e como deve ser o teste e a concatenação dela na while
instrução, para que seja avaliada corretamente?
Editar: ilkkachu queria que eu postasse um "exemplo mínimo, completo e verificável"
Espero que o seguinte seja suficiente.
Primeiro eu chamo get_installed_gems_with_versions
para obter todas as gems instaladas e suas versões em um array associativo. Agora tenho o array associativo global installedGems
.
Então eu queroanalisara opção --gems by call parse_gems_with_versions
, que por sua vez chama parseGemVersions
para analisar uma seleção das gemas instaladas e sua versão em$chosenGemVersions
Deixei de lado o código de alguns testes que são relevantes para a parte de análise, mas não para o problema atual se o loop while não funcionar.
Então aqui está o código
get_installed_gems_with_versions () {
unset installedGems
declare -Ag installedGems
local last_key=""
local values=""
while read -r line; do
line=${line##*/}
KEY="${line%-*}"
VALUE="${line##*-}"
if [ -z ${installedGems[$KEY]+x} ]; then
echo "key $KEY doesn't yet exist."
echo "append value $VALUE to key $KEY"
installedGems[$KEY]="$VALUE"
continue
else
echo "key already exists"
echo "append value $VALUE to $KEY if not already exists"
installedGems[$KEY]="${installedGems[$KEY]} $VALUE"
echo "result: ${installedGems[$KEY]}"
fi
done < <(find $directory -maxdepth 1 -type d -regextype posix-extended -regex "^${directory}\/[a-zA-Z0-9]+([-_]?[a-zA-Z0-9]+)*-[0-9]{1,3}(.[0-9]{1,3}){,3}\$")
}
parseGemVersions () {
local version_list
declare -Ag chosenGemVersions
while [[ "$current_token_index" -le "$ARGC" ]] && [[ "$current_token" =~ ^[0-9]{1,2}(\.[0-9]{,2})*$ ]]; do
if versionOfGemInstalled? $gem $current_token; then
if [ -z ${chosenGemVersions[$gem]+x} ]; then
chosenGemVersions[$gem]=$current_token
# continue
else
chosenGemVersions[$gem]="${chosenGemVersions[$gem]} $current_token"
fi
else
parsing_error! "While parsing function $FUNCNAME, current version number $current_token is not installed!"
fi
echo "result: ${chosenGemVersions[$gem]}"
eat!
done
}
parse_gems_with_versions () {
# option --gems
# --gems gem-name-1 1.1.1 1.2.1 1.2.5 gem-name-2 latest gem-name-3 ...
unset gem
unset chosenGemVersions
gem=""
declare -Ag chosenGemVersions
while [ $current_token_index -le $ARGC ] && [[ "$current_token" =~ ^[a-zA-Z0-9_-]+$ ]] || ! [[ "$current_token" =~ ^[0-9]{1,2}(\.[0-9]{,2})*$ ]]; do
if isWord? $current_token && [ ! "$current_token" = "latest" ]; then
if isWord? $current_token; then
if gemInstalled? $current_token; then
# We can conjecture that the word token is in fact a gems' name
gem=$current_token
local version_list
if isWord? $current_token && [ "$current_token" = "latest" ]; then
version_list=(${installedGems[$gem]})
chosenGemVersions[$gem]="${version_list[${#version_list} -1]}"
else
# gets list chosenGemVersions
parseGemVersions
fi
else
parsing_error! "Gem $token_name not installed!"
fi
fi
else
parsing_error! "While parsing function $FUNCNAME, "latest" is not a gemname!"
fi
eat!
done
}
Responder1
Acredito que seu problema esteja principalmente na compreensão incorreta do tipo booleano em scripts de shell.
Você pode basicamente substituir as primeiras funções auxiliares por estas:
#!/bin/bash
set -o nounset
set -o errexit
is_word ()
{
local pattern='^[a-zA-Z0-9_-]+$'
[[ "$1" =~ $pattern ]]
}
is_version_number ()
{
local pattern='^[0-9]{1,2}(\.[0-9]{,2})*$'
[[ "$1" =~ $pattern ]]
}
# testing is_word ()
if is_word 'bla01'; then
echo IS word
else
echo NOT word
fi
# testing is_version_number ()
if is_version_number '1.1'; then
echo IS version number
else
echo NOT version number
fi
Veja bem, não tente ecoar verdadeiro/falso nem nada, o comando de teste ( [..]
ou [[..]]
) em si retorna booleano adequado (na verdade, inteiro), nunca use suas próprias construções verdadeiro/falso.
Além disso, sempre use ShellCheck (https://www.shellcheck.net/) para proteger seus scripts ou depurá-los.
Um bom hábito também é usar set -o nounset
( set -u
) e set -o errexit
( set -e
).
Com a versão atualizada da sua pergunta, vejo que provavelmente você está acostumado com a programação C. Não existe $ARGC
, nem $ARGV
em scripts de shell. Use estes em vez disso:
ARGC
(contagem de argumentos):"$#"
ARGV
(matriz de argumentos):"$@"
Por exemplo, você poderia fazer isso:
eat_args ()
{
for arg in "$@"; do
current_char=${arg:0:1}
echo "$current_char"
done
}
eat_args "$@"
o que não faz muito sentido para mim, pois imprime a primeira letra de cada argumento dado. Felicidades e boa sorte.
Responder2
A solução é agrupar as condições conectadas através do ||
operador.
while [[ $current_token_index -le $ARGC ]] && { [[ "$current_token" =~ ^[a-zA-Z0-9_-]+$ ]] || ! [[ "$current_token" =~ ^[0-9]{1,2}(\.[0-9]{,2})*$ ]]; }; do