(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수정된 값도 표시된다는 것입니다 IFS. local이는 변경 사항이 이 함수에만 표시되는 것이 아니라 이 수준에서 호출되는 모든 하위 함수에도 표시된다는 의미입니다.

$ 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
        ...
}

관련 정보