Matrices asociativas en scripts de Shell

116

Necesitábamos un script que simulara matrices asociativas o una estructura de datos similar a un mapa para Shell Scripting, ¿cualquier cuerpo?

Irfan Zulfiqar
fuente

Respuestas:

20

Para agregar a la respuesta de Irfan , aquí hay una versión más corta y rápida de, get()ya que no requiere iteración sobre el contenido del mapa:

get() {
    mapName=$1; key=$2

    map=${!mapName}
    value="$(echo $map |sed -e "s/.*--${key}=\([^ ]*\).*/\1/" -e 's/:SP:/ /g' )"
}
Jerry Penner
fuente
16
bifurcar una subcapa y sed no es lo óptimo. Bash4 admite esto de forma nativa y bash3 tiene mejores alternativas.
lhunath
149

Otra opción, si la portabilidad no es su principal preocupación, es utilizar matrices asociativas integradas en el shell. Esto debería funcionar en bash 4.0 (disponible ahora en la mayoría de las distribuciones principales, aunque no en OS X a menos que lo instale usted mismo), ksh y zsh:

declare -A newmap
newmap[name]="Irfan Zulfiqar"
newmap[designation]=SSE
newmap[company]="My Own Company"

echo ${newmap[company]}
echo ${newmap[name]}

Dependiendo del caparazón, es posible que deba hacer un en typeset -A newmaplugar de declare -A newmap, o en algunos puede que no sea necesario en absoluto.

Brian Campbell
fuente
Gracias por publicar la respuesta, creo que esa sería la mejor manera de hacerlo para los chicos que usarían bash 4.0 o superior.
Irfan Zulfiqar
Agregaría un poco de kludge para asegurarme de que BASH_VERSION esté configurado, y> = 4. Y sí, ¡BASH 4 es realmente genial!
Tim Post
Estoy usando algo como esto. ¿Cuál es la mejor manera de "detectar" el error cuando no existe el índice / subíndice de la matriz? Por ejemplo, ¿qué pasaría si estuviera tomando el subíndice como una opción de línea de comando y el usuario cometió un error tipográfico e ingresó "designatio"? Recibo un error de "subíndice de matriz incorrecto", pero no sé cómo validar la entrada en el momento de la búsqueda de matriz, si es posible.
Jer
3
@Jer Es bastante oscuro, pero para determinar si una variable está configurada en el shell, puede usar test -z ${variable+x}( xno importa, podría ser cualquier cadena). Para una matriz asociativa en Bash, puede hacer algo similar; utilizar test -z ${map[key]+x}.
Brian Campbell
95

Otra forma de 4 que no es bash.

#!/bin/bash

# A pretend Python dictionary with bash 3 
ARRAY=( "cow:moo"
        "dinosaur:roar"
        "bird:chirp"
        "bash:rock" )

