Prueba de soporte de matriz por shell

11

¿Hay una manera concisa de probar el soporte de matriz por el shell local similar a Bourne en la línea de comando?

Esto siempre es posible:

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

o prueba $SHELLy versión de shell:

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

y luego leyendo la página del manual, suponiendo que tenga acceso a ella. (Incluso allí, escribiendo desde /bin/bash, estoy asumiendo que todos los proyectiles tipo Bourne admiten la opción larga --version, cuando eso se rompe para ksh, por ejemplo ).

Estoy buscando una prueba simple que pueda ser desatendida e incorporada en una sección de Uso al comienzo del script o incluso antes de llamarla.

Cbhihe
fuente
¿Supongo que quiere limitarse a las conchas tipo Bourne?
Stéphane Chazelas
@ StéphaneChazelas: Sí, si te refieres (no exhaustivamente) al grupo principal compuesto por: sh, csh, ksh, tcsh, bash, zsh y amigos cercanos. No sé dónde se posiciona Yash en esta constelación.
Cbhihe
2
cshNo es una cáscara de bourne. tcshno es uno cualquiera (que es cshcon algunos errores corregidos)
cas
1
Tenga en cuenta que $SHELLes el shell preferido del usuario, como $EDITORes su editor de texto preferido. Tiene poco que ver con el shell actualmente en ejecución.
Stéphane Chazelas
1
evalIndicar la salida de $SHELL --versioncódigo shell no tiene sentido.
Stéphane Chazelas

Respuestas:

12

Suponiendo que usted quiere restringir a Bourne como conchas (muchas otras cáscaras como csh, tcsh, rc, eso fishmatrices de apoyo, pero escribir un guión compatible al mismo tiempo a Bourne como conchas y los que es complicado y generalmente tiene sentido, ya que son intérpretes para completamente diferente y idiomas incompatibles), tenga en cuenta que existen diferencias significativas entre las implementaciones.

