Comment découper un tableau dans Bash

200

En regardant la section "Array" de la page de manuel bash (1), je n'ai pas trouvé de moyen de découper un tableau.

Je suis donc venu avec cette fonction trop compliquée:

#!/bin/bash

# @brief: slice a bash array
# @arg1:  output-name
# @arg2:  input-name
# @args:  seq args
# ----------------------------------------------
function slice() {
   local output=$1
   local input=$2
   shift 2
   local indexes=$(seq $*)

   local -i i
   local tmp=$(for i in $indexes 
                 do echo "$(eval echo \"\${$input[$i]}\")" 
               done)

   local IFS=$'\n'
   eval $output="( \$tmp )"
}

Utilisé comme ceci:

$ A=( foo bar "a  b c" 42 )
$ slice B A 1 2
$ echo "${B[0]}"  # bar
$ echo "${B[1]}"  # a  b c

Y a-t-il une meilleure manière de faire cela?

Chen Levy
la source
Je cherchais comment découper la fin d'un tableau et j'ai été dirigé ici. La réponse ne se trouve pas ici et ce sera un doublon pour le faire, car j'ai trouvé la réponse ici stackoverflow.com/questions/44939747/… . L'idée de base est que nous pouvons avoir une expression arithmétique telle que $ {# array [@]} - (2 + 7) où la longueur est attendue dans la construction $ {array: offset: length}. Aucune des réponses fournies ici n'illustre cela.
Dominic108

Réponses:

319

Consultez la section Extension des paramètres de la manpage Bash . A[@]renvoie le contenu du tableau, :1:2prend une tranche de longueur 2, en commençant à l'index 1.

A=( foo bar "a  b c" 42 )
B=("${A[@]:1:2}")
C=("${A[@]:1}")       # slice to the end of the array
echo "${B[@]}"        # bar a  b c
echo "${B[1]}"        # a  b c
echo "${C[@]}"        # bar a  b c 42
echo "${C[@]: -2:2}"  # a  b c 42 # The space before the - is necesssary

Notez que le fait que "ab c" soit un élément du tableau (et qu'il contienne un espace supplémentaire) est conservé.

Suspendu jusqu'à nouvel ordre.
la source
2
Cool. J'ai regardé dans la section Array et je ne l'ai pas vue.
Chen Levy
36
C'est idiot Chen, pourquoi serait-il dans la section Array? * sarc
deltaray
1
@AquariusPower: Créer un tableau d'indices et il tranche: idx=(${!A[@]}); echo ${idx[@]:1}.
Suspendu jusqu'à nouvel ordre.
7
@Feuermurmel: Faites-le simplement sans les crochets d'indexation:${@:1:2}
pause jusqu'à nouvel ordre.
5
@DennisWilliamson J'ai trouvé que je devais convertir $@en un tableau approprié avant de faire cela, sinon les arguments contenant des espaces seraient divisés:ARGS=( "$@" ); ARGS_AFTER_FIRST=( "${ARGS[@]:1}" )
Heath Borders
48

Il existe également un raccourci pratique pour obtenir tous les éléments du tableau en commençant par l'index spécifié. Par exemple, "$ {A [@]: 1}" serait la "queue" du tableau, c'est-à-dire le tableau sans son premier élément.

version=4.7.1
A=( ${version//\./ } )
echo "${A[@]}"    # 4 7 1
B=( "${A[@]:1}" )
echo "${B[@]}"    # 7 1
Nicolas Sushkin
la source
8
Et pendant que vous y êtes:echo "${A[@]::1}" # 4
Chen Levy
7
C'est génial, mais il convient de noter que s'il est utilisé dans une fonction, il doit être légèrement modifié pour être lu "${${@}[@]:1}".
Alex Gray
@AlexGray: Cela me donne une "mauvaise substitution" ici, mais ça ${@:2}marche bien.
Nick Matteo
3

Tranche de tableau comme en Python (à partir de la bibliothèque rebash ):

array_slice() {
    local __doc__='
    Returns a slice of an array (similar to Python).

    From the Python documentation:
    One way to remember how slices work is to think of the indices as pointing
    between elements, with the left edge of the first character numbered 0.
    Then the right edge of the last element of an array of length n has
    index n, for example:
    ```
    +---+---+---+---+---+---+
    | 0 | 1 | 2 | 3 | 4 | 5 |
    +---+---+---+---+---+---+
    0   1   2   3   4   5   6
    -6  -5  -4  -3  -2  -1
    ```

    >>> local a=(0 1 2 3 4 5)
    >>> echo $(array.slice 1:-2 "${a[@]}")
    1 2 3
    >>> local a=(0 1 2 3 4 5)
    >>> echo $(array.slice 0:1 "${a[@]}")
    0
    >>> local a=(0 1 2 3 4 5)
    >>> [ -z "$(array.slice 1:1 "${a[@]}")" ] && echo empty
    empty
    >>> local a=(0 1 2 3 4 5)
    >>> [ -z "$(array.slice 2:1 "${a[@]}")" ] && echo empty
    empty
    >>> local a=(0 1 2 3 4 5)
    >>> [ -z "$(array.slice -2:-3 "${a[@]}")" ] && echo empty
    empty
    >>> [ -z "$(array.slice -2:-2 "${a[@]}")" ] && echo empty
    empty

    Slice indices have useful defaults; an omitted first index defaults to
    zero, an omitted second index defaults to the size of the string being
    sliced.
    >>> local a=(0 1 2 3 4 5)
    >>> # from the beginning to position 2 (excluded)
    >>> echo $(array.slice 0:2 "${a[@]}")
    >>> echo $(array.slice :2 "${a[@]}")
    0 1
    0 1

    >>> local a=(0 1 2 3 4 5)
    >>> # from position 3 (included) to the end
    >>> echo $(array.slice 3:"${#a[@]}" "${a[@]}")
    >>> echo $(array.slice 3: "${a[@]}")
    3 4 5
    3 4 5

    >>> local a=(0 1 2 3 4 5)
    >>> # from the second-last (included) to the end
    >>> echo $(array.slice -2:"${#a[@]}" "${a[@]}")
    >>> echo $(array.slice -2: "${a[@]}")
    4 5
    4 5

    >>> local a=(0 1 2 3 4 5)
    >>> echo $(array.slice -4:-2 "${a[@]}")
    2 3

    If no range is given, it works like normal array indices.
    >>> local a=(0 1 2 3 4 5)
    >>> echo $(array.slice -1 "${a[@]}")
    5
    >>> local a=(0 1 2 3 4 5)
    >>> echo $(array.slice -2 "${a[@]}")
    4
    >>> local a=(0 1 2 3 4 5)
    >>> echo $(array.slice 0 "${a[@]}")
    0
    >>> local a=(0 1 2 3 4 5)
    >>> echo $(array.slice 1 "${a[@]}")
    1
    >>> local a=(0 1 2 3 4 5)
    >>> array.slice 6 "${a[@]}"; echo $?
    1
    >>> local a=(0 1 2 3 4 5)
    >>> array.slice -7 "${a[@]}"; echo $?
    1
    '
    local start end array_length length
    if [[ $1 == *:* ]]; then
        IFS=":"; read -r start end <<<"$1"
        shift
        array_length="$#"
        # defaults
        [ -z "$end" ] && end=$array_length
        [ -z "$start" ] && start=0
        (( start < 0 )) && let "start=(( array_length + start ))"
        (( end < 0 )) && let "end=(( array_length + end ))"
    else
        start="$1"
        shift
        array_length="$#"
        (( start < 0 )) && let "start=(( array_length + start ))"
        let "end=(( start + 1 ))"
    fi
    let "length=(( end - start ))"
    (( start < 0 )) && return 1
    # check bounds
    (( length < 0 )) && return 1
    (( start < 0 )) && return 1
    (( start >= array_length )) && return 1
    # parameters start with $1, so add 1 to $start
    let "start=(( start + 1 ))"
    echo "${@: $start:$length}"
}
alias array.slice="array_slice"
jandob
la source
1

Disons que je lis un tableau de l'utilisateur, alors je veux voir les éléments 3 à 7 inclus.

cnt=0
while read var;
    do
    myarr[cnt]=$var
    cnt=$((cnt+1)) 
    done


echo ${myarr[@]:3:5}
Arindam Roychowdhury
la source
4
La syntaxe de tranche dans votre code est identique à celle de la réponse acceptée de 8 ans. Votre réponse n'ajoute rien de nouveau.
melpomene