Matrizes em Unix Bourne Shell

26

Estou tentando usar matrizes no shell Bourne ( /bin/sh). Eu descobri que a maneira de inicializar os elementos da matriz é:

arr=(1 2 3)

Mas está encontrando um erro:

syntax error at line 8: `arr=' unexpected

Agora, o post onde encontrei essa sintaxe diz que é bash, mas não consegui encontrar nenhuma sintaxe separada para o shell Bourne. A sintaxe também é a mesma /bin/sh?

SubhasisM
fonte
11
verifique esta questão stackoverflow.com/questions/9481702/... no estouro de pilha
Nischay
11
Thnx @Nischay ... Depois de ler o link que você forneceu, eu refinado minha string de consulta no Google e tem o link - docstore.mik.ua/orelly/unix/upt/ch45_34.htm
SubhasisM

Respostas:

47

/bin/shhoje em dia quase nunca é um shell Bourne em nenhum sistema (mesmo o Solaris, que foi um dos últimos grandes sistemas a incluí-lo, agora mudou para um POSIX sh para seu / bin / sh no Solaris 11). /bin/shfoi a concha de Thompson no início dos anos 70. O shell Bourne o substituiu no Unix V7 em 1979.

/bin/sh foi o shell Bourne por muitos anos depois (ou o shell Almquist, uma reimplementação gratuita nos BSDs).

Atualmente, /bin/shé mais comum um intérprete ou outro para a shlinguagem POSIX , que se baseia em um subconjunto da linguagem ksh88 (e um superconjunto da linguagem shell Bourne com algumas incompatibilidades).

O shell Bourne ou a especificação de linguagem POSIX sh não suportam matrizes. Ou melhor, eles têm apenas uma matriz: os parâmetros de posição ( $1, $2, $@, de modo que uma matriz por função, bem).

O ksh88 tinha matrizes com as quais você configurou set -A, mas isso não foi especificado no sh POSIX, pois a sintaxe é estranha e pouco utilizável.

Outros conchas com variáveis de matriz / listas incluem: csh/ tcsh, rc, es, bash(que na maior parte copiado a sintaxe ksh o caminho ksh93), yash, zsh, fishcada um com uma sintaxe diferente ( rca casca de uma vez a-ser sucessor do Unix, fishe zshser o mais consistente uns)...

No padrão sh(também funciona nas versões modernas do shell 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)

(observe que no shell Bourne e no ksh88, $IFSdeve conter o caractere de espaço para "$@"funcionar corretamente (um bug) e, no shell Bourne, você não pode acessar os elementos acima $9( ${10}não funcionará, você ainda pode fazer shift 1; echo "$9"ou fazer um loop) eles)).

Stéphane Chazelas
fonte
2
Muito obrigado ... sua explicação detalhada foi muito útil.
amigos estão dizendo sobre subhasisM
11
Pode-se notar que os parâmetros posicionais diferem dos arrays bash em alguns recursos principais. Por exemplo, eles não suportam matrizes esparsas e, como sh não possui expansão de parâmetro de fatiamento, você não pode acessar sublistas como "${@:2:4}". Certamente, vejo as semelhanças , mas não considero os parâmetros posicionais como uma matriz em si.
Kojiro
@kojiro, em certa medida, eu diria que é o contrário, "$@"age como um array (como as matrizes de csh, rc, zsh, fish, yash...), é mais da Korn / bash "matrizes" que não são realmente matrizes, mas alguns forma de matrizes associativas com chaves limitadas a números inteiros positivos (eles também têm índices começando em 0 em vez de 1 como em todos os outros shells com matrizes e "$ @"). Os shells que têm suporte para fatiar podem fatiar $ @ da mesma forma (com ksh93 / bash adicionando desajeitadamente $ 0 aos parâmetros posicionais ao fatiar "$ @").
Stéphane Chazelas
3

Não há matrizes no shell Bourne simples. Você pode usar a seguinte maneira de criar uma matriz e atravessá-la:

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

Independentemente da maneira de usar as matrizes sh, você a escolherá sempre será complicado. Considere usar um idioma diferente, como Pythonou Perlse você puder, a menos que esteja preso a uma plataforma muito limitada ou queira aprender alguma coisa.

Arkadiusz Drabczyk
fonte
Obrigado pela resposta...!! Na verdade, estou realmente tentando aprender coisas no shell script ... caso contrário, implementar array em Python é realmente um pedaço de bolo. Esta foi uma grande lição de que existe alguma linguagem de script que não suporta matriz :) Uma coisa, o código que você postou está dando um erro - "erro de sintaxe na linha 6: '$' inesperado" ... Estou um pouco ocupado agora, eu resolveria isso ... plz não se incomoda.
amigos estão dizendo sobre subhasisM
@NoobGeek, o shell Bourne não tem a $(...)sintaxe. Então você deve realmente ter o shell Bourne. Você está no Solaris 10 ou antes? Provavelmente, você também não terá seq. No Solaris 10 e versões anteriores, você deseja que / usr / xpg4 / bin / sh tenha um padrão em shvez de um shell Bourne. Usar seqesse caminho também não é muito bom.
Stéphane Chazelas
O POSIX afirma que $ e `são equivalentes na substituição de comando: link . E por que o uso seqdessa maneira não é bom?
Arkadiusz Drabczyk
2
Sim em conchas POSIX, você deve preferir $(...)mais `, mas o OP de /bin/shé provavelmente um shell Bourne, não é um shell POSIX. Além de seqnão ser um comando padrão, fazer $(seq 100)significa armazenar toda a saída na memória, e isso significa que depende do valor atual de $ IFS que contém nova linha e não contém dígitos. Melhor usar i=1; while [ "$i" -le 100 ]; ...; i=$(($i + 1)); done(embora isso também não funcione no shell Bourne).
Stéphane Chazelas
11
@ Daenyth Eu diria bem o contrário: aprender basismos primeiro e depois /bin/shsintaxe portátil mais tarde, tende a fazer as pessoas pensarem que não há problema em usar o #!/bin/shshebang errado e depois interrompe seus scripts quando outras pessoas tentam usá-los. Você seria aconselhado a não postar esse tipo de flama. :)
Josip Rodin
2

Como os outros disseram, o Bourne Shell não possui matrizes verdadeiras .

No entanto, dependendo do que você precisa fazer, cadeias delimitadas devem ser suficientes:

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

Se os delimitadores típicos (espaço, tabulação e nova linha) não IFSforem suficientes, você poderá definir o delimitador que desejar antes do loop.

E se você precisar criar a matriz programaticamente, poderá criar uma cadeia delimitada.

Sildoreth
fonte
11
A menos que você queira (improvável), provavelmente também desejará desativar o globbing, que é outro efeito de deixar variáveis ​​sem aspas assim (o split+globoperador).
Stéphane Chazelas
0

Uma forma de matrizes de simular em traço (ele pode ser adaptado para qualquer número de dimensões de uma matriz): (Por favor, note que o uso do seqcomando requer que IFS. É definido como '' (SPACE = o valor padrão) Você pode usar while ... do ...ou do ... while ...loops para evitar isso (eu mantive seqo escopo de uma ilustração melhor do que o código faz).)

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

fonte
11
Observe que while localé suportado por ambos bashe dash, não é POSIX. seqtambém não é um comando POSIX. Você provavelmente deve mencionar que o código faz algumas suposições sobre o valor atual de US $ IFS (se você evitar usar seqe citar suas variáveis, ele pode ser evitado)
Stéphane Chazelas