Tableaux dans Unix Bourne Shell

26

J'essaie d'utiliser des tableaux dans Bourne shell ( /bin/sh). J'ai trouvé que la façon d'initialiser les éléments d'un tableau est:

arr=(1 2 3)

Mais il rencontre une erreur:

syntax error at line 8: `arr=' unexpected

Maintenant, le message où j'ai trouvé cette syntaxe indique qu'elle est destinée bash, mais je n'ai trouvé aucune syntaxe distincte pour le shell Bourne. La syntaxe est-elle également la même /bin/sh?

SubhasisM
la source
1
consultez cette question stackoverflow.com/questions/9481702/… sur le débordement de la pile
Nischay
1
Thnx @Nischay ... Après avoir lu le lien que vous avez fourni, j'ai affiné ma chaîne de requête dans google et obtenu le lien - docstore.mik.ua/orelly/unix/upt/ch45_34.htm
SubhasisM

Réponses:

47

/bin/shn'est presque jamais un shell Bourne sur aucun système de nos jours (même Solaris qui était l'un des derniers systèmes majeurs à l'inclure est maintenant passé à un POSIX sh pour son / bin / sh dans Solaris 11). /bin/shétait l'obus Thompson au début des années 70. Le shell Bourne l'a remplacé dans Unix V7 en 1979.

/bin/sh a été le shell Bourne pendant de nombreuses années par la suite (ou le shell Almquist, une réimplémentation gratuite sur les BSD).

De nos jours, /bin/shest plus communément un interprète ou un autre pour le shlangage POSIX qui est lui-même basé sur un sous-ensemble du langage de ksh88 (et un sur-ensemble du langage shell Bourne avec quelques incompatibilités).

Le shell Bourne ou la spécification du langage POSIX sh ne prennent pas en charge les tableaux. Ou plutôt ils ont seulement un tableau: les paramètres de position ( $1, $2, $@, donc une matrice par fonction ainsi).

ksh88 avait des tableaux avec lesquels vous définissez set -A, mais qui n'ont pas été spécifiés dans le POSIX sh car la syntaxe est maladroite et peu utilisable.

D' autres coquilles avec des variables tableau / listes comprennent: csh/ tcsh, rc, es, bash(qui la plupart du temps copié la syntaxe ksh la façon dont ksh93), yash, zsh, fishchacun avec une syntaxe différente ( rcla coque de la fois à être le successeur d'Unix, fishet zshétant la plus cohérente ceux) ...

En standard sh(fonctionne également dans les versions modernes du 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)

(notez que dans le shell Bourne et ksh88, $IFSdoit contenir le caractère espace pour "$@"fonctionner correctement (un bug), et dans le shell Bourne, vous ne pouvez pas accéder aux éléments ci-dessus $9( ${10}ne fonctionnera pas, vous pouvez toujours faire shift 1; echo "$9"ou boucler leur)).

Stéphane Chazelas
la source
2
Merci beaucoup ... votre explication détaillée a été très utile.
SubhasisM
1
Il convient de noter que les paramètres de position diffèrent des tableaux bash dans certaines fonctionnalités clés. Par exemple, ils ne prennent pas en charge les tableaux clairsemés, et comme sh n'a pas d'extension de paramètres de découpage, vous ne pouvez pas accéder aux sous-listes comme "${@:2:4}". Bien sûr, je vois les similitudes , mais je ne considère pas les paramètres de position comme un tableau en soi.
kojiro
@kojiro, dans une certaine mesure, je dirais que c'est au contraire, "$@"agit comme un tableau (comme les tableaux de csh, rc, zsh, fish, yash...), il est plus Korn / bash « tableaux » qui ne sont pas vraiment des tableaux, mais certains forme de tableaux associatifs avec des clés limitées à des entiers positifs (ils ont également des indices commençant à 0 au lieu de 1 comme dans tous les autres shells avec des tableaux et "$ @"). Les shells qui prennent en charge le découpage peuvent découper $ @ tout de même (avec ksh93 / bash en ajoutant maladroitement $ 0 aux paramètres de position lorsque vous découpez "$ @").
Stéphane Chazelas
3

Il n'y a pas de tableaux en coque Bourne simple. Vous pouvez utiliser la méthode suivante pour créer un tableau et le parcourir:

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

Quelle que soit la façon d'utiliser les tableaux sh, choisiriez-vous que ce sera toujours lourd. Pensez à utiliser une langue différente comme Pythonou Perlsi vous le pouvez, sauf si vous êtes coincé avec une plate-forme très limitée ou si vous voulez apprendre quelque chose.

Arkadiusz Drabczyk
la source
Merci pour la réponse...!! En fait, j'essaie en effet d'apprendre des choses dans un script shell ... sinon l'implémentation de tableau en Python est vraiment un jeu d'enfant. Ce fut une grande leçon qu'il existe un langage de script qui ne prend pas en charge les tableaux :) Une chose, le code que vous avez publié donne une erreur - "erreur de syntaxe à la ligne 6:` $ 'inattendu "... Je suis un peu occupé maintenant, je le résoudrais ... plz ne vous embêtez pas.
SubhasisM
@NoobGeek, le shell Bourne n'a pas la $(...)syntaxe. Il faut donc bien avoir le shell Bourne. Êtes-vous sur Solaris 10 ou avant? Il y a de fortes chances que vous n'en ayez pas non seqplus. Sous Solaris 10 et versions antérieures, vous souhaitez que / usr / xpg4 / bin / sh ait un standard shau lieu d'un shell Bourne. L'utilisation de seqcette façon n'est pas très bonne non plus.
Stéphane Chazelas
POSIX indique que $ et `sont équivalents dans la substitution de commande: lien . Et pourquoi l'utilisation de seqcette façon n'est-elle pas bonne?
Arkadiusz Drabczyk
2
Oui dans des coquilles POSIX, vous devriez préférer $(...)plus `, mais de l'OP /bin/shest probablement Bourne shell, pas une coquille POSIX. En plus de seqne pas être une commande standard, faire $(seq 100)signifie stocker la totalité de la sortie en mémoire, ce qui signifie qu'elle dépend de la valeur actuelle de $ IFS contenant une nouvelle ligne et ne contenant pas de chiffres. Mieux à utiliser i=1; while [ "$i" -le 100 ]; ...; i=$(($i + 1)); done(bien que cela ne fonctionnerait pas non plus dans le shell Bourne).
Stéphane Chazelas
1
@Daenyth Je dirais tout à fait le contraire: apprendre les bashismes d'abord, puis la /bin/shsyntaxe portable plus tard, a tendance à faire croire aux gens qu'il est correct d'utiliser le mauvais #!/bin/shshebang, puis casse leurs scripts lorsque d'autres personnes essaient de les utiliser. Vous seriez bien avisé de ne pas poster ce genre d'appât de flamme. :)
Josip Rodin
2