Los shells tipo Bourne que admiten matrices son:

  • ksh88(esa es la primera implementación de matrices, ksh88 todavía se encuentra como kshen la mayoría de las unidades comerciales tradicionales donde también es la base sh)

    • las matrices son unidimensionales
    • Las matrices se definen como set -A array foo baro set -A array -- "$var" ...si no puede garantizar que $varno comenzará con una -o +.
    • Los índices de matriz comienzan en 0.
    • Los elementos individuales de la matriz se asignan como a[1]=value.
    • Las matrices son escasas. Eso a[5]=foofuncionará incluso si a[0,1,2,3,4]no están configurados y los dejará sin configurar.
    • ${a[5]}para acceder al elemento del índice 5 (no necesariamente el sexto elemento si la matriz es escasa). El 5puede haber cualquier expresión aritmética.
    • El tamaño de la matriz y el subíndice están limitados (a 4096).
    • ${#a[@]} es el número de elemento asignado en la matriz (no el mayor índice asignado).
    • no hay forma de conocer la lista de subíndices asignados (aparte de probar los elementos 4096 individualmente con [[ -n "${a[i]+set}" ]]).
    • $aes el mismo que ${a[0]}. Es decir, las matrices de alguna manera extienden las variables escalares dándoles valores adicionales.
  • pdkshy derivados (esa es la base para, kshy algunas veces, shde varios BSD y fue la única implementación de código abierto de ksh antes de que se liberara la fuente de ksh93):

    Principalmente como ksh88pero tenga en cuenta:

    • Algunas implementaciones antiguas no eran compatibles set -A array -- foo bar( --no era necesario allí).
    • ${#a[@]}es uno más el índice del mayor índice asignado. ( a[1000]=1; echo "${#a[@]}"salidas 1001 a pesar de que la matriz tiene solo un elemento.
    • en versiones más nuevas, el tamaño de la matriz ya no está limitado (excepto por el tamaño de los enteros).
    • Las versiones recientes de mkshtienen unos pocos operadores adicionales inspirados bash, ksh93o zshcomo asignaciones a la a=(x y), a+=(z), ${!a[@]}para obtener la lista de índices asignados.
  • zsh. zshmatrices están generalmente mejor diseñados y toman lo mejor de kshy cshmatrices. Son similares kshpero con diferencias significativas:

    • los índices comienzan en 1, no en 0 (excepto en la kshemulación), eso es consistente con la matriz Bourne (los parámetros de posición $ @, que zshtambién se exponen como su matriz $ argv) y las cshmatrices.
    • son un tipo separado de las variables normales / escalares. Los operadores se aplican de manera diferente a ellos y como generalmente esperarías. $ano es lo mismo ${a[0]}pero se expande a los elementos no vacíos de la matriz ( "${a[@]}"para todos los elementos como en ksh).
    • son matrices normales, no matrices dispersas. a[5]=1funciona pero asigna todos los elementos del 1 al 4 a la cadena vacía si no se asignaron. Entonces ${#a[@]}(igual ${#a}que en ksh es el tamaño del elemento del índice 0) es el número de elementos en la matriz y el índice asignado más grande.
    • matrices asociativas son compatibles.
    • Se admite una gran cantidad de operadores para trabajar con matrices, demasiado grande para enumerar aquí.
    • matrices definidas como a=(x y). set -A a x ytambién funciona, pero set -A a -- x yno es compatible a menos que en la emulación ksh ( --no se necesita en la emulación zsh).
  • ksh93. (Aquí se describen las últimas versiones). ksh93, considerado experimental durante mucho tiempo, ahora se puede encontrar en más y más sistemas ahora que se ha lanzado como FOSS. Por ejemplo, es el /bin/sh(donde reemplazó el shell Bourne /usr/xpg4/bin/sh, el shell POSIX todavía se basa en ksh88) y kshde Solaris 11. Sus matrices amplían y mejoran los ksh88.

    • a=(x y)se puede usar para definir una matriz, pero como a=(...)también se usa para definir variables compuestas ( a=(foo=bar bar=baz)), a=()es ambigua y declara una variable compuesta, no una matriz.
    • Las matrices son multidimensionales ( a=((0 1) (0 2))) y los elementos de la matriz también pueden ser variables compuestas ( a=((a b) (c=d d=f)); echo "${a[1].c}").
    • Se a=([2]=foo [5]=bar)puede usar una sintaxis para definir matrices dispersas a la vez.
    • Limitaciones de tamaño levantadas.
    • No en la medida de zsh, pero gran cantidad de operadores soportados también para manipular matrices.
    • "${!a[@]}" para recuperar la lista de índices de matriz.
    • Las matrices asociativas también se admiten como un tipo separado.
  • bash. bashes la cáscara del proyecto GNU. Se usa como shen versiones recientes de OS / X y algunas distribuciones de GNU / Linux. bashLas matrices emulan principalmente ksh88las que tienen algunas características de ksh93y zsh.

    • a=(x y)soportado. set -A a x y No es compatible. a=()crea una matriz vacía (sin variables compuestas bash).
    • "${!a[@]}" para la lista de índices.
    • a=([foo]=bar)sintaxis compatible, así como algunos otros de ksh93y zsh.
    • Las bashversiones recientes también admiten matrices asociativas como un tipo separado.
  • yash. Es una implementación POSIX sh relativamente reciente, limpia y con reconocimiento de varios bytes. No en uso amplio. Sus matrices son otra API limpia similar azsh

    • las matrices no son escasas
    • Los índices de matriz comienzan en 1
    • definido (y declarado) con a=(var value)
    • elementos insertados, eliminados o modificados con el arrayincorporado
    • array -s a 5 valuepara modificar el 5 º elemento fallaría si ese elemento no fue asignado de antemano.
    • el número de elementos en la matriz es ${a[#]}, ${#a[@]}siendo el tamaño de los elementos como una lista.
    • Las matrices son un tipo separado. Debe a=("$a")redefinir una variable escalar como una matriz antes de poder agregar o modificar elementos.
    • Las matrices no son compatibles cuando se invoca como sh.

Entonces, a partir de eso, puede ver que la detección de soporte de matriz, que podría hacer con:

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

no es suficiente para poder usar esas matrices. Debería definir comandos de envoltura para asignar matrices como elementos completos e individuales, y asegurarse de no intentar crear matrices dispersas.

Me gusta

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

Y luego se accede a elementos de la matriz con "${a[$first_indice+n]}", toda la lista con "${a[@]}"y utilizar las funciones de contenedor ( array_elements, set_array, set_array_element) para obtener el número de elementos de un array (en $REPLY), establecer la matriz como enteros o asignar un elementos individuales.

Probablemente no valga la pena el esfuerzo. Que haría uso perlo límite a la matriz shell Bourne / POSIX: "$@".

Si la intención es que el shell interactivo de un usuario obtenga algún archivo para definir funciones que utilizan arrays internamente, aquí hay algunas notas más que pueden ser útiles.

Puede configurar las zshmatrices para que se parezcan más a las kshmatrices en ámbitos locales (en funciones o funciones anónimas).

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

También puede emular ksh(mejorar la compatibilidad con kshmatrices y varias otras áreas) con:

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

Con esto en mente y que está dispuesto a soltar para yashy ksh88y versiones anteriores de pdkshderivados, y siempre y cuando no se intenta crear matrices dispersas, que deben ser capaces de utilizar de manera uniforme:

  • a[0]=foo
  • a=(foo bar)(pero no a=())
  • "${a[#]}"` "${a[@]}"`"${a[0]}"

en aquellas funciones que tienen emulate -L ksh, mientras el zshusuario todavía usa sus matrices normalmente de la manera zsh.

Stéphane Chazelas
fuente
7

Puede usar evalpara probar la sintaxis de la matriz:

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

if is_array_support; then
  echo support
else
  echo not
fi
Cuonglm
fuente
2
ksh88admite matrices pero no a=(). En ksh93, a=()declara una variable compuesta, no una matriz, a menos que la variable se haya declarado previamente como una matriz.
Stéphane Chazelas
2
También tenga en cuenta que hay diferencias significativas entre las implementaciones de matriz. Por ejemplo, algunos tienen índices de matriz que comienzan en 0 (bash, ksh, zsh en la emulación ksh), algunos comienzan en uno (zsh, yash). Algunas son matrices / listas normales, algunas son matrices dispersas (matrices asociativas con claves limitadas a enteros positivos como en ksh o bash).
Stéphane Chazelas
En yash, no lo haces a[5]=1peroarray -s a 5 1
Stéphane Chazelas
@ StéphaneChazelas: gracias por las precisiones. En mi caso, todo se reduce a si las matrices (asociativas o no) son compatibles. Los detalles sobre index-base se pueden resolver fácilmente incluso en un script destinado a ejecutarse sin supervisión.
Cbhihe
@ StéphaneChazelas: La variable ksh93compuesta me sorprendió, ¿te importaría darme parte de la documentación al respecto? Añado 1a la matriz para que funcione.
Cuonglm