Bash while condição: meu teste está correto? O loop infinito implica

Bash while condição: meu teste está correto? O loop infinito implica

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 descentforma.

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 whileinstruções a seguir em meu script, que são problemáticas, porque obviamente a endOfInputfunçã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 endOfInputfunçã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 whilecabeças de loop postadas acima é que elas não são inseridas ou não param. Dependendo de como eu testo endOfInputou 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 endOfInputou 2) como ele é testado, ou seja,

  • 2.1 comque teste(coisas como -eqcomparação de strings como =operadores aritméticos como ==)

  • 2.2o queé testado. Ou seja

    • 2.2.1 Uma string "verdadeiro"/"falso"
    • 2.2.2 truee falsecomo literais
    • 2.2.3 0para truee 1parafalse
  • 3 Como esse valor é retornado corretamente?

    • 3.1 porreturn
    • 3.2 porexit
    • 3.3 porecho
  • 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 endOfInpute como ela é usada no cabeçalho da whiledeclaraçã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 endOfInpute como deve ser o teste e a concatenação dela na whileinstruçã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_versionspara 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 parseGemVersionspara 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 $ARGVem 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 

informação relacionada