(Bash) For ループが正しく動作しません。

(Bash) For ループが正しく動作しません。

test.sh次の内容を含むbashスクリプトにループがあります。

#!/bin/bash

CHOSEN_NQUEUE=0

foo(){
 for chunk in $(seq 0 $((${CHOSEN_NQUEUE}-1)));
    do
        echo "CHUNK = $(($chunk+1))"
 done
}

bar(){
  CHOSEN_NQUEUE=10
  foo
}

bar

ループはこれまでうまくいきました。プログラムをで実行すると、. test.shループに次のエラーコードが表示されます。

-bash: 0
1
2
3
4
5
6
7
8
9+1: syntax error in expression (error token is "1
2
3
4
5
6
7
8
9+1")

プログラムを次のように実行すると、関数はbash test.sh目的の結果を生成します。

CHUNK = 1
CHUNK = 2
CHUNK = 3
CHUNK = 4
CHUNK = 5
CHUNK = 6
CHUNK = 7
CHUNK = 8
CHUNK = 9
CHUNK = 10

これは大きなプログラムの一部bash program.shです。

特に、単に実行してもfooエラーは発生しません。fooで実行するとbarエラーが発生します。bash program.shこれは、使用するかどうかに関係なく発生します. program.sh

誰もが私が間違っていることを提案できますか? Bashの他の関数内で関数を実行するのは悪い習慣ですか?

誠にありがとうございます!

編集する:

コメントを残してくれた皆さんに感謝します!

配列でselectを使用して問題が発生したことに気づいた後、次のコードを試しました。

select opt in "${options[@]}"

    do
            next=false
            local IFS=@
            case "@${options[*]}@" in
            (*"@$opt@"*)
                    foo
            (*)
                    echo "Invalid option: $REPLY"
                    ;;
            esac
            echo ""
    done


    echo "IFS = $IFS"

問題はIFS=@ループの外側にあるべきではないという事実で発生します。

しかし、このコードを実行してローカルに設定しようとするとIFS、つまりグローバルに変更されるようlocal IFS=@です。IFSコード出力:

IFS = @

なぜこれが起こるのか知っている人がいますか?

もう一度ありがとうございます!

ベストアンサー1

あなたが受け取ったエラーは、複数$chunk行の値(すべて0から9までの数字)があることを示します。単語分割が発生すると、これが発生します。いいえ$(seq ...)結果として発生しますfor

単語の区切りを防ぐ一般的な方法は、拡張の周りに二重引用符を入れてfor chunk in "$(seq ...)"拡張しないようにすることです。しかし、ここではそうではありません。なぜなら、二重引用符を追加すると、とにかく動作することがわかるからです。一部ケース。

しかし、トークン化は常に同じではありません。値に基づいており、デフォルトではIFSスペース、タブ、および改行($' \t\n'Cスタイルの引用を使用)が含まれています。他の内容が含まれている場合、その文字は単語区切り文字として扱われます。

実際に、次を呼び出す前にIFS内部的に修正しました。selectfoo

local IFS=@
case "@${options[*]}@" in
(*"@$opt@"*)
        foo

Bashで変数の範囲がどのように機能するかは、変更されたfoo値も表示されることですIFSlocalこれは、変更がこの関数にのみ表示されるのではなく、このレベルで呼び出されるすべてのサブ関数にも表示されることを意味します。

$ x=999
$ a() { echo "a: x=$x"; }
$ b() { local x=$1; a; }
$ b 123
a: x=123

これはC言語の状況とは異なります。


解決策は、IFS次のように別の変数に保存することです。

local oldifs=$IFS
IFS=@
str="@${options[*]}@"
IFS=$oldifs
case $str in ...

またはサブシェルで変更します(隠されたIFS変更)。

str=$(IFS=@; echo "@${options[*]}@")
case $str in ...

文字列連結を実行する関数を作成することもできます(関数IFSの変更を非表示にする)。変数を名前として渡すには、名前参照のみが必要です。

# join a b c:
# join the values of array 'a' with the character 'b', and put the result in 'c'
join() {
    local -n _arr=$1
    local IFS=$2
    local -n _res=$3
    _res="${_arr[*]}"
}
src=(11 22 33)
join src @ dst
echo "$dst"        # outputs "11@22@33"

(もちろん、これは目的のために少し扱いに​​くく、名前参照は完璧ではありません。nameref~へ関数は同じ名前の変数を参照できません。外部はい(少なくともBash 4では)。コマンド置換を使用するよりも小さい利点は、サブシェルを起動するためにフォークを使用しないことです。 )

または安全のためにIFS必要なときはいつでも(再)設定してください。中foo

foo() {
    local IFS=$' \t\n'      # or just IFS=$'\n'
    for chunk in $(seq ...); do
        ...
}

おすすめ記事