我在 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
在内部进行了修改:select
foo
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 ...
或者在子 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
...
}