En un script Bash, me gustaría dividir una línea en pedazos y almacenarlos en una matriz.
La línea:
Paris, France, Europe
Me gustaría tenerlos en una matriz como esta:
array[0] = Paris
array[1] = France
array[2] = Europe
Me gustaría usar un código simple, la velocidad del comando no importa. ¿Cómo puedo hacerlo?
,
(coma-espacio) y no a un solo carácter como la coma. Si solo está interesado en lo último, las respuestas aquí son más fáciles de seguir: stackoverflow.com/questions/918886/…cut
es un comando bash útil a tener en cuenta también. El separador es definible en.wikibooks.org/wiki/Cut También puede extraer datos de una estructura de registro de ancho fijo. en.wikipedia.org/wiki/Cut_(Unix) computerhope.com/unix/ucut.htmRespuestas:
Tenga en cuenta que los personajes
$IFS
son tratados de forma individual como separadores de manera que en este caso los campos se pueden separar por cualquiera una coma o un espacio en lugar de la secuencia de los dos caracteres. Curiosamente, sin embargo, los campos vacíos no se crean cuando el espacio de coma aparece en la entrada porque el espacio se trata especialmente.Para acceder a un elemento individual:
Para iterar sobre los elementos:
Para obtener tanto el índice como el valor:
El último ejemplo es útil porque las matrices de Bash son escasas. En otras palabras, puede eliminar un elemento o agregar un elemento y luego los índices no son contiguos.
Para obtener el número de elementos en una matriz:
Como se mencionó anteriormente, las matrices pueden ser dispersas, por lo que no debe usar la longitud para obtener el último elemento. Así es como puedes hacerlo en Bash 4.2 y versiones posteriores:
en cualquier versión de Bash (desde algún lugar después de 2.05b):
Las compensaciones negativas más grandes se seleccionan más lejos del final de la matriz. Tenga en cuenta el espacio antes del signo menos en la forma anterior. Es requerido.
fuente
IFS=', '
, entonces no tiene que eliminar los espacios por separado. Prueba:IFS=', ' read -a array <<< "Paris, France, Europe"; echo "${array[@]}"
declare -p array
para la salida de prueba, por cierto.France, Europe, "Congo, The Democratic Republic of the"
esto se dividirá después del congo.str="Paris, France, Europe, Los Angeles"; IFS=', ' read -r -a array <<< "$str"
se dividirá enarray=([0]="Paris" [1]="France" [2]="Europe" [3]="Los" [4]="Angeles")
una nota. Por lo tanto, esto solo funciona con campos sin espacios, ya queIFS=', '
es un conjunto de caracteres individuales, no un delimitador de cadena.Todas las respuestas a esta pregunta son incorrectas de una forma u otra.
Respuesta incorrecta # 1
1: Esto es un mal uso de
$IFS
. El valor de la$IFS
variable no se toma como un único separador de cadena de longitud variable , sino que se toma como un conjunto de separadores de cadena de un solo carácter , donde cada campo queread
se separa de la línea de entrada puede ser terminado por cualquier carácter en el conjunto (coma o espacio, en este ejemplo).En realidad, para los fanáticos reales, el significado completo de
$IFS
es un poco más complicado. Del manual de bash :Básicamente, para valores no predeterminados no nulos de
$IFS
, los campos se pueden separar con (1) una secuencia de uno o más caracteres que pertenecen al conjunto de "caracteres de espacio en blanco IFS" (es decir, cualquiera de <space> , <tab> y <newline> ("nueva línea", que significa avance de línea (LF) ) están presentes en cualquier lugar$IFS
), o (2) cualquier carácter de "espacio en blanco IFS" que esté presente en$IFS
junto con los "caracteres de espacio en blanco IFS" que lo rodean en la línea de entrada.Para el OP, es posible que el segundo modo de separación que describí en el párrafo anterior sea exactamente lo que quiere para su cadena de entrada, pero podemos estar bastante seguros de que el primer modo de separación que describí no es correcto en absoluto. Por ejemplo, ¿qué pasa si su cadena de entrada era
'Los Angeles, United States, North America'
?2: Incluso si usted fuera a utilizar esta solución con un separador de un solo carácter (como una coma por sí mismo, es decir, sin espacio siguiente u otro equipaje), si el valor de la
$string
variable de pasa para contener cualquier LF, a continuación,read
se deje de procesar una vez que encuentre el primer LF. Elread
builtin solo procesa una línea por invocación. Esto es cierto incluso si está canalizando o redirigiendo la entrada solo a laread
declaración, como lo estamos haciendo en este ejemplo con el mecanismo here-string , y por lo tanto se garantiza que la entrada no procesada se perderá. El código que impulsa elread
incorporado no tiene conocimiento del flujo de datos dentro de su estructura de comando que contiene.Podría argumentar que es poco probable que esto cause un problema, pero aún así, es un peligro sutil que debe evitarse si es posible. Es causada por el hecho de que la
read
construcción en realidad hace dos niveles de división de entrada: primero en líneas, luego en campos. Dado que el OP solo quiere un nivel de división, este uso delread
builtin no es apropiado, y debemos evitarlo.3: Un problema potencial no obvio con esta solución es que
read
siempre elimina el campo final si está vacío, aunque de lo contrario conserva los campos vacíos. Aquí hay una demostración:Tal vez el OP no se preocuparía por esto, pero sigue siendo una limitación que vale la pena conocer. Reduce la robustez y generalidad de la solución.
Este problema se puede resolver agregando un delimitador final falso a la cadena de entrada justo antes de alimentarlo
read
, como demostraré más adelante.Respuesta incorrecta # 2
Idea similar:
(Nota: agregué los paréntesis faltantes alrededor de la sustitución del comando que el respondedor parece haber omitido).
Idea similar:
Estas soluciones aprovechan la división de palabras en una asignación de matriz para dividir la cadena en campos. Curiosamente, al igual
read
que la división de palabras general también utiliza la$IFS
variable especial, aunque en este caso se da a entender que se establece en su valor predeterminado de <space><tab> <newline> y, por lo tanto, cualquier secuencia de uno o más IFS los caracteres (que ahora son todos espacios en blanco) se consideran un delimitador de campo.Esto resuelve el problema de dos niveles de división cometidos por
read
, ya que la división de palabras por sí misma constituye solo un nivel de división. Pero al igual que antes, el problema aquí es que los campos individuales en la cadena de entrada ya pueden contener$IFS
caracteres y, por lo tanto, se dividirían incorrectamente durante la operación de división de palabras. Este no es el caso para ninguna de las cadenas de entrada de muestra proporcionadas por estos respondedores (qué conveniente ...), pero por supuesto eso no cambia el hecho de que cualquier base de código que usara este idioma correría el riesgo de explotar si alguna vez se viola esta suposición en algún momento. Una vez más, considere mi contraejemplo de'Los Angeles, United States, North America'
(o'Los Angeles:United States:North America'
).También, la división de palabras es normalmente seguido por la expansión de nombre de archivo ( aka expansión nombre de ruta aka globbing), que, si se hace, se palabras potencialmente corruptos que contienen los caracteres
*
,?
o[
seguido de]
(y, siextglob
se establece, los fragmentos entre paréntesis precedido de?
,*
,+
,@
, o!
) al compararlos con objetos del sistema de archivos y expandir las palabras ("globos") en consecuencia. El primero de estos tres respondedores ha socavado hábilmente este problema al ejecutar deset -f
antemano para desactivar el bloqueo. Técnicamente esto funciona (aunque probablemente debería agregarset +f
luego para volver a habilitar el globbing para el código posterior que puede depender de él), pero no es deseable tener que meterse con la configuración global del shell para hackear una operación básica de análisis de cadena a matriz en código local.Otro problema con esta respuesta es que todos los campos vacíos se perderán. Esto puede o no ser un problema, dependiendo de la aplicación.
Nota: Si va a usar esta solución, es mejor usar la forma de
${string//:/ }
"sustitución de patrón" de expansión de parámetros , en lugar de tener que molestarse en invocar una sustitución de comando (que bifurca el shell), iniciar una canalización y ejecutando un ejecutable externo (tr
osed
), ya que la expansión de parámetros es puramente una operación interna del shell. (Además, para las solucionestr
ysed
, la variable de entrada debe estar entre comillas dobles dentro de la sustitución del comando; de lo contrario, la división de palabras tendría efecto en elecho
comando y potencialmente alteraría los valores del campo. Además, la$(...)
forma de sustitución del comando es preferible a la anterior`...`
formulario ya que simplifica el anidamiento de las sustituciones de comandos y permite un mejor resaltado de sintaxis por parte de los editores de texto).Respuesta incorrecta # 3
Esta respuesta es casi la misma que la n . ° 2 . La diferencia es que el respondedor ha asumido que los campos están delimitados por dos caracteres, uno de los cuales está representado por defecto
$IFS
y el otro no. Ha resuelto este caso bastante específico eliminando el carácter no representado por IFS utilizando una expansión de sustitución de patrón y luego utilizando la división de palabras para dividir los campos en el carácter delimitador representado por IFS sobreviviente.Esta no es una solución muy genérica. Además, se puede argumentar que la coma es realmente el carácter delimitador "primario" aquí, y que eliminarlo y luego, dependiendo del carácter de espacio para la división de campo, es simplemente incorrecto. Una vez más, tenga en cuenta mis contraejemplo:
'Los Angeles, United States, North America'
.Además, una vez más, la expansión del nombre de archivo podría corromper las palabras expandidas, pero esto se puede evitar deshabilitando temporalmente la asignación con
set -f
y luegoset +f
.Además, nuevamente, se perderán todos los campos vacíos, lo que puede o no ser un problema dependiendo de la aplicación.
Respuesta incorrecta # 4
Esto es similar a # 2 y # 3 en que usa la división de palabras para hacer el trabajo, solo que ahora el código se establece explícitamente
$IFS
para contener solo el delimitador de campo de un solo carácter presente en la cadena de entrada. Debe repetirse que esto no puede funcionar para delimitadores de campo de caracteres múltiples, como el delimitador de espacio de coma del OP. Pero para un delimitador de un solo carácter como el LF utilizado en este ejemplo, en realidad se acerca a ser perfecto. Los campos no se pueden dividir involuntariamente en el medio como vimos con respuestas incorrectas anteriores, y solo hay un nivel de división, según sea necesario.Un problema es que la expansión del nombre de archivo corromperá las palabras afectadas como se describió anteriormente, aunque una vez más, esto se puede resolver envolviendo la declaración crítica en
set -f
yset +f
.Otro problema potencial es que, dado que LF califica como un "carácter de espacio en blanco IFS" como se definió anteriormente, todos los campos vacíos se perderán, al igual que en # 2 y # 3 . Por supuesto, esto no sería un problema si el delimitador no es un "carácter de espacio en blanco IFS" y, dependiendo de la aplicación, puede no importar de todos modos, pero sí vicia la generalidad de la solución.
En resumen, suponiendo que tiene un delimitador de un carácter y que no es un "carácter de espacio en blanco IFS" o que no le interesan los campos vacíos y ajusta la declaración crítica
set -f
yset +f
, entonces, esta solución funciona , pero por lo demás no.(Además, por el bien de la información, la asignación de un LF a una variable en bash se puede hacer más fácilmente con la
$'...'
sintaxis, por ejemploIFS=$'\n';
).Respuesta incorrecta # 5
Idea similar:
Esta solución es efectivamente un cruce entre el n . ° 1 (en que se establece
$IFS
en espacio de coma) y el n . ° 2-4 (en que usa la división de palabras para dividir la cadena en campos). Debido a esto, sufre la mayoría de los problemas que afectan a todas las respuestas incorrectas anteriores, algo así como el peor de todos los mundos.Además, con respecto a la segunda variante, puede parecer que la
eval
llamada es completamente innecesaria, ya que su argumento es un literal de cadena entre comillas simples y, por lo tanto, es estáticamente conocido. Pero en realidad hay un beneficio muy obvio de usareval
de esta manera. Normalmente, cuando se ejecuta un comando simple que consiste en una asignación de variable única , es decir, sin una palabra de comando real que le sigue, la asignación tiene efecto en el entorno de shell:Esto es cierto incluso si el comando simple involucra múltiples asignaciones de variables; de nuevo, siempre que no haya una palabra de comando, todas las asignaciones de variables afectan el entorno del shell:
Pero, si la asignación de variables se adjunta a un nombre de comando (me gusta llamar a esto una "asignación de prefijo"), entonces no afecta el entorno de shell y, en cambio, solo afecta el entorno del comando ejecutado, independientemente de si es una función incorporada o externo:
Cita relevante del manual de bash :
Es posible explotar esta característica de asignación de variables para cambiar
$IFS
solo temporalmente, lo que nos permite evitar todo el gambito de guardar y restaurar como el que se está haciendo con la$OIFS
variable en la primera variante. Pero el desafío que enfrentamos aquí es que el comando que necesitamos ejecutar es en sí mismo una mera asignación de variables, y por lo tanto no implicaría una palabra de comando para hacer que la$IFS
asignación sea temporal. Podrías pensar para ti mismo, bueno, ¿por qué no simplemente agregar una palabra de comando no-op a la declaración como: builtin
para hacer que la$IFS
asignación sea temporal? Esto no funciona porque luego también haría que la$array
asignación sea temporal:Por lo tanto, estamos efectivamente en un punto muerto, un poco atrapados. Pero, cuando
eval
ejecuta su código, lo ejecuta en el entorno de shell, como si fuera normal, código fuente estático, y por lo tanto podemos ejecutar la$array
asignación dentro deleval
argumento para que tenga efecto en el entorno de shell, mientras que la$IFS
asignación de prefijo que está prefijado aleval
comando no sobrevivirá aleval
comando. Este es exactamente el truco que se está utilizando en la segunda variante de esta solución:Entonces, como puede ver, en realidad es un truco bastante inteligente, y logra exactamente lo que se requiere (al menos con respecto a la afectación de la asignación) de una manera bastante obvia. En realidad no estoy en contra de este truco en general, a pesar de la participación de
eval
; solo tenga cuidado de comillas simples la cadena de argumentos para protegerse contra las amenazas de seguridad.Pero de nuevo, debido a la aglomeración de problemas "lo peor de todos los mundos", esta sigue siendo una respuesta incorrecta a los requisitos del OP.
Respuesta incorrecta # 6
¿Um que? El OP tiene una variable de cadena que debe analizarse en una matriz. Esta "respuesta" comienza con el contenido literal de la cadena de entrada pegada en un literal de matriz. Supongo que es una forma de hacerlo.
Parece que el respondedor puede haber asumido que la
$IFS
variable afecta a todos los análisis de bash en todos los contextos, lo cual no es cierto. Del manual de bash:Por lo tanto, la
$IFS
variable especial en realidad solo se usa en dos contextos: (1) división de palabras que se realiza después de la expansión (es decir, no al analizar el código fuente de bash) y (2) para dividir las líneas de entrada en palabras por elread
incorporado.Déjame intentar aclarar esto. Creo que podría ser bueno hacer una distinción entre análisis y ejecución . Bash primero debe analizar el código fuente, que obviamente es un evento de análisis , y luego ejecuta el código, que es cuando la expansión entra en escena. La expansión es realmente un evento de ejecución . Además, discrepo con la descripción de la
$IFS
variable que acabo de citar arriba; en lugar de decir que la división de palabras se realiza después de la expansión , yo diría que la división de palabras se realiza durante la expansión o, quizás más precisamente, la división de palabras es parte deEl proceso de expansión. La frase "división de palabras" se refiere solo a este paso de expansión; nunca debería usarse para referirse al análisis del código fuente de bash, aunque desafortunadamente los documentos parecen arrojar muchas veces las palabras "dividir" y "palabras". Aquí hay un extracto relevante de la versión linux.die.net del manual bash:Podría argumentar que la versión GNU del manual funciona un poco mejor, ya que opta por la palabra "tokens" en lugar de "palabras" en la primera oración de la sección Expansión:
El punto importante es
$IFS
que no cambia la forma en que bash analiza el código fuente. El análisis del código fuente de bash es en realidad un proceso muy complejo que implica el reconocimiento de los diversos elementos de la gramática de shell, como secuencias de comandos, listas de comandos, tuberías, expansiones de parámetros, sustituciones aritméticas y sustituciones de comandos. En su mayor parte, el proceso de análisis de bash no puede ser alterado por acciones a nivel de usuario como asignaciones de variables (en realidad, hay algunas excepciones menores a esta regla; por ejemplo, vea las diversascompatxx
configuraciones de shell, que puede cambiar ciertos aspectos del comportamiento de análisis sobre la marcha). Las "palabras" / "tokens" ascendentes que resultan de este complejo proceso de análisis se expanden de acuerdo con el proceso general de "expansión" tal como se desglosa en los extractos de documentación anteriores, donde la división de palabras del texto expandido (¿expansivo?) En aguas abajo palabras es simplemente un paso de ese proceso. La división de palabras solo toca el texto que se ha escupido de un paso de expansión anterior; no afecta el texto literal que fue analizado directamente desde la fuente por testream.Respuesta incorrecta # 7
Esta es una de las mejores soluciones. Tenga en cuenta que hemos vuelto a usar
read
. ¿No dije antes que esoread
es inapropiado porque realiza dos niveles de división, cuando solo necesitamos uno? El truco aquí es que puede llamarread
de tal manera que efectivamente solo hace un nivel de división, específicamente al dividir solo un campo por invocación, lo que requiere el costo de tener que llamarlo repetidamente en un bucle. Es un juego de manos, pero funciona.Pero hay problemas. Primero: cuando proporciona al menos un argumento NAME para
read
, ignora automáticamente los espacios en blanco iniciales y finales en cada campo que se separa de la cadena de entrada. Esto ocurre independientemente de si$IFS
se establece en su valor predeterminado o no, como se describió anteriormente en esta publicación. Ahora, el OP puede no importarle esto por su caso de uso específico, y de hecho, puede ser una característica deseable del comportamiento de análisis. Pero no todos los que quieran analizar una cadena en los campos querrán esto. Sin embargo, hay una solución: un uso algo no obvio deread
es pasar cero argumentos NAME . En este caso,read
almacenará toda la línea de entrada que obtiene de la secuencia de entrada en una variable denominada$REPLY
y, como beneficio adicional, nosepare los espacios en blanco iniciales y finales del valor. Este es un uso muy robustoread
que he explotado con frecuencia en mi carrera de programación de shell. Aquí hay una demostración de la diferencia de comportamiento:El segundo problema con esta solución es que en realidad no aborda el caso de un separador de campo personalizado, como el espacio de coma del OP. Como antes, los separadores de caracteres múltiples no son compatibles, lo cual es una limitación desafortunada de esta solución. Podríamos intentar al menos dividirnos en comas especificando el separador de la
-d
opción, pero mira lo que sucede:Como era de esperar, el espacio en blanco circundante no contabilizado se introdujo en los valores de campo y, por lo tanto, esto tendría que corregirse posteriormente mediante operaciones de recorte (esto también podría hacerse directamente en el bucle while). Pero hay otro error obvio: ¡falta Europa! ¿Que le paso a eso? La respuesta es que
read
devuelve un código de retorno fallido si llega al final del archivo (en este caso podemos llamarlo final de la cadena) sin encontrar un terminador de campo final en el campo final. Esto hace que el ciclo while se rompa prematuramente y perdamos el campo final.Técnicamente, este mismo error también afectaba a los ejemplos anteriores; la diferencia es que el separador de campo se consideró LF, que es el valor predeterminado cuando no especifica la
-d
opción, y el<<<
mecanismo ("here-string") agrega automáticamente un LF a la cadena justo antes de que se alimente como entrada al comando. Por lo tanto, en esos casos, solucionamos accidentalmente el problema de un campo final eliminado agregando involuntariamente un terminador ficticio adicional a la entrada. Llamemos a esta solución la solución "dummy-terminator". Podemos aplicar la solución de terminación ficticia manualmente para cualquier delimitador personalizado concatenando contra la cadena de entrada nosotros mismos al instanciarla en la cadena here:Ahí, problema resuelto. Otra solución es solo romper el ciclo while si tanto (1)
read
devolvió el error como (2)$REPLY
está vacío, lo que significa queread
no pudo leer ningún carácter antes de tocar el final del archivo. Manifestación:Este enfoque también revela el LF secreto que el
<<<
operador de redireccionamiento agrega automáticamente a la cadena aquí . Por supuesto, podría eliminarse por separado a través de una operación de recorte explícita como se describió hace un momento, pero obviamente el enfoque manual de terminador ficticio lo resuelve directamente, por lo que podríamos seguir con eso. La solución manual de terminación ficticia es realmente bastante conveniente ya que resuelve ambos problemas (el problema de campo final descartado y el problema de LF adjunto) de una sola vez.Entonces, en general, esta es una solución bastante poderosa. Su única debilidad es la falta de soporte para delimitadores de múltiples caracteres, que abordaré más adelante.
Respuesta incorrecta # 8
(Esto es en realidad de la misma publicación que # 7 ; el respondedor proporcionó dos soluciones en la misma publicación).
La
readarray
construcción, que es sinónimo demapfile
, es ideal. Es un comando incorporado que analiza un bytestream en una variable de matriz de una sola vez; sin jugar con bucles, condicionales, sustituciones o cualquier otra cosa. Y no elimina subrepticiamente ningún espacio en blanco de la cadena de entrada. Y (si-O
no se proporciona) borra convenientemente la matriz de destino antes de asignarla. Pero todavía no es perfecto, de ahí mi crítica de ello como una "respuesta incorrecta".Primero, solo para sacar esto del camino, tenga en cuenta que, al igual que el comportamiento de
read
cuando se analiza el campo, sereadarray
elimina el campo final si está vacío. Nuevamente, esto probablemente no sea una preocupación para el OP, pero podría serlo para algunos casos de uso. Volveré a esto en un momento.En segundo lugar, como antes, no admite delimitadores de caracteres múltiples. Daré una solución para esto en un momento también.
En tercer lugar, la solución tal como está escrita no analiza la cadena de entrada del OP y, de hecho, no se puede usar como está para analizarla. Ampliaré esto momentáneamente también.
Por las razones anteriores, todavía considero que esto es una "respuesta incorrecta" a la pregunta del OP. A continuación, daré lo que considero la respuesta correcta.
Respuesta correcta
Aquí hay un intento ingenuo de hacer que el # 8 funcione simplemente especificando la
-d
opción:Vemos que el resultado es idéntico al resultado que obtuvimos del enfoque condicional doble de la
read
solución de bucle discutido en el n . ° 7 . Casi podemos resolver esto con el truco manual del terminador ficticio:El problema aquí es que
readarray
conservó el campo final, ya que el<<<
operador de redireccionamiento agregó el LF a la cadena de entrada y, por lo tanto, el campo final no estaba vacío (de lo contrario, se habría eliminado). Podemos ocuparnos de esto desarmando explícitamente el elemento de matriz final después del hecho:Los únicos dos problemas que quedan, que en realidad están relacionados, son (1) el espacio en blanco extraño que necesita ser recortado, y (2) la falta de soporte para delimitadores de caracteres múltiples.
Por supuesto, el espacio en blanco podría recortarse después (por ejemplo, consulte ¿Cómo recortar el espacio en blanco de una variable Bash? ). Pero si podemos hackear un delimitador de múltiples caracteres, eso resolvería ambos problemas de una sola vez.
Desafortunadamente, no hay una forma directa de hacer que funcione un delimitador de caracteres múltiples. La mejor solución que he pensado es preprocesar la cadena de entrada para reemplazar el delimitador de caracteres múltiples con un delimitador de un solo carácter que se garantizará que no colisionen con el contenido de la cadena de entrada. El único carácter que tiene esta garantía es el byte NUL . Esto se debe a que, en bash (aunque no en zsh, por cierto), las variables no pueden contener el byte NUL. Este paso de preprocesamiento se puede realizar en línea en una sustitución de proceso. Aquí se explica cómo hacerlo con awk :
¡Por fin! Esta solución no dividirá erróneamente los campos en el medio, no se cortará prematuramente, no dejará caer campos vacíos, no se corromperá en las expansiones de nombre de archivo, no eliminará automáticamente los espacios en blanco iniciales y finales, no dejará un LF polizón al final, no requiere bucles y no se conforma con un delimitador de un solo carácter.
Solución de corte
Por último, quería demostrar mi propia solución de recorte bastante compleja utilizando la oscura
-C callback
opción dereadarray
. Desafortunadamente, me he quedado sin espacio contra el límite draconiano de 30,000 caracteres de Stack Overflow, por lo que no podré explicarlo. Lo dejaré como ejercicio para el lector.fuente
-d
opción dereadarray
aparecer por primera vez en Bash 4.4.awk '{ gsub(/,[ ]+|$/,"\0"); print }'
y elimina esa concatenación de la final,", "
entonces no tiene que pasar por la gimnasia para eliminar el registro final. Entonces:readarray -td '' a < <(awk '{ gsub(/,[ ]+/,"\0"); print; }' <<<"$string")
en Bash que es compatiblereadarray
. Tenga en cuenta que su método es Bash 4.4+ Creo que debido a la-d
enreadarray
readarray
. En este caso, puede usar la segunda mejor solución basada enread
. Me refiero a esto:a=(); while read -rd,; do a+=("$REPLY"); done <<<"$string,";
(con laawk
sustitución si necesita soporte delimitador de caracteres múltiples). Déjame saber si surge algún problema; Estoy bastante seguro de que esta solución debería funcionar en versiones bastante antiguas de bash, volviendo a la versión 2-algo, lanzada como hace dos décadas.Aquí hay una manera sin configurar IFS:
La idea es usar reemplazo de cadena:
para reemplazar todas las coincidencias de $ substring con espacios en blanco y luego usar la cadena sustituida para inicializar una matriz:
Nota: esta respuesta hace uso del operador split + glob . Por lo tanto, para evitar la expansión de algunos caracteres (como
*
), es una buena idea hacer una pausa en la búsqueda de este script.fuente
${string//:/ }
evitan la expansión de shellarray=(${string//:/ })
Imprime tres
fuente
a=($(echo $t | tr ',' "\n"))
. Mismo resultado cona=($(echo $t | tr ',' ' '))
.VERSION="16.04.2 LTS (Xenial Xerus)"
en unbash
shell, y el últimoecho
solo imprime una línea en blanco. ¿Qué versión de Linux y qué shell estás usando? Desafortunadamente, no se puede mostrar la sesión de terminal en un comentario.A veces me ocurrió que el método descrito en la respuesta aceptada no funcionó, especialmente si el separador es un retorno de carro.
En esos casos resolví de esta manera:
fuente
read -a arr <<< "$strings"
no funcionóIFS=$'\n'
.La respuesta aceptada funciona para valores en una línea.
Si la variable tiene varias líneas:
Necesitamos un comando muy diferente para obtener todas las líneas:
while read -r line; do lines+=("$line"); done <<<"$string"
O el mucho más simple bash readarray :
Imprimir todas las líneas es muy fácil aprovechando la función printf:
fuente
Esto es similar al enfoque de Jmoney38 , pero usando sed:
Impresiones 1
fuente
La clave para dividir su cadena en una matriz es el delimitador de caracteres múltiples de
", "
. Cualquier solución usandoIFS
para delimitadores de caracteres múltiples es inherentemente incorrecta ya que IFS es un conjunto de esos caracteres, no una cadena.Si asigna,
IFS=", "
entonces la cadena se romperá EN","
O O" "
cualquier combinación de ellos que no sea una representación precisa del delimitador de dos caracteres de", "
.Puede usar
awk
osed
para dividir la cadena, con la sustitución del proceso:Es más eficiente usar una expresión regular directamente en Bash:
Con la segunda forma, no hay sub shell y será inherentemente más rápido.
Editar por bgoldst: Aquí hay algunos puntos de referencia que comparan mi
readarray
solución con la solución regex de dawg, y también incluí laread
solución para el gusto (nota: modifiqué ligeramente la solución regex para una mayor armonía con mi solución) (también vea mis comentarios debajo del enviar):fuente
$BASH_REMATCH
. Funciona y, de hecho, evita generar subcapas. +1 de mi parte Sin embargo, a modo de crítica, la expresión regular en sí misma es un poco no ideal, ya que parece que se vio obligado a duplicar parte del token delimitador (específicamente la coma) para evitar la falta de soporte para multiplicadores no codiciosos (también mira alrededor) en ERE (sabor de expresión regular "extendido" integrado en bash). Esto lo hace un poco menos genérico y robusto.\n
líneas de texto delimitadas) que comprenda esos campos, por lo que la desaceleración catastrófica probablemente no ocurriría. Si tiene una cadena con 100,000 campos, tal vez Bash no es ideal ;-) Gracias por el punto de referencia. Aprendí una o dos cosas.Solución de delimitador de caracteres múltiples de bash puro.
Como otros han señalado en este hilo, la pregunta del OP dio un ejemplo de una cadena delimitada por comas para ser analizada en una matriz, pero no indicó si él / ella solo estaba interesado en delimitadores de coma, delimitadores de un solo carácter o de varios caracteres. delimitadores
Dado que Google tiende a clasificar esta respuesta en la parte superior de los resultados de búsqueda o cerca de ella, quería proporcionar a los lectores una respuesta sólida a la pregunta de delimitadores de caracteres múltiples, ya que eso también se menciona en al menos una respuesta.
Si está buscando una solución a un problema de delimitador de caracteres múltiples, le sugiero que revise la publicación de Mallikarjun M , en particular la respuesta de gniourf_gniourf, que proporciona esta elegante solución pura de BASH utilizando la expansión de parámetros:
Enlace al comentario citado / publicación referenciada
Enlace a la pregunta citada: ¿ Cómo dividir una cadena en un delimitador de varios caracteres en bash?
fuente
Esto funciona para mí en OSX:
Si su cadena tiene un delimitador diferente, solo reemplace aquellos con espacio:
Simple :-)
fuente
Otra forma de hacerlo sin modificar IFS:
En lugar de cambiar IFS para que coincida con nuestro delimitador deseado, podemos reemplazar todas las apariciones de nuestro delimitador deseado
", "
con contenido de$IFS
via"${string//, /$IFS}"
.¿Quizás esto sea lento para cadenas muy grandes?
Esto se basa en la respuesta de Dennis Williamson.
fuente
Encontré esta publicación cuando buscaba analizar una entrada como: word1, word2, ...
nada de lo anterior me ayudó. lo resolvió usando awk. Si ayuda a alguien:
fuente
Prueba esto
Es simple. Si lo desea, también puede agregar una declaración (y también eliminar las comas):
El IFS se agrega para deshacer lo anterior pero funciona sin él en una nueva instancia de bash
fuente
Podemos usar el comando tr para dividir la cadena en el objeto de matriz. Funciona tanto en MacOS como en Linux
Otra opción es usar el comando IFS
fuente
Utilizar este:
fuente
array=( $string )
es un (por desgracia muy común) antipatrón: división de palabras se produce:string='Prague, Czech Republic, Europe'
; La expansión del nombre de ruta ocurre:string='foo[abcd],bar[efgh]'
fallará si tiene un archivo llamado, por ejemplo,food
obarf
en su directorio. El único uso válido de tal construcción es cuandostring
es un globo.ACTUALIZACIÓN: No haga esto, debido a problemas con eval.
Con un poco menos de ceremonia:
p.ej
fuente
$
en su variable y verá ... Escribo muchos guiones y nunca tuve que usar un soloeval
Aquí está mi truco!
Dividir cadenas por cadenas es algo bastante aburrido de hacer con bash. Lo que sucede es que tenemos enfoques limitados que solo funcionan en algunos casos (divididos por ";", "/", "." Y así sucesivamente) o tenemos una variedad de efectos secundarios en los resultados.
El siguiente enfoque ha requerido una serie de maniobras, ¡pero creo que funcionará para la mayoría de nuestras necesidades!
fuente
Para elementos multilínea, ¿por qué no algo como
fuente
Otra forma sería:
Ahora sus elementos se almacenan en la matriz "arr". Para iterar a través de los elementos:
fuente
eval
truco). Su solución deja$IFS
el valor del espacio de coma después del hecho.Dado que hay muchas maneras de resolver esto, comencemos definiendo lo que queremos ver en nuestra solución.
readarray
para este propósito. Vamos a usarloIFS
, hacer bucles, usareval
o agregar un elemento adicional y luego eliminarlo.El
readarray
comando es más fácil de usar con líneas nuevas como delimitador. Con otros delimitadores, puede agregar un elemento adicional a la matriz. El enfoque más limpio es primero adaptar nuestra entrada a una forma que funcione bien conreadarray
antes de pasarla.La entrada en este ejemplo no tiene un delimitador de caracteres múltiples. Si aplicamos un poco de sentido común, se entiende mejor como entrada separada por comas para la cual cada elemento puede necesitar ser recortado. Mi solución es dividir la entrada por comas en varias líneas, recortar cada elemento y pasarlo todo
readarray
.fuente
Otro enfoque puede ser:
Después de esto, 'arr' es una matriz con cuatro cadenas. Esto no requiere tratar IFS o leer o cualquier otra cosa especial, por lo tanto, mucho más simple y directo.
fuente