for animal in "${ARRAY[@]}" ; do
    KEY=${animal%%:*}
    VALUE=${animal#*:}
    printf "%s likes to %s.\n" "$KEY" "$VALUE"
done

echo -e "${ARRAY[1]%%:*} is an extinct animal which likes to ${ARRAY[1]#*:}\n"

También puede lanzar una declaración if para buscar allí. si [[$ var = ~ / blah /]]. o lo que sea.

Bubnoff
fuente
2
Este método es bueno cuando no tienes Bash 4. Pero creo que la línea que obtiene el VALOR sería más segura de esta manera: VALOR = $ {animal # *:}. Con solo un carácter #, la coincidencia se detendrá en el primer ":". Eso permite que los valores también contengan ":".
Ced-le-pingouin
@ Ced-le-pingouin ~ ¡Ese es un gran punto! No entendí eso. Edité mi publicación para reflejar las mejoras sugeridas.
Bubnoff
1
Es una emulación bastante pirateada de matrices asociativas que utiliza la sustitución de parámetros BASH. La "clave" param-sub sustitutos todo antes de los dos puntos y todo lo que el patrón de valores sustitutos después de los dos puntos. Similar a una coincidencia de comodines de expresiones regulares. Entonces NO es una verdadera matriz asociativa. No se recomienda a menos que necesite una forma fácil de entender para hacer una funcionalidad similar a una matriz asociativa / hash en BASH 3 o inferior. ¡Aunque funciona! Más aquí: tldp.org/LDP/abs/html/parameter-substitution.html#PSOREX2
Bubnoff
1
Esto no implementa una matriz asociativa porque no proporciona una forma de buscar un elemento por clave. Solo proporciona una forma de encontrar cada clave (y valor) a partir de un índice numérico. (Se puede encontrar un elemento por clave iterando a través de la matriz, pero eso no es lo que se desea para una matriz asociativa).
Eric Postpischil
@EricPostpischil Verdadero. Es solo un truco. Le permite a una persona usar una sintaxis familiar en la configuración, pero aún requiere iterar a través de la matriz como usted dice. Intenté dejar claro en mi comentario anterior que definitivamente no es una matriz asociativa y ni siquiera la recomiendo si tiene alternativas. El único punto a su favor, en mi opinión, es que es fácil de escribir y usar para aquellos familiarizados con otros lenguajes como Python. Si se encuentra en un punto en el que realmente desea implementar matrices asociativas en BASH 3, es posible que deba volver sobre sus pasos un poco.
Bubnoff
34

Creo que es necesario dar un paso atrás y pensar en lo que realmente es un mapa o matriz asociativa. Todo lo que es es una forma de almacenar un valor para una clave determinada y recuperar ese valor de forma rápida y eficiente. Es posible que también desee poder iterar sobre las claves para recuperar cada par de clave-valor, o eliminar claves y sus valores asociados.

Ahora, piense en una estructura de datos que utilice todo el tiempo en la creación de scripts de shell, e incluso solo en el shell sin escribir un script, que tenga estas propiedades. ¿Perplejo? Es el sistema de archivos.

Realmente, todo lo que necesita para tener una matriz asociativa en la programación de shell es un directorio temporal. mktemp -des su constructor de matriz asociativa:

prefix=$(basename -- "$0")
map=$(mktemp -dt ${prefix})
echo >${map}/key somevalue
value=$(cat ${map}/key)

Si no tiene ganas de usar echoy cat, siempre puede escribir algunos pequeños envoltorios; estos están modelados a partir de Irfan, aunque solo generan el valor en lugar de establecer variables arbitrarias como $value:

#!/bin/sh

prefix=$(basename -- "$0")
mapdir=$(mktemp -dt ${prefix})
trap 'rm -r ${mapdir}' EXIT

put() {
  [ "$#" != 3 ] && exit 1
  mapname=$1; key=$2; value=$3
  [ -d "${mapdir}/${mapname}" ] || mkdir "${mapdir}/${mapname}"
  echo $value >"${mapdir}/${mapname}/${key}"
}

get() {
  [ "$#" != 2 ] && exit 1
  mapname=$1; key=$2
  cat "${mapdir}/${mapname}/${key}"
}

put "newMap" "name" "Irfan Zulfiqar"
put "newMap" "designation" "SSE"
put "newMap" "company" "My Own Company"

value=$(get "newMap" "company")
echo $value

value=$(get "newMap" "name")
echo $value

editar : Este enfoque es en realidad bastante más rápido que la búsqueda lineal usando sed sugerido por el interrogador, así como más robusto (permite que las claves y valores contengan -, =, espacio, qnd ": SP:"). El hecho de que utilice el sistema de archivos no lo hace lento; en realidad, nunca se garantiza que estos archivos se escriban en el disco a menos que llame sync; para archivos temporales como este con una vida útil corta, no es improbable que muchos de ellos nunca se escriban en el disco.

Hice algunos puntos de referencia del código de Irfan, la modificación de Jerry del código de Irfan y mi código, usando el siguiente programa controlador:

#!/bin/sh

mapimpl=$1
numkeys=$2
numvals=$3

. ./${mapimpl}.sh    #/ <- fix broken stack overflow syntax highlighting

for (( i = 0 ; $i < $numkeys ; i += 1 ))
do
    for (( j = 0 ; $j < $numvals ; j += 1 ))
    do
        put "newMap" "key$i" "value$j"
        get "newMap" "key$i"
    done
done

Los resultados:

    $ time ./driver.sh irfan 10 5

    0m0.975s reales
    usuario 0m0.280s
    sys 0m0.691s

    $ time ./driver.sh brian 10 5

    0m0.226s reales
    usuario 0m0.057s
    sys 0m0.123s

    $ time ./driver.sh jerry 10 5

    0m0.706s reales
    usuario 0m0.228s
    sys 0m0.530s

    $ time ./driver.sh irfan 100 5

    real 0m10.633s
    usuario 0m4.366s
    sys 0m7.127s

    $ hora ./driver.sh brian 100 5

    0m1.682s reales
    usuario 0m0.546s
    sys 0m1.082s

    $ time ./driver.sh jerry 100 5

    real 0m9.315s
    usuario 0m4.565s
    sys 0m5.446s

    $ time ./driver.sh irfan 10500

    real 1m46.197s
    usuario 0m44.869s
    sys 1m12.282s

    $ time ./driver.sh brian 10500

    real 0m16.003s
    usuario 0m5.135s
    sys 0m10.396s

    $ time ./driver.sh jerry 10500

    real 1m24.414s
    usuario 0m39.696s
    sys 0m54.834s

    $ time ./driver.sh irfan 1000 5

    real 4m25.145s
    usuario 3m17.286s
    sys 1m21.490s

    $ hora ./driver.sh brian 1000 5

    real 0m19.442s
    usuario 0m5.287s
    sys 0m10.751s

    $ hora ./driver.sh jerry 1000 5

    reales 5m29.136s
    usuario 4m48.926s
    sys 0m59.336s

Brian Campbell
fuente
1
No creo que debas usar un sistema de archivos para mapas, que básicamente usa IO para algo que puedes hacer bastante rápido en la memoria.
Irfan Zulfiqar
9
Los archivos nunca se escribirán necesariamente en el disco; a menos que llame a la sincronización, el sistema operativo puede dejarlos en la memoria. Su código está llamando a sed y haciendo varias búsquedas lineales, todas muy lentas. Hice algunos puntos de referencia rápidos y mi versión es 5-35 veces más rápida.
Brian Campbell
por otro lado, las matrices nativas de bash4 son un enfoque significativamente mejor y en bash3 aún puede mantener todo fuera del disco sin bifurcar mediante el uso de declare e indirección.
lhunath
7
"rápido" y "shell" realmente no van juntos de todos modos: ciertamente no para el tipo de problemas de velocidad de los que estamos hablando en el nivel de "evitar IO minúsculas". Puede buscar y usar / dev / shm para garantizar que no haya E / S.
jmtd
2
Esta solución me asombró y es simplemente increíble. Todavía es cierto en 2016. Realmente debería ser la respuesta aceptada.
Gordon
7

Bash4 admite esto de forma nativa. No uses grepo eval, son los trucos más feos.

Para obtener una respuesta detallada y detallada con código de ejemplo, consulte: /programming/3467959

lhunath
fuente
7
####################################################################
# Bash v3 does not support associative arrays
# and we cannot use ksh since all generic scripts are on bash
# Usage: map_put map_name key value
#
function map_put
{
    alias "${1}$2"="$3"
}

# map_get map_name key
# @return value
#
function map_get
{
    alias "${1}$2" | awk -F"'" '{ print $2; }'
}

# map_keys map_name 
# @return map keys
#
function map_keys
{
    alias -p | grep $1 | cut -d'=' -f1 | awk -F"$1" '{print $2; }'
}

Ejemplo:

mapName=$(basename $0)_map_
map_put $mapName "name" "Irfan Zulfiqar"
map_put $mapName "designation" "SSE"

for key in $(map_keys $mapName)
do
    echo "$key = $(map_get $mapName $key)
done
Vadim
fuente
4

Ahora respondiendo a esta pregunta.

Los siguientes scripts simulan matrices asociativas en scripts de shell. Es simple y muy fácil de entender.

El mapa no es más que una cadena interminable que tiene keyValuePair guardado como --name = Irfan --designation = SSE --company = My: SP: Own: SP: Company

los espacios se reemplazan con ': SP:' para los valores

put() {
    if [ "$#" != 3 ]; then exit 1; fi
    mapName=$1; key=$2; value=`echo $3 | sed -e "s/ /:SP:/g"`
    eval map="\"\$$mapName\""
    map="`echo "$map" | sed -e "s/--$key=[^ ]*//g"` --$key=$value"
    eval $mapName="\"$map\""
}

get() {
    mapName=$1; key=$2; valueFound="false"

    eval map=\$$mapName

    for keyValuePair in ${map};
    do
        case "$keyValuePair" in
            --$key=*) value=`echo "$keyValuePair" | sed -e 's/^[^=]*=//'`
                      valueFound="true"
        esac
        if [ "$valueFound" == "true" ]; then break; fi
    done
    value=`echo $value | sed -e "s/:SP:/ /g"`
}

put "newMap" "name" "Irfan Zulfiqar"
put "newMap" "designation" "SSE"
put "newMap" "company" "My Own Company"

get "newMap" "company"
echo $value

get "newMap" "name"
echo $value

editar: Acabo de agregar otro método para recuperar todas las claves.

getKeySet() {
    if [ "$#" != 1 ]; 
    then 
        exit 1; 
    fi

    mapName=$1; 

    eval map="\"\$$mapName\""

    keySet=`
           echo $map | 
           sed -e "s/=[^ ]*//g" -e "s/\([ ]*\)--/\1/g"
          `
}
Irfan Zulfiqar
fuente
1
Estás evalobteniendo datos como si fueran un código bash, y lo que es más: no los citas correctamente. Ambos causan una gran cantidad de errores e inyección de código arbitrario.
lhunath
3

Para Bash 3, hay un caso particular que tiene una solución agradable y simple:

Si no desea manejar muchas variables, o las claves son simplemente identificadores de variables no válidos, y se garantiza que su matriz tiene menos de 256 elementos , puede abusar de los valores de retorno de la función. Esta solución no requiere ninguna subcapa ya que el valor está disponible como una variable, ni ninguna iteración para que el rendimiento grite. También es muy legible, casi como la versión Bash 4.

Aquí está la versión más básica:

hash_index() {
    case $1 in
        'foo') return 0;;
        'bar') return 1;;
        'baz') return 2;;
    esac
}

