Unir elementos de una matriz

Si tengo una matriz como esta en Bash:

FOO=( abc ) 

¿Cómo unir los elementos con comas? Por ejemplo, producir a,b,c .

Reescribiendo la solución de Pascal Pilz como una función en Bash 100% puro (sin comandos externos):

 function join_by { local IFS="$1"; shift; echo "$*"; } 

Por ejemplo,

 join_by , a "bc" d #a,bc,d join_by / var local tmp #var/local/tmp join_by , "${FOO[@]}" #a,b,c 

Alternativamente, podemos usar printf para admitir delimitadores de múltiples caracteres, usando la idea de @gniourf_gniourf

 function join_by { local d=$1; shift; echo -n "$1"; shift; printf "%s" "${@/#/$d}"; } 

Por ejemplo,

 join_by , abc #a,b,c join_by ' , ' abc #a , b , c join_by ')|(' abc #a)|(b)|(c join_by ' %s ' abc #a %sb %sc join_by $'\n' abc #abc join_by - abc #abc join_by '\' abc #a\b\c 

Otra solución más:

 #!/bin/bash foo=('foo bar' 'foo baz' 'bar baz') bar=$(printf ",%s" "${foo[@]}") bar=${bar:1} echo $bar 

Editar: mismo pero para el separador de longitud variable de múltiples caracteres:

 #!/bin/bash separator=")|(" # eg constructing regex, pray it does not contain %s foo=('foo bar' 'foo baz' 'bar baz') regex="$( printf "${separator}%s" "${foo[@]}" )" regex="${regex:${#separator}}" # remove leading separator echo "${regex}" # Prints: foo bar)|(foo baz)|(bar baz 
 $ foo=(a "bc" d) $ bar=$(IFS=, ; echo "${foo[*]}") $ echo "$bar" a,bc,d 

Tal vez, por ejemplo,

 SAVE_IFS="$IFS" IFS="," FOOJOIN="${FOO[*]}" IFS="$SAVE_IFS" echo "$FOOJOIN" 

Sorprendentemente, mi solución aún no se ha dado 🙂 Esta es la forma más sencilla para mí. No necesita una función:

 IFS=, eval 'joined="${foo[*]}"' 

Nota: Se observó que esta solución funcionaba bien en modo no POSIX. En el modo POSIX , los elementos todavía se unen correctamente, pero IFS=, vuelve permanente.

