Cómo cortar una matriz en Bash

194

Mirando la sección "Array" en la página de manual bash (1), no encontré una manera de cortar una matriz.

Entonces se me ocurrió esta función demasiado complicada:

#!/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 )"
}

Usado así:

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

¿Hay una mejor manera de hacer esto?

Chen Levy
fuente
Estaba buscando cómo cortar el extremo de una matriz y me dirigieron aquí. La respuesta no se encuentra aquí y será un duplicado hacerlo, porque encontré la respuesta aquí stackoverflow.com/questions/44939747/… . La idea básica es que podemos tener una expresión aritmética como $ {# array [@]} - (2 + 7) donde se espera la longitud en la construcción $ {array: offset: length}. Ninguna de las respuestas proporcionadas aquí ilustran eso.
Dominic108

Respuestas:

310

Consulte la sección Expansión de parámetros en la manpágina Bash . A[@]devuelve el contenido de la matriz, :1:2toma una porción de longitud 2, comenzando en el índice 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

Tenga en cuenta que se conserva el hecho de que "ab c" es un elemento de matriz (y que contiene un espacio adicional).

Pausado hasta nuevo aviso.
fuente
2
Frio. Miré en la sección Array, y no lo vi allí.
Chen Levy el
36
Eso es tonto Chen, ¿por qué estaría en la sección Array? * sarc
deltaray
1
@AquariusPower: Crear un conjunto de índices y se mire: idx=(${!A[@]}); echo ${idx[@]:1}.
Pausado hasta nuevo aviso.
77
@Feuermurmel: Solo hágalo sin los corchetes de indexación:${@:1:2}
Pausado hasta nuevo aviso.
55
@DennisWilliamson Descubrí que necesitaba convertirme $@a una matriz adecuada antes de hacer esto o los argumentos que contenían espacios se dividirían:ARGS=( "$@" ); ARGS_AFTER_FIRST=( "${ARGS[@]:1}" )
Heath Borders el
47

También hay un atajo conveniente para obtener todos los elementos de la matriz comenzando con el índice especificado. Por ejemplo, "$ {A [@]: 1}" sería la "cola" de la matriz, es decir, la matriz sin su primer elemento.

version=4.7.1
A=( ${version//\./ } )
echo "${A[@]}"    # 4 7 1
B=( "${A[@]:1}" )
echo "${B[@]}"    # 7 1
Nicholas Sushkin
fuente
8
Y mientras lo haces:echo "${A[@]::1}" # 4
Chen Levy
77
Esto es genial, pero debe tenerse en cuenta que si se usa dentro de una función, debe modificarse ligeramente para leer "${${@}[@]:1}".
Alex Gray
@AlexGray: Eso me da "mala sustitución" aquí, pero ${@:2}funciona bien.
Nick Matteo
3

Corte de matriz como en Python (de la biblioteca 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
fuente
1

Digamos que estoy leyendo una matriz del usuario, luego quiero ver los elementos 3 a 7 ambos inclusive.

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


echo ${myarr[@]:3:5}
Arindam Roychowdhury
fuente
44
La sintaxis de corte en su código es idéntica a la de la respuesta aceptada de 8 años. Tu respuesta no agrega nada nuevo.
melpomene