Bash

在 zsh 和 Bash 中循環字元串

  • June 1, 2019

我想轉換這個 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當它是一個字元串並且以與 Bash 兼容的方式時,有沒有辦法在 zsh 中循環?

我知道 zsh可以setopt shwordsplit模擬 Bash,但我不能為循環設置它,因為它在 Bash 中不起作用。

`

if type emulate >/dev/null 2>/dev/null; then emulate ksh; fi

在 zsh 中,這會啟動使其與 ksh 和 bash 更兼容的選項,包括sh_word_split. 在其他 shell 中,emulate不存在,所以這什麼都不做。

當它是一個字元串並且以與 Bash 兼容的方式時,有沒有辦法在 zsh 中循環 $x?

是的!。var 擴展在 zsh 中不拆分(預設情況下),但命令擴展是. 因此,在 Bash 和 zsh 中,您都可以使用:

x="one two three"

for i in $( echo "$x" )
do
   echo "$i"
done

事實上,上面的程式碼在所有 Bourne shell 後代中的工作方式都是一樣的(但在原始的 Bourne 中不一樣,更改$(…)讓它在那里工作)。

上面的程式碼在 globbing 和 echo 的使用方面仍然存在一些問題,請繼續閱讀。



$var在 zsh 中,預設情況下不會拆分(也不是 glob)這樣的 var 擴展。

此程式碼在 zsh 中沒有問題(不會擴展到 pwd 中的所有文件):

var="one * two"
printf "<%s> " ${var}; echo

但也不會按 IFS 的值拆分 var。

對於 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字元分隔符在哪裡。

這仍然會受到萬用字元 ( *,?[) 擴展的影響,因此 aset -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 -.

這個概念甚至可以擴展到像破折號這樣的有限外殼:

# 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
    } 

No <<<, no<(…)和 no read -Aorreadarray使用,但它適用於輸入中的空格、換行符和/或控製字元(對於一個字元分隔符)。

但是簡單地做起來要容易得多:

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 的值,當被呼叫的函式退出時,該函式呼叫將恢復為其原始值。

引用自:https://unix.stackexchange.com/questions/295033