Teste para suporte a array por shell

11

Existe uma maneira concisa de testar o suporte de matriz pelo shell local semelhante a Bourne na linha de comando?

Isso é sempre possível:

$ arr=(0 1 2 3);if [ "${arr[2]}" != 2 ];then echo "No array support";fi

ou teste $SHELLe versão do shell:

$ eval $(echo "$SHELL --version") | grep version

e depois lendo a página de manual, assumindo que eu tenho acesso a ela. (Mesmo lá, escrevendo a partir de /bin/bash, estou assumindo que todas as conchas semelhantes a Bourne admitem a opção longa --version, quando isso ocorre por ksh, por exemplo ).

Estou procurando um teste simples que possa ser autônomo e incorporado em uma seção de Uso no início do script ou mesmo antes de chamá-lo.

Cbhihe
fonte
Suponho que você queira limitar a conchas tipo Bourne?
Stéphane Chazelas
@ StéphaneChazelas: Sim, se você quer dizer (não exaustivamente) o grupo principal formado por: sh, csh, ksh, tcsh, bash, zsh e amigos próximos. Não sei onde o yash se posiciona nesta constelação.
Cbhihe 23/10/2015
2
cshnão é uma casca de bourne. tcshnão é um quer (é cshcom alguns bugs corrigidos)
cas
1
Observe que $SHELLé o shell preferido do usuário, como $EDITORé o seu editor de texto preferido. Tem pouco a ver com o shell atualmente em execução.
Stéphane Chazelas
1
evalUsar a saída $SHELL --versioncomo código shell não faz sentido.
Stéphane Chazelas

Respostas:

12

Supondo que você deseja restringir a Bourne como conchas (muitas outras escudos como csh, tcsh, rc, esou fishmatrizes de apoio, mas escrever um script compatível ao mesmo tempo para Bourne-como conchas e aqueles é complicado e geralmente inútil como eles são intérpretes para completamente diferente e idiomas incompatíveis), observe que existem diferenças significativas entre as implementações.

