Tablice w systemie Unix Bourne Shell

26

Próbuję użyć tablic w powłoce Bourne'a ( /bin/sh). Odkryłem, że sposobem na zainicjowanie elementów tablicy jest:

arr=(1 2 3)

Ale napotyka błąd:

syntax error at line 8: `arr=' unexpected

Teraz post, w którym znalazłem tę składnię, mówi, że jest bash, ale nie mogłem znaleźć żadnej osobnej składni dla powłoki Bourne'a. Czy składnia też jest taka sama /bin/sh?

SubhasisM
źródło
1
sprawdź to pytanie stackoverflow.com/questions/9481702/... na temat przepełnienia stosu
Nischay,
1
Thnx @Nischay ... Po przeczytaniu podanego linku poprawiłem
SubhasisM

Odpowiedzi:

47

/bin/shw dzisiejszych czasach prawie nigdy nie jest powłoką Bourne'a (nawet Solaris, który był jednym z ostatnich głównych systemów w tym zakresie, teraz przeszedł na POSIX sh dla swojego / bin / sh w Solaris 11). /bin/shbył pocisk Thompson na początku lat 70. Powłoka Bourne zastąpiła ją w Unix V7 w 1979 r.

/bin/sh od wielu lat jest powłoką Bourne'a (lub powłoką Almquist, bezpłatna reimplementacja BSD).

Obecnie /bin/shczęściej jest tłumaczem lub innym shjęzykiem POSIX , który sam jest oparty na podzbiorze języka ksh88 (i nadzbiór języka powłoki Bourne'a z pewnymi niezgodnościami).

Powłoka Bourne'a lub specyfikacja języka sh POSIX nie obsługują tablic. Albo raczej, że mają tylko jedną tablicę: parametry pozycyjne ( $1, $2, $@, więc na jednej tablicy funkcji, jak również).

ksh88 miał tablice, które ustawiłeś set -A, ale nie zostały one określone w POSIX sh, ponieważ składnia jest niewygodna i niezbyt użyteczna.

Inne pociski z tablicy / list zmiennych są: csh/ tcsh, rc, es, bash(które najczęściej kopiowane składnię KSH sposób ksh93) yash, zsh, fishkażdy z innym składni ( rcpowłoce raz do następcą Unix, fishi zshjest najbardziej zgodne te) ...

Standardowo sh(działa również w nowoczesnych wersjach powłoki Bourne'a):

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)

(zwróć uwagę, że w powłoce Bourne'a i ksh88 $IFSmusi zawierać znak spacji, "$@"aby działał poprawnie (błąd), aw powłoce Bourne'a nie możesz uzyskać dostępu do elementów powyżej $9( ${10}nie będzie działać, nadal możesz to zrobić shift 1; echo "$9"lub zapętlić im)).

Stéphane Chazelas
źródło
2
Dzięki tona ... twoje szczegółowe wyjaśnienie było bardzo pomocne.
SubhasisM
1
Warto zauważyć, że w niektórych kluczowych cechach parametry pozycyjne różnią się od tablic bash. Na przykład nie obsługują rzadkich tablic, a ponieważ sh nie ma rozszerzenia parametrów krojenia, nie można uzyskać dostępu do list podrzędnych takich jak "${@:2:4}". Oczywiście, widzę podobieństwa , ale nie uważam parametrów pozycyjnych za tablicę per se.
kojiro
@kojiro, do pewnego stopnia, powiedziałbym, że jest przeciwnie, "$@"działa jak tablicy (jak tablice csh, rc, zsh, fish, yash...), to bardziej Korn / bash „tablice”, które nie są naprawdę, ale niektóre tablice forma tablic asocjacyjnych z kluczami ograniczonymi do dodatnich liczb całkowitych (mają również indeksy zaczynające się od 0 zamiast 1, jak we wszystkich innych powłokach z tablicami i „$ @”). Powłoki obsługujące krojenie mogą wycinać $ @ tak samo (z ksh93 / bash niezręcznie dodając 0 $ do parametrów pozycyjnych podczas wycinania „$ @”).
Stéphane Chazelas
3

W zwykłej powłoce Bourne'a nie ma tablic. Możesz użyć następującego sposobu, aby utworzyć tablicę i przejść przez nią:

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

Bez względu na to, w jaki sposób użyjesz tablic sh, zawsze będzie to uciążliwe. Rozważyć użycie innego języka, takich jak Pythonlub Perl, jeśli można, chyba że są skazani na bardzo ograniczonej platformie lub chcesz się czegoś nauczyć.

Arkadiusz Drabczyk
źródło
Dzięki za odpowiedzi...!! Rzeczywiście próbuję nauczyć się rzeczy w skrypcie powłoki ... w przeciwnym razie implementacja tablicy w Pythonie jest naprawdę bułka z masłem. To była wielka lekcja, że ​​istnieje jakiś język skryptowy, który nie obsługuje tablicy :) Jeden kod, który napisałeś, podaje błąd - „błąd składni w linii 6:„ nieoczekiwany $ ”” ... Jestem trochę zajęty teraz rozwiązałbym to ... proszę nie przejmuj się.
SubhasisM
@NoobGeek, powłoka Bourne'a nie ma $(...)składni. Musisz więc mieć skorupę Bourne'a. Czy korzystasz z Solaris 10 lub wcześniej? Możliwe, że nie będziesz mieć żadnego z seqnich. W Solarisie 10 i wcześniejszych chcesz, aby / usr / xpg4 / bin / sh miał standard shzamiast powłoki Bourne'a. Korzystanie z seqtego sposobu również nie jest zbyt dobre.
Stéphane Chazelas,
POSIX stwierdza, że ​​$ i `są równoważne w zastępowaniu poleceń: link . I dlaczego korzystanie w seqten sposób nie jest dobre?
Arkadiusz Drabczyk
2
Tak w muszli POSIX, należy wolisz $(...)nad `, ale OP /bin/shjest prawdopodobnie Bourne shell, a nie powłoki POSIX. Poza seqtym, że nie jest standardową komendą, robienie $(seq 100)oznacza przechowywanie całego wyniku w pamięci, a to oznacza, że ​​zależy to od bieżącej wartości $ IFS zawierającej znak nowej linii i nie zawierającej cyfr. Najlepiej używać i=1; while [ "$i" -le 100 ]; ...; i=$(($i + 1)); done(choć nie działałoby to również w powłoce Bourne'a).
Stéphane Chazelas,
1
@ Daenyth Powiedziałbym coś wręcz przeciwnego: najpierw uczenie się baszizmów, a później przenośnej /bin/shskładni, sprawia, że ​​ludzie myślą, że można użyć niewłaściwego #!/bin/shshebang, a następnie łamie skrypty, gdy inni próbują ich użyć. Radzimy nie zamieszczać tego rodzaju przynęty na płomienie. :)
Josip Rodin
2

Jak powiedzieli inni, Bourne Shell nie ma prawdziwych tablic.

Jednak w zależności od tego, co musisz zrobić, powinny wystarczyć ciągi rozdzielane:

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

Jeśli typowe ograniczniki (spacja, tabulator i znak nowej linii) nie wystarczą, możesz ustawić IFSdowolny ogranicznik przed pętlą.

A jeśli musisz zbudować tablicę programowo, możesz po prostu utworzyć łańcuch rozdzielany ograniczeniami.

Sildoreth
źródło
1
O ile tego nie chcesz (mało prawdopodobne), prawdopodobnie zechcesz także wyłączyć globowanie, co jest kolejnym efektem pozostawienia zmiennych bez cudzysłowu ( split+globoperator).
Stéphane Chazelas
0

Sposób, aby symulować tablice w desce rozdzielczej (może być dostosowany do dowolnej liczby wymiarów tablicy): (Należy pamiętać, że korzystanie z seqpolecenia wymaga IFS. Jest ustawiona na „” (spacja = wartość domyślna) można używać while ... do ...lub do ... while ...zamiast tego pętle, aby tego uniknąć (ja byłem seqw zakresie lepszej ilustracji tego, co robi kod).)

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

źródło
1
Zauważ, że chociaż localjest obsługiwany przez oba bashi dashnie jest POSIX. seqnie jest także poleceniem POSIX. Prawdopodobnie powinieneś wspomnieć, że twój kod przyjmuje pewne założenia dotyczące bieżącej wartości $ IFS (jeśli unikniesz używania seqi cytowania zmiennych, można tego uniknąć)
Stéphane Chazelas,