อาร์เรย์ใน Unix Bourne Shell


26

ฉันพยายามใช้อาร์เรย์ใน Bourne shell ( /bin/sh) ฉันพบว่าวิธีการเริ่มต้นองค์ประกอบอาร์เรย์คือ:

arr=(1 2 3)

แต่พบข้อผิดพลาด:

syntax error at line 8: `arr=' unexpected

ตอนนี้โพสต์ที่ฉันพบไวยากรณ์นี้บอกว่ามันมีไว้สำหรับbashแต่ฉันไม่สามารถหาไวยากรณ์ที่แยกต่างหากสำหรับเชลล์เป้าหมาย ไวยากรณ์มีความเหมือน/bin/shกันหรือไม่


1
ตรวจสอบคำถามนี้stackoverflow.com/questions/9481702/…บน stack overflow
Nischay

1
ขอบคุณ @Nischay ... หลังจากอ่านลิงก์ที่คุณให้ฉันได้กลั่นกรองข้อความค้นหาของฉันใน google และได้รับลิงค์ - docstore.mik.ua/orelly/unix/upt/ch45_34.htm
SubhasisM

คำตอบ:


47

/bin/shแทบจะไม่เคยเป็นเชลล์เป้าหมายในระบบใด ๆ ในปัจจุบัน (แม้แต่โซลาริสซึ่งเป็นหนึ่งในระบบหลักสุดท้ายที่รวมไว้ในตอนนี้ได้เปลี่ยนเป็น POSIX sh สำหรับ / bin / sh ใน Solaris 11) /bin/shเป็นเปลือกของ ธ ​​อมป์สันในต้นปี 70 เชลล์เป้าหมายถูกแทนที่ใน Unix V7 ในปี 1979

/bin/sh เป็นบอร์นเชลล์เป็นเวลาหลายปีหลังจากนั้น (หรือ Almquist shell ซึ่งเป็นการนำมาใช้ใหม่ฟรีบน BSD)

ทุกวันนี้/bin/shมักจะเป็นล่ามหรือภาษาอื่นสำหรับshภาษาPOSIX ซึ่งมีพื้นฐานมาจากชุดย่อยของภาษา ksh88 (และเซ็ตของภาษาเชลล์บอร์นที่มีความเข้ากันไม่ได้)

เชลล์เป้าหมายหรือข้อกำหนดภาษา POSIX sh ไม่สนับสนุนอาร์เรย์ หรือมากกว่าที่พวกเขามีเพียงหนึ่งอาร์เรย์: พารามิเตอร์ตำแหน่ง ( $1, $2, $@ดังนั้นหนึ่งอาร์เรย์ต่อฟังก์ชั่นเช่นกัน)

ksh88 มีอาร์เรย์ที่คุณตั้งค่าไว้set -Aแต่นั่นไม่ได้ระบุใน POSIX sh เนื่องจากไวยากรณ์นั้นอึดอัดและไม่สามารถใช้งานได้มาก

เปลือกหอยอื่น ๆ ที่มีตัวแปรอาร์เรย์ / รายการรวมถึง: csh/ tcsh, rc, es, bash(ซึ่งคัดลอกส่วนใหญ่ไวยากรณ์ ksh วิธี ksh93), การyash, zsh, fishแต่ละคนมีไวยากรณ์ที่แตกต่างกัน ( rcเปลือกของครั้งหนึ่งที่ไปเป็นตัวตายตัวแทนของยูนิกซ์, fishและzshเป็นสอดคล้องกันมากที่สุด คน) ...

ตามมาตรฐานsh(ใช้ได้กับเชลล์เป้าหมายรุ่นปัจจุบันด้วย):

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)

(โปรดทราบว่าในเชลล์เป้าหมายและ ksh88 $IFSต้องมีอักขระช่องว่างสำหรับ"$@"การทำงานอย่างถูกต้อง (ข้อผิดพลาด) และในเชลล์เป้าหมายคุณไม่สามารถเข้าถึงองค์ประกอบด้านบน$9( ${10}ไม่ทำงานคุณยังสามารถทำได้shift 1; echo "$9"หรือวนซ้ำ พวกเขา))


2
ขอบคุณมาก ... คำอธิบายโดยละเอียดของคุณมีประโยชน์มาก
SubhasisM

1
อาจเป็นเรื่องที่น่าสังเกตว่าพารามิเตอร์ตำแหน่งแตกต่างจาก bash arrays ในคุณสมบัติหลักบางอย่าง "${@:2:4}"ยกตัวอย่างเช่นพวกเขาไม่สนับสนุนอาร์เรย์เบาบางและตั้งแต่การดวลจุดโทษไม่ได้มีการขยายตัวพารามิเตอร์หั่นคุณไม่สามารถเข้าถึงรายการย่อยเช่น เพื่อให้แน่ใจว่าฉันเห็นความคล้ายคลึงกันแต่ฉันไม่ถือว่าพารามิเตอร์ตำแหน่งเป็นอาร์เรย์ต่อ se
kojiro

@kojiro ที่มีขอบเขตบางอย่างผมว่ามันเป็นตรงกันข้าม"$@"ทำหน้าที่เหมือนอาร์เรย์ (เช่นอาร์เรย์ของcsh, rc, zsh, fish, yash... ) ก็มากขึ้น Korn / ทุบตี "อาร์เรย์" ที่ไม่อาร์เรย์จริงๆ แต่บางส่วน รูปแบบของอาร์เรย์ที่เชื่อมโยงกับคีย์ จำกัด จำนวนเต็มบวก (พวกมันยังมีดัชนีเริ่มต้นที่ 0 แทนที่จะเป็น 1 เช่นเดียวกับในเชลล์อื่น ๆ ทั้งหมดที่มีอาร์เรย์และ "$ @") เชลล์ที่มีการรองรับการแบ่งสามารถหั่น $ @ เหมือนกัน (ด้วย ksh93 / bash เพิ่ม $ 0 ไปยังพารามิเตอร์ตำแหน่งอย่างเชื่องช้าเมื่อคุณเชือด "$ @")
Stéphane Chazelas

3

ไม่มีอาร์เรย์ในเชลล์เป้าหมาย คุณสามารถใช้วิธีการต่อไปนี้เพื่อสร้างอาร์เรย์และสำรวจมัน:

#!/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

ไม่ว่าคุณจะเลือกใช้อาร์เรย์ในแบบshใดมันจะยุ่งยากตลอดเวลา ลองใช้ภาษาอื่นเช่นPythonหรือPerlถ้าคุณทำได้เว้นแต่ว่าคุณติดอยู่กับแพลตฟอร์มที่มีข้อ จำกัด มากหรือต้องการเรียนรู้อะไรบางอย่าง


ขอบคุณสำหรับการตอบสนอง ... !! จริงๆแล้วฉันกำลังพยายามเรียนรู้สิ่งต่าง ๆ ในเชลล์สคริปต์ ... ไม่อย่างนั้นการใช้อาร์เรย์ใน Python นั้นเป็นเรื่องที่ยากมาก นี่เป็นบทเรียนที่ยิ่งใหญ่ที่มีภาษาสคริปต์บางตัวที่ไม่รองรับอาเรย์ :) สิ่งหนึ่งรหัสที่คุณโพสต์ให้ข้อผิดพลาด - "ข้อผิดพลาดทางไวยากรณ์ที่บรรทัด 6:` $ 'ไม่คาดคิด "... ฉันยุ่งเล็กน้อย ตอนนี้ฉันจะได้รับการแก้ไข ...
SubhasisM

