Como dividir uma matriz no Bash

194

Olhando a seção "Matriz" na página do manual bash (1), não encontrei uma maneira de dividir uma matriz.

Então, eu vim com essa função excessivamente 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 assim:

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

Existe uma maneira melhor de fazer isso?

Chen Levy
fonte
Eu estava procurando como cortar o final de uma matriz e foi direcionado aqui. A resposta não foi encontrada aqui e será uma duplicata, porque eu encontrei a resposta aqui stackoverflow.com/questions/44939747/… . A idéia básica é que podemos ter uma expressão aritmética como $ {# array [@]} - (2 + 7), onde o comprimento é esperado na construção $ {array: offset: length}. Nenhuma das respostas fornecidas aqui ilustra isso.
Dominic108

Respostas:

310

Consulte a seção Expansão de parâmetros na manpágina Bash . A[@]retorna o conteúdo da matriz, :1:2pega uma fatia do comprimento 2, iniciando no í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

Observe que o fato de "ab c" ser um elemento da matriz (e que contém um espaço extra) é preservado.

Pausado até novo aviso.
fonte
2
Legal. Procurei na seção Matriz e não a vi lá.
21410 Chen Levy
36
Isso é tolice Chen, por que estaria na seção Array? * sarc
deltaray
1
@AquariusPower: Crie uma matriz de índices e cortá-lo: idx=(${!A[@]}); echo ${idx[@]:1}.
Pausado até novo aviso.
7
@Feuermurmel: faça isso sem os colchetes de indexação:${@:1:2}
Pausado até novo aviso.
5
@DennisWilliamson eu achei que eu precisava para converter $@para uma matriz adequada antes de fazer isso ou argumentos que continha espaços iria ficar dividido:ARGS=( "$@" ); ARGS_AFTER_FIRST=( "${ARGS[@]:1}" )
Heath Borders
47

Também existe um atalho conveniente para obter todos os elementos da matriz começando com o índice especificado. Por exemplo "$ {A [@]: 1}" seria a "cauda" da matriz, ou seja, a matriz sem seu primeiro elemento.

version=4.7.1
A=( ${version//\./ } )
echo "${A[@]}"    # 4 7 1
B=( "${A[@]:1}" )
echo "${B[@]}"    # 7 1
Nicholas Sushkin
fonte
8
E enquanto você está nisso:echo "${A[@]::1}" # 4
Chen Levy
7
Isso é ótimo, mas deve-se notar que, se usado em uma função, deve ser ligeiramente alterado para ser lido "${${@}[@]:1}".
Alex Gray
@ AlexGray: Isso me dá "má substituição" aqui, mas ${@:2}funciona bem.
Nick Matteo
3

Matriz cortando como em Python (da biblioteca de atualização ):

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
fonte
1

Digamos que estou lendo uma matriz do usuário, e quero ver os elementos 3 a 7, inclusive.

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


echo ${myarr[@]:3:5}
Arindam Roychowdhury
fonte
4
A sintaxe da fatia no seu código é idêntica à resposta aceita nos 8 anos de idade. Sua resposta não acrescenta nada de novo.
precisa saber é o seguinte