hash_vals=("foo_val"
           "bar_val"
           "baz_val");

hash_index "foo"
echo ${hash_vals[$?]}

Recuerde, use comillas simples case, de lo contrario, está sujeto a globbing. Realmente útil para hashes estáticos / congelados desde el principio, pero se podría escribir un generador de índices a partir de una hash_keys=()matriz.

Tenga cuidado, el valor predeterminado es el primero, por lo que es posible que desee dejar de lado el elemento cero:

hash_index() {
    case $1 in
        'foo') return 1;;
        'bar') return 2;;
        'baz') return 3;;
    esac
}

hash_vals=("",           # sort of like returning null/nil for a non existent key
           "foo_val"
           "bar_val"
           "baz_val");

hash_index "foo" || echo ${hash_vals[$?]}  # It can't get more readable than this

Advertencia: la longitud ahora es incorrecta.

Alternativamente, si desea mantener la indexación basada en cero, puede reservar otro valor de índice y protegerse contra una clave inexistente, pero es menos legible:

hash_index() {
    case $1 in
        'foo') return 0;;
        'bar') return 1;;
        'baz') return 2;;
        *)   return 255;;
    esac
}

hash_vals=("foo_val"
           "bar_val"
           "baz_val");

hash_index "foo"
[[ $? -ne 255 ]] && echo ${hash_vals[$?]}

