조건부 Bash: 내 테스트가 정확합니까? 무한 루프가 필요합니다

조건부 Bash: 내 테스트가 정확합니까? 무한 루프가 필요합니다

이미 다른 스레드에서 설명했듯이While 문: 여러 조건을 복합적으로 사용할 수 없습니다.어떻게든 입력 매개변수를 구문 분석하려고 합니다 recursive descent.

안타깝게도 이 스레드의 참가자들은 나보다 지식이 풍부하고 훌륭한 조언을 제공하지만 문제는 지속됩니다.

나는 또한 성공하지 못한 채 콘솔에서 나 자신을 더 탐구했습니다.

내 스크립트에는 다음 두 개의 명령문이 있습니다 while. 이는 분명히 endOfInput함수가 while 루프를 종료하는 데 아무런 영향을 미치지 않기 때문에 문제가 있습니다.

주위를 둘러보면 루프에 들어가지도 않는 경우도 있습니다.

따라서 외부 레이어는 while-statement다음과 같습니다.

while [ $current_token_index -le $ARGC ] && [[ "$current_token" =~ ^[a-zA-Z0-9_-]+$ ]] || ! [[ "$current_token" =~ ^[0-9]{1,2}(\.[0-9]{,2})*$ ]]; do

게다가 여기도 내부다while-statement

while [[ "$current_token_index" -le "$ARGC" ]] && [[ "$current_token" =~ ^[0-9]{1,2}(\.[0-9]{,2})*$ ]]; do

도우미 메서드도 있습니다:

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
}

또한 이 eat!함수는 위치 복사본에서 다음 토큰을 가져올 수 있습니다.

eat! () {
        current_token=${ARGV[$current_token_index]}
        ((current_token_index += 1))
        
        current_char=${current_token:0:1}
}

게다가 나는 선언한다.

# as set by eat!()
current_token=""
current_char=""

current_token_index=0
curent_char_index=0

내 질문은 이 가능성에 관한 것입니다. endOfInput(원래 endOfInput??제가 아는 한 제거했지만 bash에서는 와일드카드 의미를 가지며 문제를 일으킬 수 있습니다. Ruby에서는 대부분의 특수 문자를 식별자 기호로 선택할 수 있습니다. 문제 없습니다. 분명히 다른 프로그래밍 언어에서 온 경우 bash에 많은 경고가 있습니다. 따라서 endOfInput함수의 여러 정의와 while 문의 다른 테스트 또는 비교 구문을 사용하면 함수의 진실성을 올바르게 평가할 수 없습니다. 조건이 성립하는지 여부를 평가합니다. 따라서 방정식의 세 가지가 조건부 성상도의 공식화에 공동으로 책임이 있습니다.

위에 게시된 루프 헤더의 문제점 while은 진입하지 않거나 멈추지 않는다는 것입니다. 테스트 방법 endOfInput이나 그룹화 방법에 따라 다릅니다.

! endOfInput && ...예를 들어 , 방금 교체 하면 ! false && ...while 루프에 들어가고 그렇지 않으면 입력되지 않습니다.

그래서 기능에 뭔가 문제가 있는 것 같아요. 올바르게 평가되지 않습니다. 문제는 1) 의 정의 endOfInput또는 2) 이를 테스트하는 방법, 즉

  • 2.1무슨 시험(예: -eq문자열 비교 =, 산술 연산자 등 ==)

  • 2.2무엇테스트되었습니다. 즉

    • 2.2.1 문자열 "true"/"false"
    • 2.2.2 리터럴로 true합산하기false
    • 2.2.3 0대상 true1대상false
  • 3 이 값을 올바르게 반환하는 방법은 무엇입니까?

    • 3.1 합격return
    • 3.2 패스exit
    • 3.3 패스echo
  • 4 어떤 테스트 구조에 따라 반환 값이 다른 값과 비교됩니까?

    • 4.1 없음, 기능만 실행
    • 4.2 대괄호( [또는 [[명령)
    • 4.3 산술 괄호((...))
    • 4.4 산술 확장$((...))
    • 4.5 하나 이상의 항목의 조합

따라서 의 정의 endOfInput와 그것이 문장의 머리 부분에서 어떻게 사용되는지 살펴보십시오 while. 무엇이 문제일 수 있으며 무한 루프가 발생하는 이유는 무엇입니까? 내 말은, 다른 두 기능은 isWord?작동한다는 뜻입니다.

내 함수 정의는 어떻게 되어야 하며 , 올바르게 평가되도록 endOfInput명령문에서 어떻게 테스트하고 연결해야 합니까 ?while

편집: ilkkachu는 "최소하고 완전하며 검증 가능한 예"를 게시하기를 원했습니다.

다음 내용으로 충분하길 바랍니다.

먼저 get_installed_gems_with_versions설치된 모든 gem과 해당 버전을 연관 배열로 가져오기 위해 호출합니다. 이제 전역 연관 배열이 생겼습니다 installedGems.

그럼 내 생각엔분석하다옵션 --gems는 다음 호출을 통해 설치된 gem과 해당 버전의 선택을 확인하기 위해 parse_gems_with_versions구문 분석 됩니다.parseGemVersions$chosenGemVersions

파싱 ​​부분과 관련된 일부 테스트 코드를 버렸는데, while 루프가 작동하지 않으면 현재 질문과 관련이 없습니다.

코드는 다음과 같습니다.

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
} 

답변1

귀하의 문제는 주로 쉘 스크립트의 부울 유형에 대한 오해라고 생각합니다.

기본적으로 첫 번째 도우미 함수를 다음 함수로 바꿀 수 있습니다.

#!/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

참/거짓 등을 에코하려고 하지 말고 명령( [..]또는 [[..]]) 자체를 테스트하여 올바른 부울(실제로는 정수)을 반환하고 자신만의 참/거짓 구성을 사용하지 마십시오.

또한 항상 ShellCheck(https://www.shellcheck.net/) 스크립트를 보호하거나 디버깅합니다.

set -o nounset( set -u)와 set -o errexit( )를 활용하는 것도 좋은 습관이다 set -e.


귀하의 질문이 업데이트된 버전을 통해 귀하가 C 프로그래밍에 익숙해졌음을 알 수 있습니다. $ARGC, 또는 은 쉘 스크립트에 없습니다. $ARGV대신 다음을 사용하세요.

  • ARGC(매개변수 개수):"$#"

  • ARGV(매개변수 배열):"$@"


예를 들어 다음과 같이 할 수 있습니다.

eat_args ()
{
    for arg in "$@"; do
        current_char=${arg:0:1}
        echo "$current_char"
    done
}
eat_args "$@"

이것은 주어진 각 인수의 첫 글자를 인쇄하기 때문에 나에게는 별로 의미가 없습니다. 건배와 행운을 빕니다.

답변2

해결책은 연산자로 연결된 조건을 그룹화하는 것입니다 ||.

while [[ $current_token_index -le $ARGC ]] && { [[ "$current_token" =~ ^[a-zA-Z0-9_-]+$ ]] || ! [[ "$current_token" =~ ^[0-9]{1,2}(\.[0-9]{,2})*$ ]]; }; do 

관련 정보