@NoobGeek เชลล์ Bourne ไม่มี$(...)ไวยากรณ์ ดังนั้นคุณต้องมีบอร์นเชลล์ คุณอยู่บน Solaris 10 หรือก่อนหน้า โอกาสที่คุณจะไม่มีseqทั้ง บน Solaris 10 และรุ่นก่อนหน้าคุณต้องการ / usr / xpg4 / bin / sh เพื่อให้มีมาตรฐานshแทนที่จะเป็นเชลล์เป้าหมาย การใช้seqวิธีการนั้นก็ไม่ดีเช่นกัน
Stéphane Chazelas

POSIX ระบุว่า $ และ `เทียบเท่าทดแทนคำสั่ง: การเชื่อมโยง และทำไมการใช้seqวิธีนั้นจึงไม่ดี
Arkadiusz Drabczyk

2
ใช่ใน POSIX เชลล์คุณควรจะชอบ$(...)มากกว่า`แต่ OP ของ/bin/shน่าจะเป็นเชลล์เป้าหมายไม่ใช่เชลล์ POSIX ข้างseqไม่ใช่คำสั่งมาตรฐานการทำ$(seq 100)หมายถึงการเก็บเอาต์พุตทั้งหมดในหน่วยความจำและนั่นหมายความว่ามันขึ้นอยู่กับค่าปัจจุบันของ $ IFS ที่มีการขึ้นบรรทัดใหม่และไม่มีตัวเลข ดีที่สุดที่จะใช้i=1; while [ "$i" -le 100 ]; ...; i=$(($i + 1)); done(แม้ว่าจะไม่ทำงานในเชลล์เป้าหมายเช่นกัน)
Stéphane Chazelas

