Масиви в 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/… про переповнення стека
Nischay

1
Thnx @Nischay ... Прочитавши надане вами посилання, я уточнив рядок запитів у google і отримав посилання - docstore.mik.ua/orelly/unix/upt/ch45_34.htm
SubhasisM

Відповіді:


47

/bin/shНа сьогоднішній день навряд чи є оболонка Bourne в будь-яких системах (навіть Solaris, яка була однією з останніх основних систем, яка включала її, тепер перейшла на POSIX sh для свого / bin / sh в Solaris 11). /bin/shбула оболонкою Томпсона на початку 70-х. Оболонка Bourne замінила його в Unix V7 в 1979 році.

/bin/sh був оболонкою Борна протягом багатьох років після цього (або шкаралупа Алквіста, вільне повторне здійснення на BSD).

Сьогодні /bin/shчастіше є перекладачем чи іншою shмовою POSIX, яка сама базується на підмножині мови ksh88 (і наборі мови оболонки Bourne з деякими несумісностями).

Оболонка Bourne або специфікація POSIX sh не підтримують масиви. Або , скоріше , вони мають тільки один масив: позиційні параметри ( $1, $2, $@, так що один масив на функцію, а).

ksh88 мав масиви, з якими ви встановили set -A, але це не було вказано в POSIX sh, оскільки синтаксис незручний і не дуже корисний.

Інші оболонки з масивами / списками змінних включають в себе: csh/ tcsh, rc, es, bash(які в основному скопійовані з синтаксисом КШ в ksh93 шляху), yash, zsh, fishкожен з іншим синтаксисом ( rcоболонкою один раз-бути наступник Unix, fishі zshє найбільш послідовними ті) ...

У стандартній sh(також працює в сучасних версіях оболонки 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)

(зауважте, що в оболонці Bourne та ksh88 $IFSповинен містити пробільний символ для "$@"належної роботи (помилка), а в оболонці Bourne ви не можете отримати доступ до елементів, розташованих вище $9( ${10}не буде працювати, ви все одно можете робити shift 1; echo "$9"або перебирати цикл на їх)).


2
Дякую тонну ... ваше детальне пояснення було дуже корисним.
SubhasisM

1
Можливо, варто відзначити, що в деяких ключових характеристиках позиційні параметри відрізняються від масивів bash. Наприклад, вони не підтримують розрізнені масиви, і оскільки sh не має розширення параметрів нарізки, ви не можете отримати доступ до таких списків "${@:2:4}". Напевно, я бачу подібність , але позиційні параметри я не розглядаю як масив як такий.
kojiro

@kojiro, в якій - то ступеня, я б сказав , що навпаки, "$@"діє як масив (наприклад , масиви csh, rc, zsh, fish, yash...), це більше на Korn / Баш «масиви», які на насправді не масиви, але деякі форма асоціативних масивів з ключами, обмеженими додатними цілими числами (вони також мають індекси, починаючи з 0 замість 1, як у всіх інших оболонках з масивами та "$ @"). Оболонки, які мають підтримку нарізки, можуть нарізати $ @ точно так само (коли ksh93 / bash незграбно додає $ 0 до позиційних параметрів, коли ви нарізаєте "$ @").
Стефан Шазелас

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:` $ 'несподівано "... Я трохи зайнятий Тепер, я б вирішив це ... PLZ не турбуватися.
SubhasisM

@NoobGeek, оболонка Bourne не має $(...)синтаксису. Отже, ви справді повинні мати оболонку Борна. Ви на Solaris 10 чи раніше? Швидше за все, у вас цього не буде seq. У програмі Solaris 10 та новіших версіях ви хочете, щоб / usr / xpg4 / bin / sh мати стандарт shзамість оболонки Bourne. Використання seqцього способу теж не дуже добре.
Стефан Шазелас

POSIX стверджує, що $ і `еквівалентні в підстановці команд: link . І чому використання seqцього способу не добре?
Аркадіуш Драбчик

2
Так в POSIX оболонок, слід віддавати перевагу $(...)більш `, але OP - х /bin/sh, ймовірно, Bourne оболонки, а НЕ оболонка POSIX. Окрім того, що seqне є стандартною командою, виконання $(seq 100)означає, що зберігає весь вихід у пам'яті, а це означає, що це залежить від поточного значення $ IFS, що містить новий рядок і не містить цифр. Найкраще використовувати i=1; while [ "$i" -le 100 ]; ...; i=$(($i + 1)); done(хоча це також не працює в оболонці Борна).
Стефан Шазелас

1
@Daenyth я б сказав зовсім протилежне: спочатку вивчення башизмів, а потім переносний /bin/shсинтаксис, як правило, змушує людей думати, що добре використовувати неправильний #!/bin/shшебанг, а потім порушує їхні сценарії, коли інші люди намагаються їх використовувати. Ви б радили не публікувати такого роду вогняних подій. :)
Йосип Родін

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оператора).
Стефан Шазелас

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.