Mảng trong Unix Bourne Shell


26

Tôi đang cố gắng sử dụng mảng trong Bourne shell ( /bin/sh). Tôi thấy rằng cách để khởi tạo các phần tử mảng là:

arr=(1 2 3)

Nhưng nó đang gặp một lỗi:

syntax error at line 8: `arr=' unexpected

Bây giờ bài đăng nơi tôi tìm thấy cú pháp này nói rằng nó là dành cho bash, nhưng tôi không thể tìm thấy bất kỳ cú pháp riêng biệt nào cho shell Bourne. Liệu cú pháp có giống nhau /bin/shkhông?


1
kiểm tra câu hỏi này stackoverflow.com/questions/9481702/ trên phần tràn stack
Nischay

1
Thnx @Nischay ... Sau khi đọc liên kết bạn cung cấp, tôi đã tinh chỉnh chuỗi truy vấn của mình trong google và nhận được liên kết - docstore.mik.ua/orelly/unix/upt/ch45_34.htmlm
SubhocationM

Câu trả lời:


47

/bin/shngày nay hầu như không phải là vỏ Bourne trên bất kỳ hệ thống nào (ngay cả Solaris, một trong những hệ thống lớn cuối cùng bao gồm nó giờ đã chuyển sang sh POSIX cho / bin / sh trong Solaris 11). /bin/shlà vỏ của Thompson vào đầu những năm 70. Shell Bourne đã thay thế nó trong Unix V7 vào năm 1979.

/bin/sh đã là vỏ Bourne trong nhiều năm sau đó (hay vỏ Almquist, một sự tái hiện miễn phí trên BSD).

Ngày nay, /bin/shthông thường là một trình thông dịch hoặc shngôn ngữ khác cho ngôn ngữ POSIX , bản thân nó dựa trên một tập hợp ngôn ngữ của ksh88 (và một siêu ngôn ngữ của ngôn ngữ shell Bourne với một số điểm không tương thích).

Trình bao Bourne hoặc đặc tả ngôn ngữ sh POSIX không hỗ trợ mảng. Hay đúng hơn là họ chỉ có một mảng: các tham số vị trí ( $1, $2, $@, vì vậy một mảng mỗi chức năng cũng).

ksh88 đã có các mảng mà bạn thiết lập set -A, nhưng điều đó không được chỉ định trong POSIX sh vì cú pháp rất khó xử và không sử dụng được.

Vỏ khác với các biến mảng / danh sách bao gồm: csh/ tcsh, rc, es, bash(mà chủ yếu là sao chép cú pháp ksh cách ksh93), yash, zsh, fishmỗi với một cú pháp khác nhau ( rcvỏ của một lần to-be kế của Unix, fishzshlà phù hợp nhất những cái) ...

Trong tiêu chuẩn sh(cũng hoạt động trong các phiên bản hiện đại của vỏ Bourne):

set '1st element' 2 3 # setting the array

set -- "$@" more # adding elements to the end of the array

shift 2 # removing elements (here 2) from the beginning of the array

printf '<%s>\n' "$@" # passing all the elements of the $@ array 
                     # as arguments to a command

for i do # looping over the  elements of the $@ array ($1, $2...)
  printf 'Looping over "%s"\n' "$i"
done

printf '%s\n' "$1" # accessing individual element of the array.
                   # up to the 9th only with the Bourne shell though
                   # (only the Bourne shell), and note that you need
                   # the braces (as in "${10}") past the 9th in other
                   # shells.

printf '%s\n' "$# elements in the array"

printf '%s\n' "$*" # join the elements of the array with the 
                   # first character (byte in some implementations)
                   # of $IFS (not in the Bourne shell where it's on
                   # space instead regardless of the value of $IFS)

(lưu ý rằng trong vỏ Bourne và ksh88, $IFSphải chứa ký tự khoảng trắng "$@"để hoạt động chính xác (một lỗi) và trong vỏ Bourne, bạn không thể truy cập các phần tử ở trên $9( ${10}sẽ không hoạt động, bạn vẫn có thể thực hiện shift 1; echo "$9"hoặc lặp lại họ)).


2
Cảm ơn rất nhiều ... lời giải thích chi tiết của bạn rất hữu ích.
SubhocationM

1
Có thể đáng lưu ý rằng các tham số vị trí khác với mảng bash trong một số tính năng chính. Ví dụ: chúng không hỗ trợ các mảng thưa thớt và vì sh không có mở rộng tham số, nên bạn không thể truy cập các danh sách con như "${@:2:4}". Để chắc chắn, tôi thấy những điểm tương đồng , nhưng tôi không coi các tham số vị trí là một mảng trên mỗi se.
kojiro

@kojiro, ở một mức độ nào đó, tôi muốn nói đó là ngược lại, "$@"hoạt động như một mảng (như các mảng của csh, rc, zsh, fish, yash...), nó càng Korn / bash "mảng" mà không phải là thực sự mảng, nhưng một số dạng mảng kết hợp với các khóa được giới hạn ở các số nguyên dương (chúng cũng có các chỉ số bắt đầu từ 0 thay vì 1 như trong tất cả các hệ vỏ khác có mảng và "$ @"). Các shell có hỗ trợ cắt lát có thể cắt $ @ giống nhau (với ksh93 / bash lúng túng khi thêm $ 0 vào các tham số vị trí khi bạn cắt "$ @").
Stéphane Chazelas

3

Không có mảng trong vỏ Bourne đơn giản. Bạn có thể sử dụng cách sau để tạo một mảng và duyệt qua nó:

#!/bin/sh
# ARRAY.sh: example usage of arrays in Bourne Shell

array_traverse()
{
    for i in $(seq 1 $2)
    do
    current_value=$1$i
    echo $(eval echo \$$current_value)
    done
    return 1
}

ARRAY_1=one
ARRAY_2=two
ARRAY_3=333
array_traverse ARRAY_ 3

Bất kể cách nào để sử dụng mảng trong shbạn sẽ chọn nó sẽ luôn cồng kềnh. Cân nhắc sử dụng một ngôn ngữ khác như Pythonhoặc Perlnếu bạn có thể trừ khi bạn bị mắc kẹt với một nền tảng rất hạn chế hoặc muốn học một cái gì đó.


Cảm ơn vì sự trả lời...!! Trên thực tế tôi thực sự đang cố gắng học mọi thứ trong shell script ... nếu không thì việc triển khai mảng trong Python thực sự là một miếng bánh. Đây là một bài học lớn tồn tại một số ngôn ngữ kịch bản không hỗ trợ mảng :) Một điều, đoạn mã bạn đã đăng bị lỗi - "lỗi cú pháp ở dòng 6: '$' không ngờ" ... Tôi hơi bận Bây giờ, tôi sẽ giải quyết nó ... xin đừng bận tâm.
SubhocationM

@NoobGeek, shell Bourne không có $(...)cú pháp. Vì vậy, bạn thực sự phải có vỏ Bourne. Bạn đang trên Solaris 10 hay trước đó? Có thể bạn sẽ không có một seqtrong hai. Trên Solaris 10 trở về trước, bạn muốn / usr / xpg4 / bin / sh có tiêu chuẩn shthay vì vỏ Bourne. Sử dụng seqtheo cách đó cũng không tốt lắm.
Stéphane Chazelas

POSIX nói rằng $ và `tương đương trong thay thế lệnh: link . Và tại sao sử dụng cách seqđó không tốt?
Arkadiusz Drabchot

