Si tengo una matriz como esta en Bash:
FOO=( a b c )
¿Cómo unir los elementos con comas? Por ejemplo, produciendo a,b,c
.
Si tengo una matriz como esta en Bash:
FOO=( a b c )
¿Cómo unir los elementos con comas? Por ejemplo, produciendo a,b,c
.
Respuestas:
Solución de reescritura de Pascal Pilz como función en Bash 100% puro (sin comandos externos):
function join_by { local IFS="$1"; shift; echo "$*"; }
Por ejemplo,
join_by , a "b c" d #a,b c,d
join_by / var local tmp #var/local/tmp
join_by , "${FOO[@]}" #a,b,c
Alternativamente, podemos usar printf para admitir delimitadores de caracteres múltiples, utilizando la idea de @gniourf_gniourf
function join_by { local d=$1; shift; echo -n "$1"; shift; printf "%s" "${@/#/$d}"; }
Por ejemplo,
join_by , a b c #a,b,c
join_by ' , ' a b c #a , b , c
join_by ')|(' a b c #a)|(b)|(c
join_by ' %s ' a b c #a %s b %s c
join_by $'\n' a b c #a<newline>b<newline>c
join_by - a b c #a-b-c
join_by '\' a b c #a\b\c
konsolebox
estilo :) function join { local IFS=$1; __="${*:2}"; }
o function join { IFS=$1 eval '__="${*:2}"'; }
. Luego use __
después. Sí, soy el que promueve el uso de __
como variable de resultado;) (y una variable de iteración común o variable temporal). Si el concepto llega a un sitio wiki popular de Bash, me copiaron :)
$d
en el especificador de formato de printf
. Cree que está a salvo desde que "escapó", %
pero hay otras advertencias: cuando el delimitador contiene una barra invertida (por ejemplo, \n
) o cuando el delimitador comienza con un guión (y tal vez otros que no se me ocurren ahora). Por supuesto, puede solucionarlos (reemplace las barras diagonales inversas por barras diagonales dobles y úselas printf -- "$d%s"
), pero en algún momento sentirá que está luchando contra el caparazón en lugar de trabajar con él. Es por eso que, en mi respuesta a continuación, antepuse el delimitador a los términos a unir.
Otra solución más:
#!/bin/bash
foo=('foo bar' 'foo baz' 'bar baz')
bar=$(printf ",%s" "${foo[@]}")
bar=${bar:1}
echo $bar
Editar: igual pero para el separador de longitud variable de varios caracteres:
#!/bin/bash
separator=")|(" # e.g. 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
printf -v bar ",%s" "${foo[@]}"
. Es uno fork
menos (en realidad clone
). Incluso se bifurcan lectura de un archivo: printf -v bar ",%s" $(<infile)
.
$separator
no contiene %s
o por ejemplo, puede hacer que su printf
robusta: printf "%s%s" "$separator" "${foo[@]}"
.
printf "%s%s"
usaría el separador en el conjunto de salida SOLO en primera instancia, y luego simplemente concatenaría el resto de argumentos.
printf "%s" "${foo[@]/#/$separator}"
.
IFS=; regex="${foo[*]/#/$separator}"
. En este punto, esto se convierte esencialmente en la respuesta de gniourf_gniourf, que IMO es más limpia desde el principio, es decir, usar la función para limitar el alcance de los cambios IFS y las variables temporales.
$ foo=(a "b c" d)
$ bar=$(IFS=, ; echo "${foo[*]}")
$ echo "$bar"
a,b c,d
bar=$( IFS=, ; echo "${foo[*]}" )
@
lugar de *
, como en $(IFS=, ; echo "${foo[@]}")
? Puedo ver que *
ya conserva el espacio en blanco en los elementos, nuevamente no estoy seguro de cómo, ya @
que generalmente se requiere para este motivo.
*
. En la página de manual de bash, busque "Parámetros especiales" y busque la explicación junto a *
:
Tal vez, por ejemplo,
SAVE_IFS="$IFS"
IFS=","
FOOJOIN="${FOO[*]}"
IFS="$SAVE_IFS"
echo "$FOOJOIN"
echo "-${IFS}-"
(las llaves separan los guiones del nombre de la variable).
echo $IFS
hace lo mismo.)
Sorprendentemente, mi solución aún no se ha dado :) Esta es la forma más simple para mí. No necesita una función:
IFS=, eval 'joined="${foo[*]}"'
Nota: Se observó que esta solución funciona bien en modo no POSIX. En el modo POSIX , los elementos aún se unen correctamente, pero se IFS=,
vuelven permanentes.
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 printf -v
(¿por qué no le gustaría?) Y pasa un nombre de variable, por supuesto, 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}"
}
join_ret
una variable local y luego haciendo eco al final. Esto permite que join () se use de la forma habitual de scripting de shell, por ejemplo $(join ":" one two three)
, y no requiere una variable global.
$(...)
recorta las nuevas líneas; así que si el último campo de la matriz contiene nuevas líneas finales, estas se recortarán (vea la demostración donde no se recortan con mi diseño).
Esto no es muy diferente de las soluciones existentes, pero evita usar una función separada, no se modifica IFS
en el shell principal y está todo en una sola línea:
arr=(a b c)
printf '%s\n' "$(IFS=,; printf '%s' "${arr[*]}")"
Resultando en
a,b,c
Limitación: el separador no puede tener más de un carácter.
Sin usar comandos externos:
$ FOO=( a b c ) # 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, supone que los elementos no tienen espacios en blanco.
echo ${FOO[@]} | tr ' ' ','
Yo haría eco de la matriz como una cadena, luego transformaría los espacios en saltos de línea, y luego usaría paste
para unir todo en una línea de esta manera:
tr " " "\n" <<< "$FOO" | paste -sd , -
Resultados:
a,b,c
¡Esto parece ser el más rápido y limpio para mí!
$FOO
Sin embargo, es solo el primer elemento de la matriz. Además, esto se rompe para los elementos de la matriz que contienen espacios.
Con la reutilización de @ no importa la solución, sino con una declaración evitando la sustitución $ {: 1} y la necesidad de una variable intermediaria.
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, de modo que se documentan las concatenaciones de las cadenas. Luego, el truco es usar la longitud de la LISTA para cortar el último sperator, ya que el corte retendrá solo la longitud de la LISTA a medida que los campos cuentan.
s=$(IFS=, eval 'echo "${FOO[*]}"')
@Q
podría escapar de los valores unidos de una mala interpretación cuando tienen una foo=("a ," "b ' ' c" "' 'd e" "f " ";" "ls -latr"); s=$(IFS=, eval 'echo "${foo[*]@Q}"'); echo "${s}"
'a ,','b '\'' '\'' c',''\'' '\''d e','f ',';','ls -latr '
Solución printf que acepta separadores de cualquier longitud (basado en @ no importa la 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
printf
especificador de formato . (Por ejemplo, %s
sin querer en $sep
causará problemas.
sep
se puede desinfectar con ${sep//\%/%%}
. Me gusta su solución mejor que ${bar#${sep}}
o ${bar%${sep}}
(alternativa). Esto es bueno si se convierte en una función que almacena el resultado en una variable genérica como __
, y no en echo
ella.
function join_by { printf -v __ "${1//\%/%%}%s" "${@:2}"; __=${__:${#1}}; }
$ set a 'b c' d
$ history -p "$@" | paste -sd,
a,b c,d
HISTSIZE=0
?
paste -sd,
no se trata del uso de la historia.
HISTSIZE=0
, pruébalo.
Versión más corta de la respuesta superior:
joinStrings() { local a=("${@:3}"); printf "%s" "$2${a[@]/#/$1}"; }
Uso:
joinStrings "$myDelimiter" "${myArray[@]}"
join_strings () { local d="$1"; echo -n "$2"; shift 2 && printf '%s' "${@/#/$d}"; }
join_strings () { local d="$1"; echo -n "$2"; shift 2 && printf '$d%s' "${@}"; }
esto funciona con el uso:join_strings 'delim' "${array[@]}"
o sin join_strings 'delim' ${array[@]}
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
Ejemplos:
$ join_ws , a b c
a,b,c
$ join_ws '' a b c
abc
$ join_ws $'\n' a b c
a
b
c
$ join_ws ' \/ ' A B C
A \/ B \/ C
join_ws ,
(sin argumentos) resultados incorrectos ,,
. 2. join_ws , -e
no genera nada incorrectamente (eso se debe a que está utilizando incorrectamente en echo
lugar de printf
). En realidad, no sé por qué anunciaste el uso de en echo
lugar de printf
: echo
está notoriamente roto y printf
es una construcción robusta.
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 que 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 )
ARGS="--ignore $(echo "${TO_IGNORE[@]}" | tr ' ' ',')"
. El operador $()
es más poderoso que el backtics (permite anidar $()
y ""
). Envolver ${TO_IGNORE[@]}
con comillas dobles también debería ayudar.
Use perl para separadores de caracteres múltiples:
function join {
perl -e '$s = shift @ARGV; print join($s, @ARGV);' "$@";
}
join ', ' a b c # a, b, c
O en una línea:
perl -le 'print join(shift, @ARGV);' ', ' 1 2 3
1, 2, 3
join
nombre está en conflicto con algo de basura OS X
... lo llamaría conjoined
, ¿o tal vez jackie_joyner_kersee
?
Gracias @gniourf_gniourf por los comentarios detallados sobre mi combinación de los mejores mundos hasta ahora. Perdón por publicar código que no está completamente diseñado y probado. Aquí hay un mejor intento.
# join with separator
join_ws() { local d=$1 s=$2; shift 2 && printf %s "$s${@/#/$d}"; }
Esta belleza por concepción es
Ejemplos adicionales:
$ join_ws '' a b c
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 $
$
En caso de que los elementos que desee 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`
bar=${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 una consulta SQL:
./my_script "aa bb cc dd"
En my_script, necesito hacer "SELECT * FROM table WHERE name IN ('aa', 'bb', 'cc', 'dd'). Entonces el comando anterior será útil.
printf -v bar ...
lugar de tener que ejecutar el ciclo printf en una subshell y capturar la salida.
Aquí hay uno que admite la mayoría de los shells compatibles con POSIX:
join_by() {
# Usage: join_by "||" a b c d
local arg arr=() sep="$1"
shift
for arg in "$@"; do
if [ 0 -lt "${#arr[@]}" ]; then
arr+=("${sep}")
fi
arr+=("${arg}") || break
done
printf "%s" "${arr[@]}"
}
local
) en absoluto.
Usar indirección variable para referirse directamente a una matriz también funciona. También se pueden usar referencias con nombre, pero solo estuvieron disponibles en 4.3.
La ventaja de usar esta forma de función es que puede tener el separador opcional (por defecto es el primer carácter predeterminado IFS
, que es un espacio; quizás lo convierta en una cadena vacía si lo desea), y evita expandir los valores dos veces (primero cuando se pasa 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.
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.
Siéntase libre de usar un nombre más cómodo para la función.
Esto funciona desde 3.1 a 5.0-alpha. Como se observó, la indirección de variables 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 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.
En la versión de lanzamiento de Bash 5.0, la indirección variable ya se denomina expansión indirecta y su comportamiento ya está explícitamente documentado en el manual:
Si el primer carácter del parámetro es un signo de exclamación (!), Y el parámetro no es una referencia de nombre, introduce un nivel de indirección. Bash usa el valor formado al expandir el resto del parámetro como el nuevo parámetro; esto se expande y ese valor se usa en el resto de la expansión, en lugar de la expansión del parámetro original. Esto se conoce como expansión indirecta.
Tomando nota de que en la documentación de ${parameter}
, parameter
se conoce como "un parámetro de shell como se describe (en) PARÁMETROS o una referencia de matriz ". Y en la documentación de las matrices, se menciona que "Cualquier elemento de una matriz puede ser referenciado usando ${name[subscript]}
". Esto hace __r[@]
una referencia de matriz.
Vea mi comentario en la respuesta de Riccardo Galli .
__
como nombre de variable? Hace que el código sea realmente ilegible.
Si construye 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[*]}
x=${"${arr[*]}"// /,}
Esta es la forma más corta de hacerlo.
Ejemplo,
arr=(1 2 3 4 5)
x=${"${arr[*]}"// /,}
echo $x # output: 1,2,3,4,5
bash: ${"${arr[*]}"// /,}: bad substitution
Quizás me estoy perdiendo algo obvio, ya que soy un novato en todo lo relacionado con bash / zsh, pero me parece que no necesitas usar printf
nada. Tampoco se vuelve realmente feo prescindir de él.
join() {
separator=$1
arr=$*
arr=${arr:2} # throw away separator and following space
arr=${arr// /$separator}
}
Al menos, me ha funcionado hasta ahora sin problemas.
Por ejemplo, join \| *.sh
que, digamos que estoy en mi ~
directorio, salidas 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 se encarga de la coma extra al final también. No soy un experto en bash. Solo mi 2c, ya que esto es más elemental y comprensible