Aquí hay una función Bash 100% pura que hace el trabajo:

 join() { # $1 is return variable name # $2 is sep # $3... are the elements to join local retname=$1 sep=$2 ret=$3 shift 3 || shift $(($#)) printf -v "$retname" "%s" "$ret${@/#/$sep}" } 

Mira:

 $ a=( one two "three three" four five ) $ join joineda " and " "${a[@]}" $ echo "$joineda" one and two and three three and four and five $ join joinedb randomsep "only one element" $ echo "$joinedb" only one element $ join joinedc randomsep $ echo "$joinedc" $ a=( $' stuff with\nnewlines\n' $'and trailing newlines\n\n' ) $ join joineda $'a sep with\nnewlines\n' "${a[@]}" $ echo "$joineda" stuff with newlines a sep with newlines and trailing newlines $ 

Esto conserva incluso las nuevas líneas finales, y no necesita una subshell para obtener el resultado de la función. Si no le gusta el printf -v (¿por qué no le gustaría?) Y le pasa un nombre de variable, puede usar una variable global para la cadena devuelta:

 join() { # $1 is sep # $2... are the elements to join # return is in global variable join_ret local sep=$1 IFS= join_ret=$2 shift 2 || shift $(($#)) join_ret+="${*/#/$sep}" } 

Con la reutilización de @ no importa la solución, pero con una statement al evitar la substición $ {: 1} y la necesidad de una variable intermedia.

 echo $(printf "%s," "${LIST[@]}" | cut -d "," -f 1-${#LIST[@]} ) 

printf tiene ‘La cadena de formato se reutiliza tantas veces como sea necesario para satisfacer los argumentos’. en sus páginas man, para que las concatenaciones de las cadenas estén documentadas. Entonces, el truco es usar la longitud LIST para cortar el último sperator, ya que cut retendrá solo la longitud de LIST, ya que los campos cuentan.

Me gustaría hacer eco de la matriz como una cadena, luego transformar los espacios en fonts de línea, y luego usar paste para unir todo en una línea así:

tr " " "\n" < << "$FOO" | paste -sd , -

Resultados:

a,b,c

¡Esto parece ser lo más rápido y limpio para mí!

 s=$(IFS=, eval 'echo "${FOO[*]}"') 

solución printf que acepta separadores de cualquier longitud (basado en @ no importa respuesta)

 #/!bin/bash foo=('foo bar' 'foo baz' 'bar baz') sep=',' # can be of any length bar=$(printf "${sep}%s" "${foo[@]}") bar=${bar:${#sep}} echo $bar 
 $ set a 'bc' d $ history -p "$@" | paste -sd, a,bc,d 

Combina lo mejor de todos los mundos hasta ahora con la siguiente idea.

 # join with separator join_ws() { local IFS=; local s="${*/#/$1}"; echo "${s#"$1$1$1"}"; } 

Esta pequeña obra maestra es

  • Bash 100% puro (expansión de parámetros con IFS temporalmente desarmado, sin llamadas externas, sin printf …)
  • compacto, completo y sin defectos (funciona con limitadores de uno y varios caracteres, funciona con limitadores que contienen espacios en blanco, saltos de línea y otros caracteres especiales de la carcasa, funciona con delimitador vacío)
  • eficiente (sin subshell, sin copia de matriz)
  • simple y estúpido y, hasta cierto punto, hermoso e instructivo también

Ejemplos:

 $ join_ws , abc a,b,c $ join_ws '' abc abc $ join_ws $'\n' abc a b c $ join_ws ' \/ ' ABC A \/ B \/ C 

Sin usar comandos externos:

 $ FOO=( abc ) # initialize the array $ BAR=${FOO[@]} # create a space delimited string from array $ BAZ=${BAR// /,} # use parameter expansion to substitute spaces with comma $ echo $BAZ a,b,c 

Advertencia, asume que los elementos no tienen espacios en blanco.

Versión más corta de la respuesta principal:

 joinStrings() { local a=("${@:3}"); printf "%s" "$2${a[@]/#/$1}"; } 

Uso:

 joinStrings "$myDelimiter" "${myArray[@]}" 

En este momento estoy usando:

 TO_IGNORE=( E201 # Whitespace after '(' E301 # Expected N blank lines, found M E303 # Too many blank lines (pep8 gets confused by comments) ) ARGS="--ignore `echo ${TO_IGNORE[@]} | tr ' ' ','`" 

Lo cual funciona, pero (en el caso general) se romperá horriblemente si los elementos de la matriz tienen un espacio en ellos.

(Para aquellos interesados, este es un script de envoltura alrededor de pep8.py )

Mi bash.

 $ array=(one two "three four" five) $ echo "${array[0]}$(printf " SEP %s" "${array[@]:1}")" one SEP two SEP three four SEP five 

Use perl para separadores de múltiples caracteres:

 function join { perl -e '$s = shift @ARGV; print join($s, @ARGV);' "$@"; } join ', ' abc # a, b, c 

O en una línea:

 perl -le 'print join(shift, @ARGV);' ', ' 1 2 3 1, 2, 3 

Usar la indirección variable para referirse directamente a una matriz también funciona. Las referencias nombradas también se pueden usar, pero solo estuvieron disponibles en 4.3.

La ventaja de utilizar esta forma de una función es que puede tener el separador opcional (por defecto es el primer carácter de IFS predeterminado, que es un espacio, quizás convertirlo en una cadena vacía si lo desea), y evita expandir los valores dos veces ( primero cuando se pasan como parámetros, y segundo como "$@" dentro de la función).

Esta solución tampoco requiere que el usuario llame a la función dentro de una sustitución de comando, que invoca una subshell, para obtener una versión unida de una cadena asignada a otra variable.

En cuanto a las desventajas: tendría que tener cuidado al pasar un nombre de parámetro correcto, y pasar __r le daría __r[@] . El comportamiento de la indirección variable para también expandir otras formas de parámetros tampoco está explícitamente documentado.

 function join_by_ref { __= local __r=$1[@] __s=${2-' '} printf -v __ "%s${__s//\%/%%}" "${!__r}" __=${__%${__s}} } array=(1 2 3 4) join_by_ref array echo "$__" # Prints '1 2 3 4'. join_by_ref array '%s' echo "$__" # Prints '1%s2%s3%s4'. join_by_ref 'invalid*' '%s' # Bash 4.4 shows "invalid*[@]: bad substitution". echo "$__" # Prints nothing but newline. 

Esto funciona de 3.1 a 5.0-alfa. Como se observó, la indirección variable no solo funciona con variables, sino también con otros parámetros.

Un parámetro es una entidad que almacena valores. Puede ser un nombre, un número o uno de los caracteres especiales enumerados a continuación en Parámetros especiales. Una variable es un parámetro denotado por un nombre.

Las matrices y los elementos de la matriz también son parámetros (entidades que almacenan el valor) y las referencias a las matrices también son referencias técnicas a los parámetros. Y al igual que el parámetro especial @ , array[@] también hace una referencia válida.

Las formas de expansión alteradas o selectivas (como la expansión de subcadenas) que desvían la referencia del parámetro en sí ya no funcionan.

Este enfoque se ocupa de los espacios dentro de los valores, pero requiere un ciclo:

 #!/bin/bash FOO=( abc ) BAR="" for index in ${!FOO[*]} do BAR="$BAR,${FOO[$index]}" done echo ${BAR:1} 

En caso de que los elementos que desea unir no sean una matriz solo una cadena separada por espacios, puede hacer algo como esto:

foo = “aa bb cc dd” bar = for i in $foo; do printf ",'%s'" $i; done for i in $foo; do printf ",'%s'" $i; done bar for i in $foo; do printf ",'%s'" $i; done = $ {bar: 1} echo $ bar ‘aa’, ‘bb’, ‘cc’, ‘dd’

por ejemplo, mi caso de uso es que algunas cadenas se pasan en mi script de shell y necesito usar esto para ejecutar en una consulta SQL:

./my_script “aa bb cc dd”

En mi_script, necesito hacer “SELECCIONAR * FROM tabla DONDE nombre IN (‘aa’, ‘bb’, ‘cc’, ‘dd’). Entonces el comando anterior será útil.

Si construyes la matriz en un bucle, aquí hay una manera simple:

 arr=() for x in $(some_cmd); do arr+=($x,) done arr[-1]=${arr[-1]%,} echo ${arr[*]} 

Gracias @gniourf_gniourf por los comentarios detallados sobre mi combinación de los mejores mundos hasta ahora. Perdón por publicar código no completamente diseñado y probado. Aquí hay una mejor oportunidad.

 # join with separator join_ws() { local d=$1 s=$2; shift 2 && printf %s "$s${@/#/$d}"; } 

Esta belleza por concepción es

  • (todavía) bash 100% puro (gracias por señalar explícitamente que printf también es un built-in. No sabía esto antes …)
  • funciona con delimitadores de múltiples caracteres
  • más compacto y más completo y esta vez cuidadosamente meditado y probado a largo plazo con subcadenas aleatorias de scripts de shell entre otros, que cubre el uso de caracteres especiales de shell o caracteres de control o sin caracteres en separador y / o parámetros, y casos extremos , y los casos de esquina y otras objeciones como ningún argumento en absoluto. Eso no garantiza que no haya más errores, pero será un desafío un poco más difícil encontrar uno. Por cierto, incluso las respuestas más votadas en la actualidad y las relacionadas sufren de cosas como esa … error …

Ejemplos adicionales:

 $ join_ws '' abc abc $ join_ws ':' {1,7}{A..C} 1A:1B:1C:7A:7B:7C $ join_ws -e -e -e $ join_ws $'\033[F' $'\n\n\n' 1. 2. 3. $'\n\n\n\n' 3. 2. 1. $ join_ws $ $ 

Tal vez me esté perdiendo algo obvio, ya que soy un principiante con todo el asunto de bash / zsh, pero me parece que no necesita usar printf en absoluto. Tampoco se vuelve realmente feo prescindir.

 join() { separator=$1 arr=$* arr=${arr:2} # throw away separator and following space arr=${arr// /$separator} } 

Al menos, me ha funcionado hasta ahora sin problema.

Por ejemplo, join \| *.sh join \| *.sh , que, digamos que estoy en mi ~ directorio, utilities.sh|play.sh|foobar.sh . Suficientemente bueno para mi.

EDITAR: Esta es básicamente la respuesta de Nil Geisweiller , pero generalizada en una función.

 liststr="" for item in list do liststr=$item,$liststr done LEN=`expr length $liststr` LEN=`expr $LEN - 1` liststr=${liststr:0:$LEN} 

Esto también cuida la coma adicional al final. No soy un experto en bash. Solo mi 2c, ya que esto es más elemental y comprensible

 awk -v sep=. 'BEGIN{ORS=OFS="";for(i=1;i 

o

 $ a=(1 "ab" 3) $ b=$(IFS=, ; echo "${a[*]}") $ echo $b 1,ab,3