O, para mantener la longitud correcta, desplace el índice en uno:

hash_index() {
    case $1 in
        'foo') return 1;;
        'bar') return 2;;
        'baz') return 3;;
    esac
}

hash_vals=("foo_val"
           "bar_val"
           "baz_val");

hash_index "foo" || echo ${hash_vals[$(($? - 1))]}
Lloeki
fuente
2

Puede utilizar nombres de variables dinámicas y dejar que los nombres de las variables funcionen como las claves de un mapa hash.

Por ejemplo, si tiene un archivo de entrada con dos columnas, nombre, crédito, como el ejemplo a continuación, y desea sumar los ingresos de cada usuario:

Mary 100
John 200
Mary 50
John 300
Paul 100
Paul 400
David 100

El siguiente comando sumará todo, usando variables dinámicas como claves, en forma de mapa _ $ {persona} :

while read -r person money; ((map_$person+=$money)); done < <(cat INCOME_REPORT.log)

Para leer los resultados:

set | grep map

La salida será:

map_David=100
map_John=500
map_Mary=150
map_Paul=500

Desarrollando estas técnicas, estoy desarrollando en GitHub una función que funciona como un objeto HashMap , shell_map .

Para crear " instancias HashMap ", la función shell_map puede crear copias de sí misma con diferentes nombres. Cada nueva copia de función tendrá una variable $ FUNCNAME diferente. $ FUNCNAME luego se usa para crear un espacio de nombres para cada instancia de Map.

Las claves del mapa son variables globales, en la forma $ FUNCNAME_DATA_ $ KEY, donde $ KEY es la clave agregada al mapa. Estas variables son variables dinámicas .

A continuación, pondré una versión simplificada para que pueda usarla como ejemplo.

#!/bin/bash

