Arrays in der Unix Bourne Shell

26

Ich versuche, Arrays in der Bourne-Shell ( /bin/sh) zu verwenden. Ich fand, dass der Weg zum Initialisieren von Array-Elementen ist:

arr=(1 2 3)

Aber es ist ein Fehler aufgetreten:

syntax error at line 8: `arr=' unexpected

In dem Beitrag, in dem ich diese Syntax gefunden habe, heißt es, dass sie bashfür die Bourne-Shell bestimmt ist , aber ich konnte keine separate Syntax finden. Steht die Syntax /bin/shauch gleich?

SubhasisM
quelle
1
Überprüfen Sie diese Frage stackoverflow.com/questions/9481702/… auf Stapelüberlauf
Nischay
1
Vielen Dank @Nischay ... Nachdem ich den von Ihnen angegebenen Link gelesen hatte, verfeinerte ich meine Abfragezeichenfolge
SubhasisM

Antworten:

47

/bin/shist heutzutage auf keinem System mehr eine Bourne-Shell (selbst Solaris, das als eines der letzten großen Systeme einbezogen wurde, hat in Solaris 11 für / bin / sh auf POSIX sh umgestellt). /bin/shwar die Thompson-Shell in den frühen 70er Jahren. Die Bourne-Shell ersetzte sie 1979 in Unix V7.

/bin/sh ist seit vielen Jahren die Bourne-Shell (oder die Almquist-Shell, eine kostenlose Neuimplementierung von BSDs).

Heutzutage /bin/shist eher ein Dolmetscher oder ein anderer für die POSIXsh Sprache, der selbst auf einer Teilmenge der Sprache von ksh88 (und einer Obermenge der Bourne-Shell-Sprache mit einigen Inkompatibilitäten) basiert.

Die Bourne-Shell oder die POSIX-Sprachspezifikation unterstützen keine Arrays. Oder besser gesagt sie haben nur ein Array: die Positionsparameter ( $1, $2,$@ , so ein Array pro Funktion als auch).

ksh88 hatte Arrays, die Sie eingestellt haben set -A, aber diese wurden in POSIX sh nicht angegeben, da die Syntax umständlich und wenig benutzerfreundlich ist.

Andere Schalen mit Array / Listen Variablen umfassen: csh/ tcsh, rc, es, bash(was meistens der KSH Syntax der ksh93 Weise kopiert), yash, zsh, die fishjeweils mit einer anderen Syntax ( rcdie Schale des einmal zu-sein Nachfolger von Unix, fishund zshwobei die beständigsten Einsen)...

Standardmäßig sh(funktioniert auch in modernen Versionen der Bourne-Shell):

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)

(Beachten Sie, dass in der Bourne-Shell und in ksh88 $IFSdas Leerzeichen enthalten sein muss "$@", damit es ordnungsgemäß funktioniert (ein Fehler), und dass Sie in der Bourne-Shell nicht auf die obigen Elemente zugreifen können $9( ${10}nicht funktionieren, Sie können immer noch eine shift 1; echo "$9"Schleife ausführen oder einen Loop ausführen) Sie)).

Stéphane Chazelas
quelle
2
Vielen Dank ... Ihre ausführliche Erklärung war sehr hilfreich.
SubhasisM
1
Es kann erwähnenswert sein, dass sich Positionsparameter in einigen Schlüsselmerkmalen von Bash-Arrays unterscheiden. Beispielsweise unterstützen sie keine spärlichen Arrays, und da sh keine Slicing-Parameter-Erweiterung hat, können Sie nicht auf Unterlisten wie zugreifen "${@:2:4}". Natürlich sehe ich die Ähnlichkeiten , aber ich betrachte Positionsparameter nicht als Array an sich.
Kojiro
@kojiro zu einem gewissen Grad, würde ich sagen , es ist das Gegenteil, "$@"wirkt wie ein Array (wie die Anordnungen von csh, rc, zsh, fish, yash...), es ist mehr die Korn / bash „arrays“ , die nicht wirklich Arrays sind, aber einige Form von assoziativen Arrays mit Schlüsseln, die auf positive Ganzzahlen beschränkt sind (sie haben auch Indizes, die bei 0 anstelle von 1 beginnen, wie in allen anderen Shells mit Arrays und "$ @"). Shells, die Slicing unterstützen, können ebenfalls $ @ in Scheiben schneiden (wobei ksh93 / bash den Positionsparametern umständlich $ 0 hinzufügt, wenn Sie "$ @" in Scheiben schneiden).
Stéphane Chazelas
3

In der einfachen Bourne-Shell gibt es keine Arrays. Sie können ein Array auf folgende Weise erstellen und durchlaufen:

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

Unabhängig davon, in welcher Weise Arrays verwendet shwerden, ist dies immer umständlich. Ziehen Sie in Betracht, eine andere Sprache zu verwenden, z. B. Pythonoder Perlwenn Sie können, es sei denn, Sie haben eine sehr eingeschränkte Plattform oder möchten etwas lernen.

Arkadiusz Drabczyk
quelle
Danke für die Antwort...!! Eigentlich versuche ich tatsächlich, Dinge in Shell-Skripten zu lernen ... ansonsten ist das Implementieren von Arrays in Python wirklich ein Kinderspiel. Dies war eine große Lektion, dass es eine Skriptsprache gibt, die kein Array unterstützt :) Der von Ihnen veröffentlichte Code gibt einen Fehler aus - "Syntaxfehler in Zeile 6:" $ "unerwartet" ... Ich bin ein wenig beschäftigt jetzt würde ich es gelöst bekommen ... plz stört mich nicht.
SubhasisM
@NoobGeek, die Bourne-Shell hat keine $(...)Syntax. Sie müssen also in der Tat die Bourne-Shell haben. Sind Sie auf Solaris 10 oder früher? Möglicherweise haben Sie auch keine seq. Unter Solaris 10 und früheren Versionen soll / usr / xpg4 / bin / sh einen Standard shanstelle einer Bourne-Shell haben. Auf seqdiese Weise ist es auch nicht sehr gut.
Stéphane Chazelas
POSIX gibt an, dass $ und `bei der Befehlsersetzung gleichbedeutend sind: link . Und warum ist die Verwendung seqdieses Weges nicht gut?
Arkadiusz Drabczyk
2
Ja in POSIX Schalen, sollten Sie es vorziehen , $(...)über `, aber die OP ist /bin/shwahrscheinlich eine Bourne - Shell, kein POSIX - Shell. Neben der Tatsache, dass es sich seqnicht um einen Standardbefehl handelt, $(seq 100)bedeutet dies, dass die gesamte Ausgabe im Speicher abgelegt wird. Dies hängt vom aktuellen Wert von $ IFS ab, der Zeilenvorschub enthält und keine Ziffern enthält. Am besten zu verwenden i=1; while [ "$i" -le 100 ]; ...; i=$(($i + 1)); done(obwohl das auch in der Bourne-Shell nicht funktioniert).
Stéphane Chazelas
1
@Daenyth Ich würde das Gegenteil sagen: Erst Bashismen lernen und später die übertragbare /bin/shSyntax, lässt die Leute denken, dass es in Ordnung ist, den falschen #!/bin/shSchebang zu verwenden, und bricht dann ihre Skripte, wenn andere Leute versuchen, sie zu verwenden. Sie sind gut beraten, diese Art von Flammenschwarm nicht zu veröffentlichen. :)
Josip Rodin
2

Wie die anderen gesagt haben, hat die Bourne Shell keine echten Arrays.

Abhängig davon, was Sie tun müssen, sollten jedoch durch Trennzeichen getrennte Zeichenfolgen ausreichen:

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

Wenn die typischen Trennzeichen (Leerzeichen, Tabulator und Zeilenvorschub) nicht ausreichen, können IFSSie vor der Schleife ein beliebiges Trennzeichen festlegen .

Und wenn Sie das Array programmgesteuert erstellen müssen, können Sie einfach eine durch Trennzeichen getrennte Zeichenfolge erstellen.

Sildoreth
quelle
1
Wenn Sie dies nicht möchten (unwahrscheinlich), möchten Sie wahrscheinlich auch das Globbing deaktivieren. Dies ist ein weiterer Effekt, wenn Sie Variablen wie diesen (den split+globOperator) nicht in Anführungszeichen setzen .
Stéphane Chazelas
0

Eine Möglichkeit, Arrays im Bindestrich zu simulieren (kann für eine beliebige Anzahl von Dimensionen eines Arrays angepasst werden): (Beachten Sie, dass für die Verwendung des seqBefehls die IFSEinstellung '' (SPACE = der Standardwert) erforderlich ist. Sie können while ... do ...oder verwenden do ... while ...Schleifen stattdessen, um dies zu vermeiden (ich habe seqim Rahmen einer besseren Veranschaulichung dessen, was der Code tut, gehalten).

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

quelle
1
Beachten Sie, dass while localvon beiden bashund unterstützt wird und dashnicht POSIX ist. seqist auch kein POSIX-Befehl. Sie sollten wahrscheinlich erwähnen, dass Ihr Code einige Annahmen über den aktuellen Wert von $ IFS macht (wenn Sie die Verwendung seqund Anführungszeichen Ihrer Variablen vermeiden , kann dies vermieden werden)
Stéphane Chazelas