Kiểm tra hỗ trợ mảng bằng shell


11

Có cách kiểm tra ngắn gọn nào để hỗ trợ mảng bằng shell giống Bourne cục bộ tại dòng lệnh không?

Điều này luôn luôn có thể:

$ arr=(0 1 2 3);if [ "${arr[2]}" != 2 ];then echo "No array support";fi

hoặc thử nghiệm cho $SHELLvà phiên bản shell:

$ eval $(echo "$SHELL --version") | grep version

và sau đó đọc trang người đàn ông, giả sử tôi có quyền truy cập vào nó. (Ngay cả ở đó, viết từ /bin/bash, tôi giả định rằng tất cả các Bourne giống như vỏ thừa nhận tùy chọn dài --version, khi điều đó phá vỡ cho ksh ví dụ .)

Tôi đang tìm kiếm một thử nghiệm đơn giản có thể không được giám sát và kết hợp trong phần Sử dụng khi bắt đầu tập lệnh hoặc thậm chí trước khi gọi nó.


Tôi giả sử bạn muốn giới hạn vỏ như Bourne?
Stéphane Chazelas

@ StéphaneChazelas: Có, nếu bạn muốn nói (không hoàn toàn) nhóm cốt lõi được tạo thành từ: sh, csh, ksh, tcsh, bash, zsh và những người bạn thân. Tôi không biết vị trí của yash trong chòm sao này.
Cbhihe

2
cshkhông phải là vỏ bourne. tcshcũng không phải là một ( cshvới một số lỗi đã được sửa)
cas

1
Lưu ý rằng đó $SHELLlà trình bao ưa thích của người dùng, giống như $EDITORtrình soạn thảo văn bản ưa thích của anh ta. Nó có rất ít để làm với vỏ hiện đang chạy.
Stéphane Chazelas

1
evaluating đầu ra của $SHELL --versionmã shell không có ý nghĩa.
Stéphane Chazelas

Câu trả lời:


12

Giả sử bạn muốn hạn chế để Bourne giống như vỏ (nhiều tiện ích khác như csh, tcsh, rc, eshoặc fishmảng hỗ trợ nhưng viết một kịch bản tương thích cùng một lúc để Bourne giống như vỏ và những người là phức tạp và thường vô nghĩa vì họ là thông dịch viên cho hoàn toàn khác nhau và ngôn ngữ không tương thích), lưu ý rằng có sự khác biệt đáng kể giữa các lần thực hiện.