2
Có trong vỏ POSIX, bạn nên ưu tiên $(...)hơn `, nhưng OP /bin/shcó lẽ là vỏ Bourne, không phải vỏ POSIX. Bên cạnh việc seqkhông phải là một lệnh tiêu chuẩn, thực hiện $(seq 100)có nghĩa là lưu trữ toàn bộ đầu ra trong bộ nhớ và điều đó có nghĩa là nó phụ thuộc vào giá trị hiện tại của $ IFS chứa dòng mới và không chứa chữ số. Tốt nhất để sử dụng i=1; while [ "$i" -le 100 ]; ...; i=$(($i + 1)); done(mặc dù điều đó sẽ không hoạt động trong vỏ Bourne).
Stéphane Chazelas

1
@Daenyth Tôi sẽ nói hoàn toàn ngược lại: học bashism trước, sau đó là /bin/shcú pháp di động sau, có xu hướng khiến mọi người nghĩ rằng sử dụng #!/bin/shshebang sai , và sau đó phá vỡ tập lệnh của họ khi người khác cố gắng sử dụng chúng. Bạn nên lưu ý không đăng loại flamebait này. :)
Josip Rodin

2

Như những người khác đã nói, Bourne Shell không có mảng thực sự .

Tuy nhiên, tùy thuộc vào những gì bạn cần làm, các chuỗi được phân tách phải đủ:

sentence="I don't need arrays because I can use delimited strings"
for word in $sentence
do
  printf '%s\n' "$word"
done

Nếu các dấu phân cách điển hình (dấu cách, tab và dòng mới) không đủ, bạn có thể đặt IFSthành bất kỳ dấu phân cách nào bạn muốn trước vòng lặp.

Và nếu bạn cần xây dựng mảng theo chương trình, bạn có thể xây dựng một chuỗi phân tách.


1
Trừ khi bạn muốn nó (không có khả năng), có lẽ bạn cũng sẽ muốn vô hiệu hóa tính năng toàn cầu, đó là một hiệu ứng khác của việc để các biến không được trích dẫn như thế ( split+globtoán tử).
Stéphane Chazelas

0

Một cách để mô phỏng các mảng trong dấu gạch ngang (nó có thể được điều chỉnh cho bất kỳ số lượng kích thước nào của một mảng): (Xin lưu ý rằng việc sử dụng seqlệnh yêu cầu IFSđược đặt thành '' (SPACE = giá trị mặc định). Bạn có thể sử dụng while ... do ...hoặc do ... while ...thay vào đó các vòng lặp để tránh điều này (tôi giữ seqtrong phạm vi minh họa rõ hơn về những gì mã làm).)

#!/bin/sh

## The following functions implement vectors (arrays) operations in dash:
## Definition of a vector <v>:
##      v_0 - variable that stores the number of elements of the vector
##      v_1..v_n, where n=v_0 - variables that store the values of the vector elements

VectorAddElementNext () {
# Vector Add Element Next
# Adds the string contained in variable $2 in the next element position (vector length + 1) in vector $1

    local elem_value
    local vector_length
    local elem_name

    eval elem_value=\"\$$2\"
    eval vector_length=\$$1\_0
    if [ -z "$vector_length" ]; then
        vector_length=$((0))
    fi

    vector_length=$(( vector_length + 1 ))
    elem_name=$1_$vector_length

    eval $elem_name=\"\$elem_value\"
    eval $1_0=$vector_length
}

VectorAddElementDVNext () {
# Vector Add Element Direct Value Next
# Adds the string $2 in the next element position (vector length + 1) in vector $1

    local elem_value
    local vector_length
    local elem_name

    eval elem_value="$2"
    eval vector_length=\$$1\_0
    if [ -z "$vector_length" ]; then
        vector_length=$((0))
    fi

    vector_length=$(( vector_length + 1 ))
    elem_name=$1_$vector_length

    eval $elem_name=\"\$elem_value\"
    eval $1_0=$vector_length
}

VectorAddElement () {
# Vector Add Element
# Adds the string contained in the variable $3 in the position contained in $2 (variable or direct value) in the vector $1

    local elem_value
    local elem_position
    local vector_length
    local elem_name

    eval elem_value=\"\$$3\"
    elem_position=$(($2))
    eval vector_length=\$$1\_0
    if [ -z "$vector_length" ]; then
        vector_length=$((0))
    fi

    if [ $elem_position -ge $vector_length ]; then
        vector_length=$elem_position
    fi

    elem_name=$1_$elem_position

    eval $elem_name=\"\$elem_value\"
    if [ ! $elem_position -eq 0 ]; then
        eval $1_0=$vector_length
    fi
}

VectorAddElementDV () {
# Vector Add Element
# Adds the string $3 in the position $2 (variable or direct value) in the vector $1

    local elem_value
    local elem_position
    local vector_length
    local elem_name

    eval elem_value="$3"
    elem_position=$(($2))
    eval vector_length=\$$1\_0
    if [ -z "$vector_length" ]; then
        vector_length=$((0))
    fi

    if [ $elem_position -ge $vector_length ]; then
        vector_length=$elem_position
    fi

    elem_name=$1_$elem_position

    eval $elem_name=\"\$elem_value\"
    if [ ! $elem_position -eq 0 ]; then
        eval $1_0=$vector_length
    fi
}

VectorPrint () {
# Vector Print
# Prints all the elements names and values of the vector $1 on sepparate lines

    local vector_length

    vector_length=$(($1_0))
    if [ "$vector_length" = "0" ]; then
        echo "Vector \"$1\" is empty!"
    else
        echo "Vector \"$1\":"
        for i in $(seq 1 $vector_length); do
            eval echo \"[$i]: \\\"\$$1\_$i\\\"\"
            ###OR: eval printf \'\%s\\\n\' \"[\$i]: \\\"\$$1\_$i\\\"\"
        done
    fi
}

VectorDestroy () {
# Vector Destroy
# Empties all the elements values of the vector $1

    local vector_length

    vector_length=$(($1_0))
    if [ ! "$vector_length" = "0" ]; then
        for i in $(seq 1 $vector_length); do
            unset $1_$i
        done
        unset $1_0
    fi
}

##################
### MAIN START ###
##################

## Setting vector 'params' with all the parameters received by the script:
for i in $(seq 1 $#); do
    eval param="\${$i}"
    VectorAddElementNext params param
done

# Printing the vector 'params':
VectorPrint params

read temp

## Setting vector 'params2' with the elements of the vector 'params' in reversed order:
if [ -n "$params_0" ]; then
    for i in $(seq 1 $params_0); do
        count=$((params_0-i+1))
        VectorAddElement params2 count params_$i
    done
fi

# Printing the vector 'params2':
VectorPrint params2

read temp

## Getting the values of 'params2'`s elements and printing them:
if [ -n "$params2_0" ]; then
    echo "Printing the elements of the vector 'params2':"
    for i in $(seq 1 $params2_0); do
        eval current_elem_value=\"\$params2\_$i\"
        echo "params2_$i=\"$current_elem_value\""
    done