shell_map () {
    local METHOD="$1"

    case $METHOD in
    new)
        local NEW_MAP="$2"

        # loads shell_map function declaration
        test -n "$(declare -f shell_map)" || return

        # declares in the Global Scope a copy of shell_map, under a new name.
        eval "${_/shell_map/$2}"
    ;;
    put)
        local KEY="$2"  
        local VALUE="$3"

        # declares a variable in the global scope
        eval ${FUNCNAME}_DATA_${KEY}='$VALUE'
    ;;
    get)
        local KEY="$2"
        local VALUE="${FUNCNAME}_DATA_${KEY}"
        echo "${!VALUE}"
    ;;
    keys)
        declare | grep -Po "(?<=${FUNCNAME}_DATA_)\w+((?=\=))"
    ;;
    name)
        echo $FUNCNAME
    ;;
    contains_key)
        local KEY="$2"
        compgen -v ${FUNCNAME}_DATA_${KEY} > /dev/null && return 0 || return 1
    ;;
    clear_all)
        while read var; do  
            unset $var
        done < <(compgen -v ${FUNCNAME}_DATA_)
    ;;
    remove)
        local KEY="$2"
        unset ${FUNCNAME}_DATA_${KEY}
    ;;
    size)
        compgen -v ${FUNCNAME}_DATA_${KEY} | wc -l
    ;;
    *)
        echo "unsupported operation '$1'."
        return 1
    ;;
    esac
}

Uso:

shell_map new credit
credit put Mary 100
credit put John 200
for customer in `credit keys`; do 
    value=`credit get $customer`       
    echo "customer $customer has $value"
done
credit contains_key "Mary" && echo "Mary has credit!"
Bruno Negrão Zica
fuente
2

Otra forma más que no es bash-4 (es decir, bash 3, compatible con Mac):

val_of_key() {
    case $1 in
        'A1') echo 'aaa';;
        'B2') echo 'bbb';;
        'C3') echo 'ccc';;
        *) echo 'zzz';;
    esac
}

for x in 'A1' 'B2' 'C3' 'D4'; do
    y=$(val_of_key "$x")
    echo "$x => $y"
done

Huellas dactilares:

A1 => aaa
B2 => bbb
C3 => ccc
D4 => zzz

La función con caseactúa como una matriz asociativa. Desafortunadamente, no se puede usar return, por lo que tiene echosu salida, pero esto no es un problema, a menos que sea un purista que evite bifurcar subcapas.

Walter Tross
fuente
1

Qué lástima que no vi la pregunta antes: escribí el marco de shell de la biblioteca que contiene, entre otros, los mapas (matrices asociativas). La última versión se puede encontrar aquí .

Ejemplo:

#!/bin/bash 
#include map library
shF_PATH_TO_LIB="/usr/lib/shell-framework"
source "${shF_PATH_TO_LIB}/map"

#simple example get/put
putMapValue "mapName" "mapKey1" "map Value 2"
echo "mapName[mapKey1]: $(getMapValue "mapName" "mapKey1")"

#redefine old value to new
putMapValue "mapName" "mapKey1" "map Value 1"
echo "after change mapName[mapKey1]: $(getMapValue "mapName" "mapKey1")"

#add two new pairs key/values and print all keys
putMapValue "mapName" "mapKey2" "map Value 2"
putMapValue "mapName" "mapKey3" "map Value 3"
echo -e "mapName keys are \n$(getMapKeys "mapName")"

#create new map
putMapValue "subMapName" "subMapKey1" "sub map Value 1"
putMapValue "subMapName" "subMapKey2" "sub map Value 2"

#and put it in mapName under key "mapKey4"
putMapValue "mapName" "mapKey4" "subMapName"

#check if under two key were placed maps
echo "is map mapName[mapKey3]? - $(if isMap "$(getMapValue "mapName" "mapKey3")" ; then echo Yes; else echo No; fi)"
echo "is map mapName[mapKey4]? - $(if isMap "$(getMapValue "mapName" "mapKey4")" ; then echo Yes; else echo No; fi)"

#print map with sub maps
printf "%s\n" "$(mapToString "mapName")"
Beggy
fuente
1

Añadiendo otra opción, si jq está disponible:

export NAMES="{
  \"Mary\":\"100\",
  \"John\":\"200\",
  \"Mary\":\"50\",
  \"John\":\"300\",
  \"Paul\":\"100\",
  \"Paul\":\"400\",
  \"David\":\"100\"
}"
export NAME=David
echo $NAMES | jq --arg v "$NAME" '.[$v]' | tr -d '"' 
critium
fuente
0

