我有一个变量,其中包含要在命令中循环的字符串列表,每行一个for...in...do...done
。
我经常在 bourne shell 和 zsh 之间切换。据我所知,默认情况下,zsh 不会在换行符或空格处从字符串中分割单词;伯恩壳确实如此。因此,像 zsh 这样的命令for list_item in $list; do...
会失败,而它们可以在 bourne shell 中工作,或者使用文字文本而不是变量。我尝试在命令中使用IFS=
并引用变量,但似乎无法取得进展。
是否有一个用于循环字符串中的单词/行的单一语法,可以在两个 shell 中不经修改地工作,以便轻松实现?如果不是,最佳实践是什么?
答案1
您可以通过使用 选项显式设置 zsh 的分词行为来与 bash 类似,从而在 bash 和 zsh 之间实现一致的行为shwordsplit
。例如:
set -o shwordsplit
var='foo bar baz'
for item in $var; do
echo "$item"
done
这将使 zsh 根据内部字段分隔符 ( IFS
) 执行分词,就像 bash 一样。
答案2
Bourne shell 已不再使用,并且是 POSIX 之前的版本,并且不符合 POSIX 标准。有一些基于 Bourne shell 且符合 POSIX 标准的 shell:原始 Korn shell(在 ksh93 重写之前),因为 sh 的 POSIX 规范就是基于该 shell,而 bosh 基于 OpenSolaris sh
(本身基于SysV shell 有一些修改,本身基于 Bourne shell)。
FreeBSDsh
基于 Almquist shell(它本身是 SysV shell 的一个大多数兼容的开源克隆,具有一些扩展,包括一些来自 ksh 的扩展,例如$(...)
),但经过修改以使其兼容 POSIX。
在 POSIX 中sh
,要在循环中循环行for
,不能使用 split+glob,因为换行符是空白字符,对于 IFS 分割一个或多个换行符的任何序列充当分隔符,因此类似:
IFS='
' # split on newline
set -o noglob # disable the glob part
for line in $multiline_string; do
printf '<%s>\n' "$line"
don
将跳过空行。另外,zsh
不会对未加引号的参数扩展进行拆分或通配,因此您需要在emulate sh
那里。
您可以使用以下行"$@"
构造数组:$multiline_string
NL='
'
set --
while [ -n "$multiline_string" ]; do
line=${multiline_string%%"$NL"*}
set -- "$@" "$line"
multiline_string=${multiline_string#"$line"}
multiline_string=${multiline_string#"$NL"}
done
for line do
printf '<%s>\n'
done
或者:
printf '%s\n' "$multiline_string" | {
set --
while IFS= read -r line; do
set -- "$@" "$line"
done
for line do
printf '<%s>\n' "$line"
done
}
或者您可以for
使用以下内容构建循环 shell 代码:
eval '
for line in '"$(
awk -v q="'" -F '\n' -v ORS=' ' -- '
BEGIN {
n = split(ARGV[1], line)
for (i = 1; i < n; i++) {
gsub(q, "&\\\\&&", line[i])
print q line[i] q
}
}' "$multiline_string")"'; do
printf "<%s>\n" "$line"
done
'