Bash while条件: テストは正しいか? 無限ループは

Bash while条件: テストは正しいか? 無限ループは

他のスレッドでもすでに説明したようにwhile 文: 複数の条件の組み合わせを機能させることができません入力引数をある方法で解析しようとしていますrecursive descent

残念なことに、このスレッドの参加者は私よりもはるかに知識が豊富で、良いアドバイスをくれたにもかかわらず、問題は依然として残っています。

また、コンソールでさらにいろいろ調べてみましたが、何も見つかりません。

私のスクリプトには次の 2 つのステートメントがありますが、明らかに関数は while ループの終了に影響を与えないwhileため、問題があります。endOfInput

いろいろ調べてみると、ループに入らないことさえありました。

外側は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 ステートメント内の異なるテストまたは比較構文とともに、関数の複数の定義によって正しく評価されません。したがって、方程式には条件付き星座の形成に関与する 3 つの要素があります。

上に投稿したループ ヘッドの問題whileは、ループ ヘッドが入力されないか、停止しないことです。テスト方法endOfInputやグループ化方法によって異なります。

! endOfInput && ...たとえば、だけを置き換えると! false && ...while ループが開始されますが、それ以外の場合は開始されません。

このため、関数に問題があると推測しています。正しく評価されていません。問題は、1) の定義endOfInput、または 2) どのようにテストされるか、つまり、

  • 2.1 と何のテスト( のようなもの-eq、 のような文字列比較=、 のような算術演算子==)

  • 2.2テストされます。つまり

    • 2.2.1 文字列「true」/「false」
    • 2.2.2trueおよびfalseリテラルとして
    • 2.2.30およびtrue1false
  • 3 この値はどのように正しく返されるのでしょうか?

    • 3.1 作成者return
    • 3.2 作成者exit
    • 3.3 作成者echo
  • 4 返された値はどのようなテスト構造によって他のものと比較されますか?

    • 4.1 なし、関数を実行するだけ
    • 4.2 括弧 ([または[[コマンド)
    • 4.3 算術括弧((...))
    • 4.4 算術展開$((...))
    • 4.5 1つまたは複数を組み合わせたもの

endOfInputでは、 の定義と、それがステートメントの先頭でどのように使用されているかを確認してくださいwhile。 問題は何でしょうか。なぜ無限ループがあるのでしょうか。 つまり、 などの他の 2 つの関数はisWord?機能します。

正しく評価されるようにするには、関数の定義endOfInputと、ステートメント内でのテストおよび連結をどのようにすればよいでしょうか?while

編集: ilkkachu は私に「最小限で、完全で、検証可能な例」を投稿して欲しいと言っていました

以下が十分であることを願います。

get_installed_gems_with_versionsまず、インストールされているすべての gem とそのバージョンを連想配列で取得するために呼び出します。これで、グローバル連想配列ができましたinstalledGems

私は解析するオプション --gems を呼び出すとparse_gems_with_versionsparseGemVersionsインストールされている gem とそのバージョンを選択して解析するためにが呼び出されます。$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

ご存知のとおり、true/false などを echo しようとしないでください。テスト コマンド ([..]または[[..]]) 自体は適切なブール値 (実際には整数) を返します。独自の true/false 構造は使用しないでください。

さらに、常に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 

関連情報