Bourne giống như các shell hỗ trợ các mảng là:

  • ksh88(đó là mảng đầu tiên triển khai, ksh88 vẫn được tìm thấy như kshtrên hầu hết các Unice thương mại truyền thống, nơi nó cũng là cơ sở cho sh)

    • mảng là một chiều
    • Mảng được định nghĩa là set -A array foo barhoặc set -A array -- "$var" ...nếu bạn không thể đảm bảo rằng $varsẽ không bắt đầu bằng -hoặc +.
    • Chỉ số mảng bắt đầu tại 0.
    • Các phần tử mảng riêng lẻ được gán là a[1]=value.
    • mảng thì thưa thớt. Đó là a[5]=foosẽ hoạt động ngay cả khi a[0,1,2,3,4]không được thiết lập và sẽ khiến chúng không được đặt.
    • ${a[5]}để truy cập phần tử của chỉ số 5 (không nhất thiết là phần tử thứ 6 nếu mảng thưa thớt). Có 5thể có bất kỳ biểu thức số học.
    • kích thước mảng và chỉ mục được giới hạn (đến 4096).
    • ${#a[@]} là số phần tử được gán trong mảng (không phải là chỉ số được gán lớn nhất).
    • không có cách nào để biết danh sách các mục con được chỉ định (ngoài việc kiểm tra 4096 phần tử riêng lẻ với [[ -n "${a[i]+set}" ]]).
    • $acũng giống như ${a[0]}. Đó là mảng bằng cách nào đó mở rộng các biến vô hướng bằng cách cung cấp cho chúng các giá trị bổ sung.
  • pdkshvà các công cụ phái sinh (đó là cơ sở cho kshvà đôi khi shcủa một số BSD và là triển khai ksh mã nguồn mở duy nhất trước khi nguồn ksh93 được giải phóng):

    Chủ yếu là thích ksh88nhưng lưu ý:

    • Một số triển khai cũ không hỗ trợ set -A array -- foo bar, ( --không cần thiết ở đó).
    • ${#a[@]}là một cộng với chỉ số của chỉ số được gán lớn nhất. ( a[1000]=1; echo "${#a[@]}"xuất ra 1001 mặc dù mảng chỉ có một phần tử.
    • trong các phiên bản mới hơn, kích thước mảng không còn bị giới hạn (ngoài kích thước của số nguyên).
    • phiên bản gần đây của mkshtôi có một vài nhà khai thác thêm nguồn cảm hứng từ bash, ksh93hoặc zshnhư bài tập a la a=(x y), a+=(z), ${!a[@]}để có được danh sách các chỉ số được giao.
  • zsh. zshmảng thường được thiết kế tốt hơn và tận dụng tốt nhất kshcshmảng. Chúng tương tự kshnhưng có sự khác biệt đáng kể:

    • các chỉ số bắt đầu từ 1, không phải 0 (ngoại trừ trong kshmô phỏng), phù hợp với mảng Bourne (tham số vị trí $ @, zshcũng hiển thị dưới dạng mảng $ argv) và cshmảng.
    • chúng là một loại riêng biệt từ các biến bình thường / vô hướng. Các nhà khai thác áp dụng khác nhau cho họ và như bạn thường mong đợi. $akhông giống như ${a[0]}nhưng mở rộng ra các phần tử không trống của mảng ( "${a[@]}"cho tất cả các phần tử như trong ksh).
    • chúng là các mảng bình thường, không phải là các mảng thưa thớt. a[5]=1hoạt động nhưng gán tất cả các phần tử từ 1 đến 4 chuỗi trống nếu chúng không được gán. Vì vậy ${#a[@]}(giống như ${#a}trong ksh là kích thước của phần tử của chỉ số 0) là số phần tử trong mảng chỉ số được gán lớn nhất.
    • mảng kết hợp được hỗ trợ.
    • một số lượng lớn các toán tử để làm việc với các mảng được hỗ trợ, quá lớn để liệt kê ở đây.
    • mảng được định nghĩa là a=(x y). set -A a x ycũng hoạt động, nhưng set -A a -- x ykhông được hỗ trợ trừ khi trong mô phỏng ksh ( --không cần thiết trong mô phỏng zsh).
  • ksh93. (ở đây mô tả các phiên bản mới nhất). ksh93, từ lâu được coi là thử nghiệm có thể được tìm thấy trong ngày càng nhiều hệ thống mà nó đã được phát hành dưới dạng FOSS. Chẳng hạn, đó là /bin/sh(nơi nó thay thế vỏ Bourne, /usr/xpg4/bin/shvỏ POSIX vẫn dựa trên ksh88) và kshcủa Solaris 11. Mảng của nó mở rộng và tăng cường ksh88.

    • a=(x y)có thể được sử dụng để định nghĩa một mảng, nhưng vì a=(...)cũng được sử dụng để xác định các biến hỗn hợp ( a=(foo=bar bar=baz)), a=()không rõ ràng và khai báo một biến ghép, không phải là một mảng.
    • mảng là đa chiều ( a=((0 1) (0 2))) và các phần tử mảng cũng có thể là biến tổng hợp ( a=((a b) (c=d d=f)); echo "${a[1].c}").
    • Một a=([2]=foo [5]=bar)cú pháp có thể được sử dụng để xác định các mảng thưa thớt cùng một lúc.
    • Hạn chế kích thước nâng.
    • Không đến mức zsh, nhưng số lượng lớn các nhà khai thác cũng hỗ trợ để thao tác các mảng.
    • "${!a[@]}" để lấy danh sách các chỉ số mảng.
    • mảng kết hợp cũng được hỗ trợ như một loại riêng biệt.
  • bash. bashlà vỏ của dự án GNU. Nó được sử dụng như shtrên các phiên bản gần đây của OS / X và một số bản phân phối GNU / Linux. bashmảng chủ yếu mô phỏng ksh88những cái có một số tính năng của ksh93zsh.

    • a=(x y)được hỗ trợ. set -A a x y không được hỗ trợ. a=()tạo một mảng trống (không có biến ghép trong bash).
    • "${!a[@]}" cho danh sách các chỉ số.
    • a=([foo]=bar)cú pháp được hỗ trợ cũng như một vài người khác từ ksh93zsh.
    • các bashphiên bản gần đây cũng hỗ trợ các mảng kết hợp như một loại riêng biệt.
  • yash. Đây là một triển khai POSIX sh nhận biết nhiều byte tương đối gần đây. Không sử dụng rộng rãi. Mảng của nó là một API sạch khác tương tự nhưzsh

    • mảng không thưa thớt
    • Chỉ số mảng bắt đầu từ 1
    • được định nghĩa (và khai báo) với a=(var value)
    • các phần tử được chèn, xóa hoặc sửa đổi với arraynội dung
    • array -s a 5 valueđể sửa đổi phần tử thứ 5 sẽ thất bại nếu phần tử đó không được gán trước.
    • số phần tử trong mảng là ${a[#]}, ${#a[@]}là kích thước của các yếu tố như một danh sách.
    • mảng là một loại riêng biệt. Bạn cần a=("$a")xác định lại một biến vô hướng dưới dạng một mảng trước khi bạn có thể thêm hoặc sửa đổi các phần tử.
    • mảng không được hỗ trợ khi được gọi là sh.

Vì vậy, từ đó bạn có thể thấy rằng việc phát hiện hỗ trợ mảng, điều bạn có thể làm với:

if (unset a; set -A a a; eval "a=(a b)"; eval '[ -n "${a[1]}" ]'
   ) > /dev/null 2>&1
then
  array_supported=true
else
  array_supported=false
fi

là không đủ để có thể sử dụng các mảng đó. Bạn cần xác định các lệnh bao bọc để gán các mảng như là toàn bộ và các phần tử riêng lẻ và đảm bảo rằng bạn không cố gắng tạo các mảng thưa thớt.

Giống

unset a
array_elements() { eval "REPLY=\"\${#$1[@]}\""; }
if (set -A a -- a) 2> /dev/null; then
  set -A a -- a b
  case ${a[0]}${a[1]} in
    --) set_array() { eval "shift; set -A $1"' "$@"'; }
        set_array_element() { eval "$1[1+(\$2)]=\$3"; }
        first_indice=0;;
     a) set_array() { eval "shift; set -A $1"' -- "$@"'; }
        set_array_element() { eval "$1[1+(\$2)]=\$3"; }
        first_indice=1;;
   --a) set_array() { eval "shift; set -A $1"' "$@"'; }
        set_array_element() { eval "$1[\$2]=\$3"; }
        first_indice=0;;
    ab) set_array() { eval "shift; set -A $1"' -- "$@"'; }
        set_array_element() { eval "$1[\$2]=\$3"; }
        first_indice=0;;
  esac
elif (eval 'a[5]=x') 2> /dev/null; then
  set_array() { eval "shift; $1=("'"$@")'; }
  set_array_element() { eval "$1[\$2]=\$3"; }
  first_indice=0
elif (eval 'a=(x) && array -s a 1 y && [ "${a[1]}" = y ]') 2> /dev/null; then
  set_array() { eval "shift; $1=("'"$@")'; }
  set_array_element() {
    eval "
      $1=(\${$1+\"\${$1[@]}"'"})
      while [ "$(($2))" -ge  "${'"$1"'[#]}" ]; do
        array -i "$1" "$2" ""
      done'
    array -s -- "$1" "$((1+$2))" "$3"
   }
  array_elements() { eval "REPLY=\${$1[#]}"; }
  first_indice=1
else
  echo >&2 "Array not supported"
fi

Và sau đó bạn truy cập vào các phần tử mảng với "${a[$first_indice+n]}", the whole list với "${a[@]}"và sử dụng các chức năng wrapper ( array_elements, set_array, set_array_element) để có được số phần tử của một mảng (trong $REPLY), thiết lập các mảng như toàn bộ hoặc assign một yếu tố cá nhân.

Có lẽ không đáng để nỗ lực. Tôi sẽ sử dụng perlhoặc giới hạn đối với mảng shell Bourne / POSIX : "$@".

Nếu mục đích là để một số tệp có nguồn gốc từ vỏ tương tác của người dùng để xác định các hàm sử dụng nội bộ mảng, thì đây là một vài lưu ý có thể hữu ích.

Bạn có thể định cấu hình zshcác mảng giống như kshcác mảng trong phạm vi cục bộ (trong các hàm hoặc hàm ẩn danh).

myfunction() {
  [ -z "$ZSH_VERSION" ] || setopt localoption ksharrays
  # use arrays of indice 0 in this function
}

Bạn cũng có thể mô phỏng ksh(cải thiện khả năng tương thích với kshcác mảng và một số khu vực khác) với:

myfunction() {
  [ -z "$ZSH_VERSION" ] || emulate -L ksh
  # ksh code more likely to work here
}

Với ý nghĩ đó và bạn sẵn sàng thả hỗ trợ cho yashksh88và các phiên bản cũ của pdkshcác dẫn xuất, và miễn là bạn không cố gắng tạo ra các mảng thưa thớt, bạn sẽ có thể liên tục sử dụng:

  • a[0]=foo
  • a=(foo bar)(nhưng không a=())
  • "${a[#]}", "${a[@]}","${a[0]}"

trong các chức năng có emulate -L ksh, trong khi zshngười dùng vẫn sử dụng mảng của mình thông thường theo cách zsh.


7

Bạn có thể sử dụng evalđể thử cú pháp mảng:

is_array_support() (
  eval 'a=(1)'
) >/dev/null 2>&1

if is_array_support; then
  echo support
else
  echo not
fi

2
ksh88hỗ trợ mảng nhưng không a=(). Trong ksh93, a=()khai báo một biến ghép, không phải là một mảng trừ khi biến đó đã được khai báo là một mảng trước đó.
Stéphane Chazelas

2
Cũng lưu ý rằng có sự khác biệt đáng kể giữa các triển khai mảng. Chẳng hạn, một số có các chỉ số mảng bắt đầu từ 0 (bash, ksh, zsh trong mô phỏng ksh), một số bắt đầu từ một (zsh, yash). Một số là mảng / danh sách bình thường, một số là mảng thưa thớt (mảng kết hợp với các khóa được giới hạn ở các số nguyên dương như trong ksh hoặc bash).
Stéphane Chazelas

Trong yash, bạn không làm a[5]=1nhưngarray -s a 5 1
Stéphane Chazelas

@ StéphaneChazelas: cảm ơn vì sự thận trọng. Trong trường hợp của tôi, mọi thứ đều tập trung vào việc liệu các mảng (kết hợp hay không) có được hỗ trợ hay không. Chi tiết về cơ sở chỉ mục có thể dễ dàng được thực hiện ngay cả trong một tập lệnh có nghĩa là chạy không giám sát.
Cbhihe

@ StéphaneChazelas: Biến tổng hợp ksh93làm tôi ngạc nhiên, bạn có phiền khi cho tôi một phần tài liệu về nó không. Tôi thêm 1vào mảng để làm cho nó hoạt động.
cuonglm
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.