(Bash) For 循环无法正常运行

(Bash) For 循环无法正常运行

我在 bash 脚本中有一个循环,test.sh其内容如下:

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

或者在子 shell 中更改它(隐藏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 中)。与仅使用命令替换相比,这样做的一个小好处是避免使用 fork 来启动子 shell。)

或者,为了安全起见,IFS每次需要时(重新)设置。里面foo

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

相关内容