Có cách nào lặp lại trên $ x trong zsh khi đó là một chuỗi và theo cách tương thích với Bash không?
Đúng!. Một mở rộng var không được phân chia (theo mặc định) trong zsh, nhưng mở rộng lệnh là . Do đó, trong cả Bash và zsh, bạn có thể sử dụng:
x="one two three"
for i in $( echo "$x" )
do
echo "$i"
done
Trong thực tế, mã ở trên hoạt động giống nhau trong tất cả các hậu duệ vỏ Bourne (nhưng không phải trong Bourne gốc, thay đổi $(…)
để `…`
làm cho nó hoạt động ở đó).
Đoạn mã trên vẫn có một số vấn đề với Globing và việc sử dụng tiếng vang, hãy tiếp tục đọc.
Trong zsh, một mở rộng var như $var
không được phân chia (cũng không phải toàn cầu) theo mặc định.
Mã này không có vấn đề (không mở rộng ra tất cả các tệp trong pwd) trong zsh:
var="one * two"
printf "<%s> " ${var}; echo
Nhưng cũng không chia var cho giá trị của IFS.
Đối với zsh, việc phân tách trên IFS có thể được thực hiện bằng cách sử dụng:
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`).
Nhưng không có tùy chọn nào trong số đó là di động để bash (hoặc bất kỳ shell nào khác ngoại trừ ksh cho -A
).
Đi xuống một cú pháp cũ hơn được chia sẻ bởi cả hai shell: read
có thể giúp ích.
Nhưng điều đó chỉ có thể làm việc cho một dấu phân cách ký tự (không phải IFS) và chỉ khi dấu phân cách tồn tại trong chuỗi đầu vào:
# ksh, zsh and bash(3.0+)
t1(){ set -f;
while read -rd "$delimiter" i; do
echo "|$i|"
done <<<"$x"
}
Trong trường hợp $1
là một một ký tự dấu phân cách.
Mà vẫn bị của việc mở rộng các nhân vật globbing ( *
, ?
và [
), do đó, một set -f
là bắt buộc. Và, chúng ta có thể đặt một biến mảng outarr
thay thế:
# for either zsh or ksh
t2(){ set -f; IFS="$delimiter" read -d $'\3' -A outarr < <(printf '%s\3' "$x"); }
Và ý tưởng tương tự cho bash:
# bash
t3(){ local -; set -f; mapfile -td "$1" outarr < <(printf '%s' "$x"); }
Hiệu ứng của set -f
được khôi phục trong hàm bash bằng cách sử dụng local -
.
Khái niệm này thậm chí có thể được mở rộng sang một lớp vỏ giới hạn như dấu gạch ngang:
# 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
}
Không <<<
, không <(…)
và không read -A
hoặc readarray
được sử dụng, nhưng nó hoạt động (đối với một dấu phân cách ký tự ) với khoảng trắng, dòng mới và / hoặc ký tự điều khiển trong đầu vào.
Nhưng nó đơn giản hơn nhiều để làm:
t9(){ set -f; outarr=( $(printf '%s' "$x") ); }
Đáng buồn thay, zsh không hiểu local -
, vì vậy giá trị của set -f
phải được khôi phục như sau:
t99(){ oldset=$(set +o); set -f; outarr=( $( printf '%s' "$x" ) ); eval "$oldset"; }
Bất kỳ chức năng nào ở trên có thể được gọi với:
IFS=$1 delimiter=$1 $2
Trong đó đối số thứ nhất $
là dấu phân cách (và IFS) và đối số thứ hai là hàm cần gọi (t1, t2, Ném t9, t99). Cuộc gọi đó chỉ đặt giá trị của IFS trong khoảng thời gian của lệnh gọi hàm được khôi phục về giá trị ban đầu khi hàm được gọi là thoát.
bash
. Ưu điểm duy nhất là nó sẽ có thể để xử lý;
,|
,(
, vv nhưng không*
trongx
biến. Giải pháp ban đầu của bạn (sử dụng eval) chỉ là tốt hơn.