Lặp lại một chuỗi trong zsh và Bash


7

Tôi muốn chuyển đổi vòng lặp Bash này:

x="one two three"

for i in ${x}
do
    echo ${i}
done

theo cách như vậy để làm việc với cả Bash và zsh

Giải pháp này hoạt động:

x=( one two three )

for i in ${x[@]}
do
    echo ${i}
done

Dù sao tôi đang sửa đổi xtừ một chuỗi thành một mảng.

Có cách nào lặp lại $xtrong zsh khi nó là một chuỗi và theo cách tương thích với Bash không?

Tôi biết về zsh setopt shwordsplitđể mô phỏng Bash, nhưng tôi không thể đặt nó ad hoc cho vòng lặp, vì nó không hoạt động trong Bash.

`

Câu trả lời:


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

Trong zsh, điều này kích hoạt các tùy chọn làm cho nó tương thích hơn với ksh và bash, bao gồm sh_word_split. Trong các shell khác, emulatekhông tồn tại nên điều này không có gì.


8

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ư $varkhô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: readcó 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 $1là 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 ( *, ?[), do đó, một set -flà bắt buộc. Và, chúng ta có thể đặt một biến mảng outarrthay 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 -Ahoặ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 -fphả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.


3

Nếu bạn không ngại sử dụng eval (= evil):

x="one two three"
eval "x=($x)"
for i in ${x[@]}; do 
    echo $i
done

giải pháp "đơn giản hơn" không đơn giản hơn chút nào; ngược lại, nó sẽ rẽ nhánh một quá trình bổ sung (subshell) trong bash. Ưu điểm duy nhất là nó sẽ có thể để xử lý ;, |, (, vv nhưng không * trong xbiến. Giải pháp ban đầu của bạn (sử dụng eval) chỉ là tốt hơn.
mosvy 29/12/18

Làm thế nào là phần đầu tiên (mới) của câu trả lời này tốt hơn câu trả lời tôi đã đăng ?
Isaac

@mosvy (1) Câu trả lời ban đầu (như câu trả lời thứ hai) bị ảnh hưởng bởi Globing. Đó là dễ dàng để tránh (chính xác) bằng cách làm set -f. (2) Nhưng "câu trả lời ban đầu" (với eval) cũng bị ảnh hưởng bởi bước mở rộng thứ hai rất khó giải quyết (tôi sẽ nói: không thể giải quyết). (3) Bạn đang thiếu điểm rằng $(echo "$x")nó hoàn toàn di động đối với tất cả các bourne như đạn pháo. (4) Nếu bạn muốn các giải pháp xử lý chính xác mọi đầu vào bao gồm khoảng trắng, dòng mới và / hoặc ký tự điều khiển, thì: đọc câu trả lời của tôi .
Isaac

@Isaac Rất tiếc, xin lỗi Isaac, tôi thực sự nhận ra phương pháp khác này mà không đọc câu trả lời của bạn có cùng một phương pháp. Bạn phải tin tôi về điều này. Dù sao, tôi đã nâng cao câu trả lời của bạn và bây giờ nó có nhiều hơn so với tôi bây giờ, điều mà tôi nghĩ là xứng đáng. Cũng quay lại chỉnh sửa.
Hielke Walinga
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.