在 zsh 和 Bash 中循环字符串

在 zsh 和 Bash 中循环字符串

我想转换这个 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 -Areadarray使用过,但它有效(对于一个字符输入中包含空格、换行符和/或控制字符的分隔符)。

但简单地做到这一点要容易得多:

 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

相关内容