Comme les autres l'ont dit, le Bourne Shell n'a pas de vrais tableaux.

Cependant, selon ce que vous devez faire, des chaînes délimitées devraient suffire:

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

Si les délimiteurs typiques (espace, tabulation et nouvelle ligne) ne suffisent pas, vous pouvez définir le IFSdélimiteur souhaité avant la boucle.

Et si vous devez créer le tableau par programme, vous pouvez simplement créer une chaîne délimitée.

Sildoreth
la source
1
À moins que vous ne le vouliez (peu probable), vous voudrez probablement également désactiver la globalisation, ce qui est un autre effet de laisser des variables sans guillemets comme ça (l' split+globopérateur).
Stéphane Chazelas
0

Un moyen de simuler des tableaux en tiret (il peut être adapté à n'importe quel nombre de dimensions d'un tableau): (Veuillez noter que l'utilisation de la seqcommande nécessite qu'elle IFSsoit définie sur '' (ESPACE = la valeur par défaut). Vous pouvez utiliser while ... do ...ou do ... while ...boucles à la place pour éviter cela (j'ai gardé seqla portée d'une meilleure illustration de ce que fait le code).)

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

la source
1
Notez que tandis que localest pris en charge par les deux bashet dash, ce n'est pas POSIX. seqn'est pas non plus une commande POSIX. Vous devriez probablement mentionner que votre code fait quelques hypothèses sur la valeur actuelle de $ IFS (si vous évitez d'utiliser seqet de citer vos variables, cela peut être évité)
Stéphane Chazelas