Descubrí que es cierto, como ya se mencionó, que el método de mejor rendimiento es escribir key / vals en un archivo y luego usar grep / awk para recuperarlos. Suena como todo tipo de E / S innecesarias, pero el caché de disco entra en acción y lo hace extremadamente eficiente, mucho más rápido que intentar almacenarlos en la memoria usando uno de los métodos anteriores (como muestran los puntos de referencia).

Aquí hay un método rápido y limpio que me gusta:

hinit() {
    rm -f /tmp/hashmap.$1
}

hput() {
    echo "$2 $3" >> /tmp/hashmap.$1
}

hget() {
    grep "^$2 " /tmp/hashmap.$1 | awk '{ print $2 };'
}

hinit capitols
hput capitols France Paris
hput capitols Netherlands Amsterdam
hput capitols Spain Madrid

echo `hget capitols France` and `hget capitols Netherlands` and `hget capitols Spain`

Si desea aplicar un valor único por clave, también puede hacer una pequeña acción grep / sed en hput ().

Al P.
fuente
0

Hace varios años escribí una biblioteca de scripts para bash que admitía matrices asociativas entre otras características (registro, archivos de configuración, soporte extendido para argumentos de línea de comando, generar ayuda, pruebas unitarias, etc.). La biblioteca contiene un contenedor para matrices asociativas y cambia automáticamente al modelo apropiado (interno para bash4 y emulado para versiones anteriores). Se llamaba shell-framework y estaba alojado en origo.ethz.ch, pero hoy el recurso está cerrado. Si alguien aún lo necesita, puedo compartirlo contigo.

Beggy
fuente
Podría valer la pena pegarlo en github
Mark K Cowan
0

Shell no tiene un mapa integrado como estructura de datos, yo uso una cadena sin procesar para describir elementos como ese:

ARRAY=(
    "item_A|attr1|attr2|attr3"
    "item_B|attr1|attr2|attr3"
    "..."
)

cuando se extraen elementos y sus atributos:

for item in "${ARRAY[@]}"
do
    item_name=$(echo "${item}"|awk -F "|" '{print $1}')
    item_attr1=$(echo "${item}"|awk -F "|" '{print $2}')
    item_attr2=$(echo "${item}"|awk -F "|" '{print $3}')

    echo "${item_name}"
    echo "${item_attr1}"
    echo "${item_attr2}"
done

Esto no parece más inteligente que la respuesta de otras personas, pero es fácil de entender para que la gente nueva lo pueda burlar.

coanor
fuente
-1

Modifiqué la solución de Vadim con lo siguiente:

####################################################################
# Bash v3 does not support associative arrays
# and we cannot use ksh since all generic scripts are on bash
# Usage: map_put map_name key value
#
function map_put
{
    alias "${1}$2"="$3"
}

# map_get map_name key
# @return value
#
function map_get {
    if type -p "${1}$2"
        then
            alias "${1}$2" | awk -F "'" '{ print $2; }';
    fi
}

# map_keys map_name 
# @return map keys
#
function map_keys
{
    alias -p | grep $1 | cut -d'=' -f1 | awk -F"$1" '{print $2; }'
}

El cambio es map_get para evitar que devuelva errores si solicita una clave que no existe, aunque el efecto secundario es que también ignorará silenciosamente los mapas faltantes, pero se adapta mejor a mi caso de uso ya que acabo de quería buscar una clave para omitir elementos en un bucle.

Haravikk
fuente
-1

Respuesta tardía, pero considere abordar el problema de esta manera, utilizando la lectura incorporada de bash como se ilustra en el fragmento de código de un script de firewall ufw que sigue. Este enfoque tiene la ventaja de utilizar tantos conjuntos de campos delimitados (no solo 2) como se desee. Hemos utilizado el | delimitador porque los especificadores de rango de puertos pueden requerir dos puntos, es decir, 6001: 6010 .

#!/usr/bin/env bash

readonly connections=(       
                            '192.168.1.4/24|tcp|22'
                            '192.168.1.4/24|tcp|53'
                            '192.168.1.4/24|tcp|80'
                            '192.168.1.4/24|tcp|139'
                            '192.168.1.4/24|tcp|443'
                            '192.168.1.4/24|tcp|445'
                            '192.168.1.4/24|tcp|631'
                            '192.168.1.4/24|tcp|5901'
                            '192.168.1.4/24|tcp|6566'
)

function set_connections(){
    local range proto port
    for fields in ${connections[@]}
    do
            IFS=$'|' read -r range proto port <<< "$fields"
            ufw allow from "$range" proto "$proto" to any port "$port"
    done
}

set_connections
AsymLabs
fuente