我想转换这个 Bash 循环:
x="one two three"
for i in ${x}
do
echo ${i}
done
以这种方式同时使用 Bash 和 zsh
该解决方案有效:
x=( one two three )
for i in ${x[@]}
do
echo ${i}
done
无论如何,我正在x
从字符串修改为数组。
$x
当 zsh 是字符串并且以与 Bash 兼容的方式时,有没有办法在 zsh 中循环?
我知道 zshsetopt shwordsplit
可以模拟 Bash,但我无法设置它特别指定for 循环,因为它在 Bash 中不起作用。
`
答案1
当 $x 是字符串并且与 Bash 兼容时,有没有办法在 zsh 中循环 $x ?
是的!。 var 扩展在 zsh 中不会被分割(默认情况下),但是命令扩展是。因此,在 Bash 和 zsh 中您都可以使用:
x="one two three"
for i in $( echo "$x" )
do
echo "$i"
done
事实上,上面的代码在所有 Bourne shell 后代中的工作方式都是相同的(但在原始 Bourne 中不是这样,请更改$(…)
为`…`
使其在其中工作)。
上面的代码在通配符和 echo 的使用方面仍然存在一些问题,请继续阅读。
在 zsh 中,像 var 扩展这样的$var
默认情况下不会分割(也不是 glob)。
此代码在 zsh 中没有问题(不会扩展到 pwd 中的所有文件):
var="one * two"
printf "<%s> " ${var}; echo
但也不会将 var 除以 IFS 的值。
对于 zsh,可以通过使用以下任一方法来完成 IFS 上的拆分:
1. Call splitting explicitly: `echo ${=var}`.
2. Set SH_WORD_SPLIT option: `set -y; echo ${var}`.
3. Using read to split to a list of vars (or an array with `-A`).
但这些选项都不能移植到 bash(或除 ksh for 之外的任何其他 shell -A
)。
使用两个 shell 共享的旧语法:read
可能会有所帮助。
但这只能适用于一个字符分隔符(不是 IFS),并且仅当输入字符串中存在分隔符时:
# ksh, zsh and bash(3.0+)
t1(){ set -f;
while read -rd "$delimiter" i; do
echo "|$i|"
done <<<"$x"
}
哪里$1
有一个一个字符分隔符。
*
仍然受到通配字符( 、?
和)扩展的影响,因此需要[
a 。set -f
而且,我们可以设置一个数组变量outarr
:
# for either zsh or ksh
t2(){ set -f; IFS="$delimiter" read -d $'\3' -A outarr < <(printf '%s\3' "$x"); }
bash 也有同样的想法:
# bash
t3(){ local -; set -f; mapfile -td "$1" outarr < <(printf '%s' "$x"); }
set -f
在 bash 函数中使用 恢复了的效果local -
。
这个概念甚至可以扩展到像 dash 这样的有限 shell:
# valid for dash and bash
t4(){ local -; set -f;
while read -r i; do
printf '%s' "$i" | tr "$delimiter"'\n' '\n'"$delimiter"; echo
done <<-EOT
$(echo "$x" | tr "$delimiter"'\n' '\n'"$delimiter")
EOT
}
不<<<
,不<(…)
,不read -A
或readarray
使用过,但它有效(对于一个字符输入中包含空格、换行符和/或控制字符的分隔符)。
但简单地做到这一点要容易得多:
t9(){ set -f; outarr=( $(printf '%s' "$x") ); }
遗憾的是, zsh 不理解,因此必须按如下方式恢复local -
的值:set -f
t99(){ oldset=$(set +o); set -f; outarr=( $( printf '%s' "$x" ) ); eval "$oldset"; }
上述任何函数都可以通过以下方式调用:
IFS=$1 delimiter=$1 $2
其中第一个参数$
是分隔符(和 IFS),第二个参数是要调用的函数(t1,t2,... t9,t99)。该调用仅在函数调用期间设置 IFS 的值,当调用的函数退出时,该值将恢复为其原始值。
答案2
if type emulate >/dev/null 2>/dev/null; then emulate ksh; fi
在 zsh 中,这会激活使其与 ksh 和 bash 更加兼容的选项,包括sh_word_split
.在其他 shell 中,emulate
不存在,所以这不执行任何操作。
答案3
如果你不害怕使用 eval (=邪恶):
x="one two three"
eval "x=($x)"
for i in ${x[@]}; do
echo $i
done