1
@Daenyth ฉันจะพูดค่อนข้างตรงกันข้าม: การเรียนรู้ bashisms ก่อนแล้ว/bin/shไวยากรณ์แบบพกพาในภายหลังมีแนวโน้มที่จะทำให้คนคิดว่ามันก็โอเคที่จะใช้#!/bin/shShebang ที่ไม่ถูกต้องแล้วแบ่งสคริปต์ของพวกเขาเมื่อคนอื่นพยายามที่จะใช้พวกเขา คุณควรอย่างยิ่งที่จะไม่โพสต์ flamebait ประเภทนี้ :)
Josip Rodin

2

อย่างที่คนอื่น ๆ พูดกันบอร์นเชลล์ไม่มีอาร์เรย์ที่แท้จริง

อย่างไรก็ตามขึ้นอยู่กับสิ่งที่คุณต้องทำสตริงที่มีการคั่นควรเพียงพอ:

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

หากตัวคั่นทั่วไป (เว้นวรรคแท็บและขึ้นบรรทัดใหม่) ไม่พอคุณสามารถตั้งค่าIFSเป็นตัวคั่นที่คุณต้องการก่อนลูป

และถ้าคุณต้องการสร้างอาเรย์โดยทางโปรแกรมคุณสามารถสร้างสตริงที่มีตัวคั่นได้


1
หากคุณไม่ต้องการ (ไม่น่าเป็นไปได้) คุณอาจต้องการปิดการใช้งานแบบวงกลมซึ่งเป็นผลของการปล่อยตัวแปรโดยไม่ต้องใส่เครื่องหมายเช่นนั้น (ตัวsplit+globดำเนินการ)
Stéphane Chazelas

0

วิธีการจำลองอาร์เรย์ในเส้นประ (สามารถปรับได้ตามขนาดของอาร์เรย์): (โปรดทราบว่าการใช้seqคำสั่งจำเป็นต้องIFSมีการตั้งค่าเป็น '' (SPACE = ค่าเริ่มต้น) คุณสามารถใช้while ... do ...หรือdo ... while ...วนซ้ำแทนเพื่อหลีกเลี่ยงสิ่งนี้ (ฉันเก็บไว้seqในขอบเขตของภาพประกอบที่ดีกว่าของโค้ด)

#!/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
โปรดทราบว่าขณะที่localทั้งคู่รองรับbashและdashไม่ใช่ POSIX seqไม่ใช่คำสั่ง POSIX คุณอาจควรพูดถึงว่ารหัสของคุณทำให้สมมติฐานบางอย่างเกี่ยวกับมูลค่าปัจจุบันของ $ IFS (ถ้าคุณหลีกเลี่ยงการใช้seqและอ้างอิงตัวแปรของคุณก็สามารถหลีกเลี่ยงได้)
Stéphane Chazelas
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.