Recorre una variable de shell separada por comas


109

Supongamos que tengo una variable de shell de Unix como se muestra a continuación

variable=abc,def,ghij

Quiero extraer todos los valores ( abc, defy ghij) usando un bucle for y pasar cada valor a un procedimiento.

El script debería permitir extraer un número arbitrario de valores separados por comas de $variable.


¿Qué es exactamente lo que quieres hacer repitiendo?
SMA

1
Quiero pasar cada uno de los campos (digamos abc) como argumento para un procedimiento.
Ramanathan K

Respuestas:


125

Puede usar la siguiente secuencia de comandos para recorrer dinámicamente su variable, sin importar cuántos campos tenga, siempre que solo esté separada por comas.

variable=abc,def,ghij
for i in $(echo $variable | sed "s/,/ /g")
do
    # call your procedure/other scripts here below
    echo "$i"
done

En lugar de la echo "$i"llamada anterior, entre doy donedentro del bucle for, puede invocar su procedimiento proc "$i".


Actualización : el fragmento de código anterior funciona si el valor de la variable no contiene espacios. Si tiene tal requisito, use una de las soluciones que pueden cambiar IFSy luego analice su variable.


Espero que esto ayude.


8
Esto no funciona si $variablecontiene espacios en blanco, por ejemplo, variable=a b,c,d=> imprime 4 líneas (a | b | c | d) en lugar de 3 (ab | c | d)
Dan

Además de agregar nuevas citas como ** "value **. ¿Podría actualizar si hay alguna expresión regular para eliminar eso?
gks

136

No jugar con IFS
No llamar al comando externo

variable=abc,def,ghij
for i in ${variable//,/ }
do
    # call your procedure/other scripts here below
    echo "$i"
done

Uso de manipulación de cadenas de bash http://www.tldp.org/LDP/abs/html/string-manipulation.html


3
Lo bueno de esto es que puede anidar múltiples bucles usando diferentes delimitadores. ¡Buena sugerencia!
Brad Parks

5
¡Buen consejo para evitar meterse IFS!
Laimoncijus

13
Pequeña advertencia: si alguno de los valores $icontiene espacios, se dividirán (y esa podría ser la razón por la que se pasa como separados por comas en lugar de separados por espacios)
Toby Speight

4
Si una función anterior cambió la configuración de IFS, entonces esto no funciona ... Cámbielo a esto:for i in ${variable//,/$IFS} do; echo "$i"; done
Joep

2
Recibo el mensaje de error "Sustitución incorrecta" cuando intento este enfoque.
Lost Crotchet

55

Si establece un separador de campo diferente, puede usar directamente un forbucle:

IFS=","
for v in $variable
do
   # things with "$v" ...
done

También puede almacenar los valores en una matriz y luego recorrerla como se indica en ¿Cómo divido una cadena en un delimitador en Bash? :

IFS=, read -ra values <<< "$variable"
for v in "${values[@]}"
do
   # things with "$v"
done

Prueba

$ variable="abc,def,ghij"
$ IFS=","
$ for v in $variable
> do
> echo "var is $v"
> done
var is abc
var is def
var is ghij

Puede encontrar un enfoque más amplio en esta solución sobre cómo recorrer una lista separada por comas y ejecutar un comando para cada entrada .

Ejemplos sobre el segundo enfoque:

$ IFS=, read -ra vals <<< "abc,def,ghij"
$ printf "%s\n" "${vals[@]}"
abc
def
ghij
$ for v in "${vals[@]}"; do echo "$v --"; done
abc --
def --
ghij --

También vea stackoverflow.com/questions/918886/… :-) Buena suerte a todos.
shellter

¡Sí! También pensé en eso, solo que luego se requerían los pasos: uno whilepara leer en una matriz y otro para recorrer los resultados.
fedorqui 'SO deja de dañar'

3
Me hace feliz cuando alguien realmente sabe cómo usar el shell, en lugar de juntar un montón de binutils.
PeterT

¿Tienes que IFSvolver a lo que era antes?
pstanton

1
@fedorqui ¡Así fue! Esa es la única variable que quería recorrer e hizo que mi archivo yaml fuera más fácil de leer (en lugar de una variable de entorno LONG, tiene un montón de filas)
GammaGames

5
#/bin/bash   
TESTSTR="abc,def,ghij"

for i in $(echo $TESTSTR | tr ',' '\n')
do
echo $i
done

Prefiero usar tr en lugar de sed, porque sed tiene problemas con caracteres especiales como \ r \ n en algunos casos.

otra solución es establecer IFS en cierto separador


1

Aquí hay una solución alternativa basada en tr que no usa eco, expresada como una línea.

for v in $(tr ',' '\n' <<< "$var") ; do something_with "$v" ; done

Se siente más ordenado sin eco, pero esa es solo mi preferencia personal.


0

Prueba este.

#/bin/bash   
testpid="abc,def,ghij" 
count=`echo $testpid | grep -o ',' | wc -l` # this is not a good way
count=`expr $count + 1` 
while [ $count -gt 0 ]  ; do
     echo $testpid | cut -d ',' -f $i
     count=`expr $count - 1 `
done

pero ¿qué pasa si no estoy seguro del número de campos en la variable testpid?
Ramanathan K

Además, necesito pasar cada archivo de la variable anterior como argumento para un procedimiento. Y quiero hacer esto hasta que la longitud de la variable sea cero. (es decir, todos los campos deben pasarse una vez al procedimiento de procesamiento)
Ramanathan K

No sé sobre el procedimiento. Desde este enlace puede obtener el recuento de los campos. http://stackoverflow.com/questions/8629330/unix-count-of-columns-in-file. Después de eso, conviértalo en bucle for en bucle while. Puedes conseguir ese.
Karthikeyan.RS

Supongo que esto es para contar los campos de un archivo. ¿Qué pasa si necesito contar los campos en una variable (supongamos que la variable es testpid = abc, def, ghij)
Ramanathan K

repita la variable y canalice la salida al awk. O bien, vea mi respuesta actualizada. Puede ser útil.
Karthikeyan.RS

0

Otra solución que no usa IFS y aún conserva los espacios:

$ var="a bc,def,ghij"
$ while read line; do echo line="$line"; done < <(echo "$var" | tr ',' '\n')
line=a bc
line=def
line=ghij

Esto funciona correctamente en bash, sin embargo zsh se traga los espacios.
lleaff

0

Aquí está mi solución bash pura que no cambia IFS y puede incluir un delimitador de expresiones regulares personalizado.

loop_custom_delimited() {
    local list=$1
    local delimiter=$2
    local item
    if [[ $delimiter != ' ' ]]; then
        list=$(echo $list | sed 's/ /'`echo -e "\010"`'/g' | sed -E "s/$delimiter/ /g")
    fi
    for item in $list; do
        item=$(echo $item | sed 's/'`echo -e "\010"`'/ /g')
        echo "$item"
    done
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.