Os Bourne como conchas que suportam matrizes são:

  • ksh88(esse é o primeiro a implementar matrizes, o ksh88 ainda é encontrado como kshnos escritórios comerciais mais tradicionais, onde também é a base sh)

    • matrizes são unidimensionais
    • As matrizes são definidas como set -A array foo barou set -A array -- "$var" ...se você não pode garantir que $varnão começará com um -ou +.
    • Os índices da matriz começam em 0.
    • Elementos de matriz individuais são atribuídos como a[1]=value.
    • matrizes são esparsas. Ou seja a[5]=foo, funcionará mesmo se a[0,1,2,3,4]não estiver definido e os deixará desativados.
    • ${a[5]}para acessar o elemento do índice 5 (não necessariamente o sexto elemento se a matriz for escassa). O 5que pode haver qualquer expressão aritmética.
    • o tamanho e o índice da matriz são limitados (a 4096).
    • ${#a[@]} é o número do elemento designado na matriz (não o maior índice atribuído).
    • não há como conhecer a lista de subscritos atribuídos (além de testar individualmente os 4096 elementos [[ -n "${a[i]+set}" ]]).
    • $aé o mesmo que ${a[0]}. Ou seja, matrizes de alguma forma estendem variáveis ​​escalares, dando-lhes valores extras.
  • pdkshe derivativos (essa é a base para kshe, às vezes, shvários BSDs e foi a única implementação do ksh de código aberto antes da liberação do código do ksh93):

    Principalmente como ksh88mas note:

    • Algumas implementações antigas não eram compatíveis set -A array -- foo bar( --não eram necessárias lá).
    • ${#a[@]}é um mais o índice do maior índice atribuído. ( a[1000]=1; echo "${#a[@]}"gera 1001 mesmo que a matriz tenha apenas um elemento.
    • nas versões mais recentes, o tamanho da matriz não é mais limitado (exceto pelo tamanho dos números inteiros).
    • versões recentes de mkshter alguns operadores extras inspirados bash, ksh93ou zshcomo atribuições a la a=(x y), a+=(z), ${!a[@]}para obter a lista de índices atribuídos.
  • zsh. zshmatrizes geralmente são melhor projetadas e tiram o melhor de kshe cshmatrizes. Eles são semelhantes, kshmas com diferenças significativas:

    • índices começam em 1, não em 0 (exceto kshemulação), que é consistente com a matriz Bourne (os parâmetros de posição $ @, que zshtambém expõe como sua matriz $ argv) e cshmatrizes.
    • eles são um tipo separado das variáveis ​​normais / escalares. Os operadores se aplicam de maneira diferente a eles e como você geralmente espera. $anão é o mesmo que, ${a[0]}mas se expande para os elementos não vazios da matriz ( "${a[@]}"para todos os elementos como em ksh).
    • eles são matrizes normais, não matrizes esparsas. a[5]=1funciona, mas atribui todos os elementos de 1 a 4 a sequência vazia, se eles não foram atribuídos. Então ${#a[@]}(o mesmo ${#a}que em ksh é o tamanho do elemento do índice 0) é o número de elementos na matriz e o maior índice atribuído.
    • matrizes associativas são suportadas.
    • um grande número de operadores para trabalhar com matrizes é suportado, grande demais para listar aqui.
    • matrizes definidas como a=(x y). set -A a x ytambém funciona, mas set -A a -- x ynão é suportado, exceto na emulação ksh ( --não é necessária na emulação zsh).
  • ksh93. (aqui descrevendo as versões mais recentes). ksh93, considerados há muito experimentais, agora podem ser encontrados em mais e mais sistemas, agora que foram lançados como FOSS. Por exemplo, é o /bin/sh(onde substituiu o shell Bourne, /usr/xpg4/bin/sho shell POSIX ainda se baseia ksh88) e kshde Solaris 11. Suas matrizes estendem e aprimoram o ksh88.

    • a=(x y)pode ser usado para definir uma matriz, mas como a=(...)também é usado para definir variáveis ​​compostas ( a=(foo=bar bar=baz)), a=()é ambíguo e declara uma variável composta, não uma matriz.
    • matrizes são multidimensionais ( a=((0 1) (0 2))) e elementos de matriz também podem ser variáveis ​​compostas ( a=((a b) (c=d d=f)); echo "${a[1].c}").
    • Uma a=([2]=foo [5]=bar)sintaxe pode ser usada para definir matrizes esparsas de uma só vez.
    • Limitações de tamanho levantadas.
    • Não na extensão de zsh, mas um grande número de operadores suportados também para manipular matrizes.
    • "${!a[@]}" para recuperar a lista de índices da matriz.
    • matrizes associativas também suportadas como um tipo separado.
  • bash. bashé o shell do projeto GNU. É usado como shnas versões recentes do OS / X e em algumas distribuições GNU / Linux. bashmatrizes emulam principalmente ksh88aquelas com alguns recursos de ksh93e zsh.

    • a=(x y)suportado. set -A a x y não suportado. a=()cria uma matriz vazia (sem variáveis ​​compostas bash).
    • "${!a[@]}" para a lista de índices.
    • a=([foo]=bar)sintaxe suportada, bem como algumas outras de ksh93e zsh.
    • bashversões recentes também suportam matrizes associativas como um tipo separado.
  • yash. É uma implementação POSIX sh relativamente recente, limpa e com reconhecimento de vários bytes. Não é amplamente utilizado. Suas matrizes são outra API limpa semelhante azsh

    • matrizes não são esparsas
    • Os índices de matriz começam em 1
    • definido (e declarado) com a=(var value)
    • elementos inseridos, deletados ou modificados com o arrayembutido
    • array -s a 5 valuepara modificar o 5 th elemento iria falhar se esse elemento não foi atribuído anteriormente.
    • o número de elementos na matriz é ${a[#]}, ${#a[@]}sendo o tamanho dos elementos como uma lista.
    • matrizes são um tipo separado. Você precisa a=("$a")redefinir uma variável escalar como uma matriz antes de poder adicionar ou modificar elementos.
    • matrizes não são suportadas quando chamadas como sh.

Então, a partir disso, você pode ver a detecção de suporte à matriz, o que você poderia fazer com:

if (unset a; set -A a a; eval "a=(a b)"; eval '[ -n "${a[1]}" ]'
   ) > /dev/null 2>&1
then
  array_supported=true
else
  array_supported=false
fi

não é suficiente para poder usar essas matrizes. Você precisaria definir comandos de wrapper para atribuir matrizes como um todo e elementos individuais e certifique-se de não tentar criar matrizes esparsas.

Gostar

unset a
array_elements() { eval "REPLY=\"\${#$1[@]}\""; }
if (set -A a -- a) 2> /dev/null; then
  set -A a -- a b
  case ${a[0]}${a[1]} in
    --) set_array() { eval "shift; set -A $1"' "$@"'; }
        set_array_element() { eval "$1[1+(\$2)]=\$3"; }
        first_indice=0;;
     a) set_array() { eval "shift; set -A $1"' -- "$@"'; }
        set_array_element() { eval "$1[1+(\$2)]=\$3"; }
        first_indice=1;;
   --a) set_array() { eval "shift; set -A $1"' "$@"'; }
        set_array_element() { eval "$1[\$2]=\$3"; }
        first_indice=0;;
    ab) set_array() { eval "shift; set -A $1"' -- "$@"'; }
        set_array_element() { eval "$1[\$2]=\$3"; }
        first_indice=0;;
  esac
elif (eval 'a[5]=x') 2> /dev/null; then
  set_array() { eval "shift; $1=("'"$@")'; }
  set_array_element() { eval "$1[\$2]=\$3"; }
  first_indice=0
elif (eval 'a=(x) && array -s a 1 y && [ "${a[1]}" = y ]') 2> /dev/null; then
  set_array() { eval "shift; $1=("'"$@")'; }
  set_array_element() {
    eval "
      $1=(\${$1+\"\${$1[@]}"'"})
      while [ "$(($2))" -ge  "${'"$1"'[#]}" ]; do
        array -i "$1" "$2" ""
      done'
    array -s -- "$1" "$((1+$2))" "$3"
   }
  array_elements() { eval "REPLY=\${$1[#]}"; }
  first_indice=1
else
  echo >&2 "Array not supported"
fi

E então você acessar os elementos de matriz com "${a[$first_indice+n]}", toda a lista com "${a[@]}"e usar as funções de mensagens publicitárias ( array_elements, set_array, set_array_element) para obter o número de elementos de um array (em $REPLY), definir a matriz como um todo ou atribuir um elementos individuais.

Provavelmente não vale o esforço. Eu usaria perlou limite para a matriz shell Bourne / POSIX: "$@".

Se a intenção é ter algum arquivo a ser obtido pelo shell interativo de um usuário para definir funções que usam internamente matrizes, aqui estão mais algumas notas que podem ser úteis.

Você pode configurar zshmatrizes para se kshparecerem com matrizes em escopos locais (em funções ou funções anônimas).

myfunction() {
  [ -z "$ZSH_VERSION" ] || setopt localoption ksharrays
  # use arrays of indice 0 in this function
}

Você também pode emular ksh(melhorar a compatibilidade com kshmatrizes e várias outras áreas) com:

myfunction() {
  [ -z "$ZSH_VERSION" ] || emulate -L ksh
  # ksh code more likely to work here
}

Com isso em mente e você está disposto a perder o suporte para yashe ksh88e versões mais antigas do pdkshderivados, e contanto que você não tente criar matrizes esparsas, você deve ser capaz de usar de forma consistente:

  • a[0]=foo
  • a=(foo bar)(mas não a=())
  • "${a[#]}", "${a[@]}","${a[0]}"

naquelas funções que possuem emulate -L ksh, enquanto o zshusuário ainda usa suas matrizes normalmente da maneira zsh.

Stéphane Chazelas
fonte
7

Você pode usar evalpara experimentar a sintaxe da matriz:

is_array_support() (
  eval 'a=(1)'
) >/dev/null 2>&1

if is_array_support; then
  echo support
else
  echo not
fi
cuonglm
fonte
2
ksh88suporta matrizes, mas não a=(). Em ksh93, a=()declara uma variável composta, não uma matriz, a menos que a variável tenha sido declarada anteriormente como uma matriz.
Stéphane Chazelas
2
Observe também que existem diferenças significativas entre implementações de matriz. Por exemplo, alguns têm índices de matriz começando em 0 (bash, ksh, zsh na emulação ksh), outros começando em um (zsh, yash). Algumas são matrizes / listas normais, outras são matrizes esparsas (matrizes associativas com chaves limitadas a números inteiros positivos, como em ksh ou bash).
Stéphane Chazelas
Em yash, você não faz a[5]=1masarray -s a 5 1
Stéphane Chazelas
@ StéphaneChazelas: obrigado pelas precisões. No meu caso, tudo se resume a saber se matrizes (associativas ou não) são suportadas. Detalhes sobre a base de índice podem ser facilmente trabalhados, mesmo em um script destinado a executar sem supervisão.
Cbhihe # 23/15
@ StéphaneChazelas: A variável ksh93composta me surpreendeu, você se importaria em me dar parte da documentação sobre isso? Eu adiciono 1à matriz para fazê-la funcionar.
cuonglm