Массивы в Unix Bourne Shell

26

Я пытаюсь использовать массивы в оболочке Bourne ( /bin/sh). Я обнаружил, что способ инициализации элементов массива:

arr=(1 2 3)

Но он сталкивается с ошибкой:

syntax error at line 8: `arr=' unexpected

Теперь в посте, где я нашел этот синтаксис, говорится, что он предназначен bash, но я не смог найти какой-либо отдельный синтаксис для оболочки Bourne. Синтаксис также одинаковый /bin/sh?

SubhasisM
источник
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, в какой - то степени, я бы сказал , что наоборот, "$@"действует как массив (например , массивы 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:` $ 'непредвиденно »... Я немного занят сейчас я бы решил ... плз, не беспокойтесь.
SubhasisM
@NoobGeek, оболочка Bourne не имеет $(...)синтаксиса. Таким образом, у вас действительно должна быть оболочка Борна. Вы на Солярисе 10 или раньше? Скорее всего, у вас тоже не будет seq. В Solaris 10 и более ранних версиях у / usr / xpg4 / bin / sh должен быть стандарт shвместо оболочки Bourne. Использование seqэтого способа тоже не очень хорошо.
Стефан Шазелас
POSIX утверждает, что $ и `эквивалентны в подстановке команд: ссылка . И почему использование seqэтого способа не хорошо?
Аркадиуш Драбчик
2
Да в POSIX оболочек, следует отдавать предпочтение $(...)более `, но OP - х /bin/sh, вероятно, Bourne оболочки, а не оболочка POSIX. Помимо того, seqчто это не стандартная команда, выполнение $(seq 100)означает сохранение всего вывода в памяти, а это означает, что оно зависит от текущего значения $ IFS, содержащего символ новой строки и не содержащего цифр. Лучше всего использовать i=1; while [ "$i" -le 100 ]; ...; i=$(($i + 1)); done(хотя это не сработает и в оболочке Bourne).
Стефан Шазелас
1
@Daenyth Я бы сказал, что совсем наоборот: сначала изучая bashisms, а затем переносимый /bin/shсинтаксис, склоняюсь к тому, что люди думают, что можно использовать неправильный #!/bin/shshebang, а затем ломает свои сценарии, когда другие люди пытаются их использовать. Вам бы не советовали не публиковать такую ​​приманку. :)
Йосип Роден
2

Как говорили другие, Bourne Shell не имеет истинных массивов.

Однако, в зависимости от того, что вам нужно сделать, должно быть достаточно разделенных строк:

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

Если типичные разделители (пробел, табуляция и перевод строки) недостаточны, вы можете установить IFSлюбой разделитель, какой захотите, перед циклом.

И если вам нужно построить массив программно, вы можете просто создать строку с разделителями.

Sildoreth
источник
1
Если вы этого не хотите (маловероятно), вы, вероятно, также захотите отключить глобализацию, что является еще одним следствием того, что переменные не заключаются в кавычки ( split+globоператор).
Стефан Шазелас
0

Способ имитации массивов в тире (его можно адаптировать к любому количеству измерений массива): (Обратите внимание, что для использования seqкоманды требуется IFSзначение '' (ПРОБЕЛ = значение по умолчанию). Вы можете использовать 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и цитируете свои переменные, этого можно избежать)
Стефан Шазелас