else
    echo "Vector 'params2' is empty!"
fi

read temp

## Creating a two dimensional array ('a'):
for i in $(seq 1 10); do
    VectorAddElement a 0 i
    for j in $(seq 1 8); do
        value=$(( 8 * ( i - 1 ) + j ))
        VectorAddElementDV a_$i $j $value
    done
done

## Manually printing the two dimensional array ('a'):
echo "Printing the two-dimensional array 'a':"
if [ -n "$a_0" ]; then
    for i in $(seq 1 $a_0); do
        eval current_vector_lenght=\$a\_$i\_0
        if [ -n "$current_vector_lenght" ]; then
            for j in $(seq 1 $current_vector_lenght); do
                eval value=\"\$a\_$i\_$j\"
                printf "$value "
            done
        fi
        printf "\n"
    done
fi

################
### MAIN END ###
################

1
Lưu ý rằng mặc dù localđược hỗ trợ bởi cả hai bashdash, nó không phải là POSIX. seqcũng không phải là lệnh POSIX. Có lẽ bạn nên đề cập rằng mã của bạn đưa ra một số giả định về giá trị hiện tại của $ IFS (nếu bạn tránh sử dụng seqvà trích dẫn các biến của mình, điều đó có thể tránh được)
Stéphane Chazelas
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.