for ループとネストされた if ステートメントを含む Bash 関数が奇妙な結果を返す

for ループとネストされた if ステートメントを含む Bash 関数が奇妙な結果を返す

次のような bash 関数があり、それが私を困らせています。zenity ボックスに次のように入力すると...

従業員ID = 2 カテゴリID = 3

次のような結果が出ました: 2 3

もし...

従業員ID = カテゴリID = 3

2番目のZenityウィンドウが開いたら、2と入力すると次のようになります: 2 3

しかし、私が入ると

従業員ID = 2 カテゴリID =

追加のZenityウィンドウは開かず、次のメッセージが表示されます: 2

私が本当に望んでいるのは、テストを実行した後の 2、3 です。

ここで何が問題なのか誰か知っていますか?

#!/bin/bash


num(){
        emp=$(echo "$1" | awk -F, -v  OFS=, '{print $1 "," $2}')

        IFS=, read -ra array1 <<<"$emp"
        
        p=$(for i in "${array1[@]}"
        
        do
                
                if [[ "${i}" =~ ^[0-9]+$ ]]; then

                        out="${i}"
                elif
                        [[ "${i}" = NULL ]]; then

                        out="${i}"


                else   local var2

                        until [[ ${var2} =~ ^[0-9]+$ ]] || [[ ${var2} = NULL ]]; do

                                var2="$(zenity --forms --title="table salaries_wages" --text "Add a number"  --separator="," \
                                --add-entry="WARNING! You either forgot to enter or didn't enter a number. Please enter a valid number: ")"

                         done

                                out="${var2}"

        fi

        echo "$out"
        
done)
      
        echo "$p"
}

input="$(zenity --forms --title="table salaries_wages" --text="Add a new salaries_wages entry" --separator="," \
        --add-entry="ENTER employeeid: " \
        --add-entry="ENTER categoryid: ")"

num "$input"

答え1

ここで何が問題なのですか?

どのように動作するかという想定は、実際にreadどのように動作するかとは異なりますread。次のコードを Bash で実行します。

how_many () { IFS=, read -ra array1 <<<"$1"; echo "${#array1[@]}"; }
how_many "2,3"
how_many ",3"
how_many "2,"

2、、が表示されます。最後の数字が目立ちます。これは、末尾の区切り文字 (この場合は) がターミネータのように扱われることを意味します2。つまり、その後の空のフィールドは配列に読み込まれず、配列の要素が 1 つ短くなります。コード内でこれが発生すると、最初のフィールドに対してのみループが実行されます。1,readfor i in "${array1[@]}"

解決策としては、余分な ,を末尾のターミネータとして意図的に使用します。すると、read3 番目のフィールドは読み込まれませんが、常に 2 つのフィールドが読み込まれます (2 番目のフィールドが空であっても)。 を追加した場合の違いを確認してください,

how_many () { IFS=, read -ra array1 <<<"$1,"; echo "${#array1[@]}"; }
how_many "2,3"
how_many ",3"
how_many "2,"
how_many ","

出力は2毎回です。

この方法でコードを修正するには、<<<"$emp,"の代わりにを使用します<<<"$emp"


コードを修正すると、elseブロックが複数回実行されると(つまり、両方のフィールドが最初は無効である場合)、動作がおかしくなります。var2変数の再利用また。

以前はこれを避けていたと思いますlocal var2が、ブロック内やループの単一の反復内localではなく、関数内で変数をローカルにします。関数の同じインスタンスで再利用しています。elseforvar2num

関数を一度呼び出しています。内部はvar2常に同じ でvar2、関数の呼び出しの外部localと区別されるだけです。関数の外部で を使用した場合、関数内の は異なります。複数回呼び出した場合、各呼び出しで独自の が使用されます。これらのどちらも発生しません。関数を一度呼び出し、変数を再利用しています。var2var2numvar2そこには2つのフィールドが無効な場合、最初のフィールドに変数が使用され、次に再利用2番目のフィールド用。

validateしかし、ループ内から何らかの関数 (例 ) が呼び出されるようにコードを再構築すると、次のようになります。

for i in "${array1[@]}"; do validate "$i"; …

local var2関数内で使用すればvalidatevar2関数の各呼び出しは別々になります。これがlocal役に立つ方法です。各ループでvalidate新たに呼び出され、そのローカル変数は同じ名前の他の変数との接続なしに新しく初期化されます。ただし、ローカル変数を再利用できます。内部関数を、何かを壊すような方法で再利用します (現在関数var2内で再利用している方法と同様num)。

上記を書いた後、すでにリンクされている回答


p=$(stuff); echo "$p"とほぼ同等でありecho "$(stuff)"、ほとんどの場合 と等価ですstuffこの答えについて詳しく説明されていますvar=$(stuff); echo "$var"

関連情報