Realmente no necesitas tanto código:
IFS=$'\n' sorted=($(sort <<<"${array[*]}"))
unset IFS
Admite espacios en blanco en los elementos (siempre que no sea una nueva línea) y funciona en Bash 3.x.
p.ej:
$ array=("a c" b f "3 5")
$ IFS=$'\n' sorted=($(sort <<<"${array[*]}")); unset IFS
$ printf "[%s]\n" "${sorted[@]}"
[3 5]
[a c]
[b]
[f]
Nota: @sorontar ha señalado que se requiere cuidado si los elementos contienen comodines como *
o ?
:
La parte sorted = ($ (...)) está utilizando el operador "split and glob". Debe desactivar pegote: set -f
o set -o noglob
o shopt -op noglob
o un elemento de la matriz como *
se ampliará a una lista de archivos.
Qué esta pasando:
El resultado es una culminación de seis cosas que suceden en este orden:
IFS=$'\n'
"${array[*]}"
<<<
sort
sorted=($(...))
unset IFS
Primero el IFS=$'\n'
Esta es una parte importante de nuestra operación que afecta el resultado de 2 y 5 de la siguiente manera:
Dado:
"${array[*]}"
se expande a cada elemento delimitado por el primer carácter de IFS
sorted=()
crea elementos dividiéndose en cada personaje de IFS
IFS=$'\n'
configura las cosas para que los elementos se expandan usando una nueva línea como delimitador, y luego se crean de manera que cada línea se convierta en un elemento. (es decir, dividir en una nueva línea).
La delimitación por una nueva línea es importante porque así es como sort
funciona (ordenando por línea). Dividir solo una nueva línea no es tan importante, pero es necesario preservar los elementos que contienen espacios o pestañas.
El valor predeterminado de IFS
es un espacio , una pestaña , seguido de una nueva línea , y no sería adecuado para nuestra operación.
Luego, la sort <<<"${array[*]}"
parte
<<<
, llamado aquí cadenas , toma la expansión de "${array[*]}"
, como se explicó anteriormente, y la introduce en la entrada estándar de sort
.
Con nuestro ejemplo, sort
se alimenta esta siguiente cadena:
a c
b
f
3 5
Como sort
tipo , produce:
3 5
a c
b
f
Luego, la sorted=($(...))
parte
La $(...)
parte, llamada sustitución de comando , hace que su contenido ( sort <<<"${array[*]}
) se ejecute como un comando normal, mientras toma la salida estándar resultante como el literal que va a donde sea que haya estado $(...)
.
En nuestro ejemplo, esto produce algo similar a simplemente escribir:
sorted=(3 5
a c
b
f
)
sorted
luego se convierte en una matriz que se crea dividiendo este literal en cada nueva línea.
Finalmente, el unset IFS
Esto restablece el valor de IFS
al valor predeterminado, y es solo una buena práctica.
Es para garantizar que no causamos problemas con nada de lo que dependa IFS
más adelante en nuestro script. (De lo contrario, tendríamos que recordar que hemos cambiado las cosas, algo que podría no ser práctico para scripts complejos).
IFS
, dividirá tus elementos en pedazos pequeños si tienen espacios en blanco. Pruebe el eg conIFS=$'\n'
omitido y vea!IFS
, divide sus elementos en pequeños pedazos si tienen solo un tipo particular de espacio en blanco. Bueno; no perfecto :-)unset IFS
necesario? Pensé que anteponerIFS=
a un comando definía el cambio solo a ese comando, volviendo a su valor anterior automáticamente después.sorted=()
no es un comando sino una segunda asignación de variable.Respuesta original:
salida:
Tenga en cuenta que esta versión hace frente a valores que contienen caracteres especiales o espacios en blanco ( excepto las nuevas líneas)
Nota readarray es compatible con bash 4+.
Editar Según la sugerencia de @Dimitre, lo actualicé a:
que tiene la ventaja de comprender incluso los elementos de clasificación con caracteres de nueva línea incrustados correctamente. Desafortunadamente, como lo indicó correctamente @ruakh, esto no significaba que el resultado
readarray
sería correcto , ya quereadarray
no tiene la opción de usar enNUL
lugar de líneas nuevas regulares como separadores de línea.fuente
readarray -t sorted < <(printf '%s\n' "${array[@]}" | sort)
sort -z
es una mejora útil, supongo que la-z
opción es una extensión de tipo GNU.sorted=(); while read -d $'\0' elem; do sorted[${#sorted[@]}]=$elem; done < <(printf '%s\0' "${array[@]}" | sort -z)
. Esto también funciona si está utilizando bash v3 en lugar de bash v4, porque readarray no está disponible en bash v3.<
) combinada con la sustitución del proceso<(...)
. O para decirlo intuitivamente: porque(printf "bla")
no es un archivo.Aquí hay una implementación de clasificación rápida de Bash pura:
Usar como, por ejemplo,
Esta implementación es recursiva ... así que aquí hay un resumen rápido iterativo:
En ambos casos, puede cambiar el orden que usa: utilicé comparaciones de cadenas, pero puede usar comparaciones aritméticas, comparar el tiempo de modificación del archivo wrt, etc. simplemente use la prueba adecuada; incluso puede hacerlo más genérico y hacer que use un primer argumento que es el uso de la función de prueba, por ejemplo,
Entonces puede tener esta función de comparación:
y use:
ordenar los archivos de la carpeta actual por hora de modificación (los más nuevos primero).
NOTA. Estas funciones son puro Bash! sin utilidades externas, y sin subcapas! son seguros con cualquier símbolo divertido que pueda tener (espacios, caracteres de nueva línea, caracteres globales, etc.).
fuente
sort
ofrece es suficiente, una soluciónsort
+read -a
será más rápida a partir de alrededor de, por ejemplo, 20 elementos, y cada vez más y significativamente más rápida cuanto más elementos esté tratando. Por ejemplo, en mi iMac de finales de 2012 con OSX 10.11.1 con una unidad Fusion: matriz de 100 elementos: ca. 0.03s segundos (qsort()
) vs. ca. 0.005 segundos (sort
+read -a
); Matriz de 1000 elementos: ca. 0.375 segundos (qsort()
) vs. ca. 0,014 segundos (sort
+read -a
).if [ "$i" -lt "$pivot" ]; then
era necesario, de lo contrario, el "2" <"10" resuelto devuelve verdadero. Creo que esto es POSIX vs. Lexicográfico; o tal vez Enlace en línea .Si no necesita manejar caracteres especiales de shell en los elementos de la matriz:
Con bash necesitarás un programa de clasificación externo de todos modos.
Con zsh no se necesitan programas externos y los caracteres especiales de shell se manejan fácilmente:
ksh tiene
set -s
que ordenar ASCIIbetically .fuente
set -A array x 'a a' d; set -s -- "${array[@]}"; set -A sorted "$@"
y, por supuesto, el comando set restablecerá los parámetros posicionales actuales, si los hay.tl; dr :
Clasifique la matriz
a_in
y almacene el resultado ena_out
(los elementos no deben tener nuevas líneas incrustadas [1] ):Bash v4 +:
Bash v3:
Ventajas sobre la solución de antak :
No debe preocuparse por el bloqueo accidental (interpretación accidental de los elementos de la matriz como patrones de nombre de archivo), por lo que no se necesita ningún comando adicional para desactivar el bloqueo (
set -f
yset +f
restaurarlo más adelante).No necesita preocuparse por restablecer
IFS
conunset IFS
. [2]Lectura opcional: explicación y código de muestra
Lo anterior combina el código Bash con una utilidad externa
sort
para una solución que funciona con elementos arbitrarios de una sola línea y ordenación léxica o numérica (opcionalmente por campo) :Rendimiento : para alrededor de 20 elementos o más , esto será más rápido que una solución Bash pura , de manera significativa y creciente una vez que supere los 100 elementos.
(Los umbrales exactos dependerán de su entrada, máquina y plataforma específicas).
printf '%s\n' "${a_in[@]}" | sort
realiza la clasificación (léxicamente, de forma predeterminada; consultesort
la especificación POSIX ):"${a_in[@]}"
se expande de forma segura a los elementos de la matriza_in
como argumentos individuales , independientemente de lo que contengan (incluido el espacio en blanco).printf '%s\n'
luego imprime cada argumento, es decir, cada elemento de la matriz, en su propia línea, tal cual.Tenga en cuenta el uso de una sustitución de proceso (
<(...)
) para proporcionar la salida ordenada como entrada aread
/readarray
(a través de la redirección a stdin,<
), porqueread
/readarray
debe ejecutarse en el shell actual (no debe ejecutarse en un subshell ) para que la variable de salidaa_out
sea visible al shell actual (para que la variable permanezca definida en el resto del script).Lectura
sort
de la salida en una variable de matriz :Bash v4 +:
readarray -t a_out
lee las líneas individuales generadas porsort
los elementos de la variable de matriza_out
, sin incluir el final\n
de cada elemento (-t
).Bash v3:
readarray
no existe, por lo queread
debe usarse:IFS=$'\n' read -d '' -r -a a_out
le diceread
que lea en la-a
variable array ( )a_out
, lea toda la entrada, a través de las líneas (-d ''
), pero dividiéndola en elementos de la matriz por nuevas líneas (IFS=$'\n'
.$'\n'
, Que produce una nueva línea literal (LF) ), es una cadena llamada ANSI C entre comillas ).(
-r
, una opción con la que prácticamente siempre debe usarseread
, desactiva el manejo inesperado de\
caracteres).Código de muestra anotado:
Debido al uso de
sort
sin opciones, esto produce una clasificación léxica (los dígitos se ordenan antes que las letras y las secuencias de dígitos se tratan léxicamente, no como números):Si querías ordenar numéricamente por el primer campo, usarías en
sort -k1,1n
lugar de solosort
, lo que produce (los números no se ordenan antes que los números y los números se ordenan correctamente):[1] Para manejar elementos con líneas nuevas incrustadas, use la siguiente variante (Bash v4 +, con GNU
sort
):readarray -d '' -t a_out < <(printf '%s\0' "${a_in[@]}" | sort -z)
.La útil respuesta de Michał Górny tiene una solución Bash v3.
[2] Mientras
IFS
se establece en la variante Bash v3, el cambio se limita al comando .Por el contrario, lo que sigue
IFS=$'\n'
en la respuesta de antak es una asignación en lugar de un comando, en cuyo caso elIFS
cambio es global .fuente
En el viaje en tren de 3 horas de Múnich a Frankfurt (que tuve problemas para alcanzar porque el Oktoberfest comienza mañana) estaba pensando en mi primer puesto. Emplear una matriz global es una idea mucho mejor para una función de clasificación general. La siguiente función maneja cadenas arbitrarias (líneas nuevas, espacios en blanco, etc.):
Esto imprime:
La misma salida se crea a partir de
Tenga en cuenta que probablemente Bash utiliza internamente punteros inteligentes, por lo que la operación de intercambio podría ser barata (aunque lo dudo). Sin embargo,
bubble_sort
demuestra que funciones más avanzadas comomerge_sort
también están al alcance del lenguaje shell.fuente
local -n BSORT="$1"
al comienzo de la función. Entonces puedes correrbubble_sort myarray
para ordenar myarray .Otra solución que usa externos
sort
y hace frente a cualquier carácter especial (excepto NULs :)). Debería funcionar con bash-3.2 y GNU o BSDsort
(lamentablemente, POSIX no incluye-z
).Primero mire la redirección de entrada al final. Estamos utilizando la función
printf
integrada para escribir los elementos de la matriz, con terminación cero. La cita se asegura de que los elementos de la matriz se pasen como están, y los detalles del shellprintf
hacen que reutilice la última parte de la cadena de formato para cada parámetro restante. Es decir, es equivalente a algo como:La lista de elementos terminados en nulo se pasa a
sort
. La-z
opción hace que lea elementos terminados en nulo, los ordene y genere también resultados terminados en nulo. Si necesita obtener solo los elementos únicos, puede pasar-u
ya que es más portátil queuniq -z
. EstoLC_ALL=C
garantiza un orden de clasificación estable independientemente de la configuración regional, a veces útil para los scripts. Si quieres elsort
respetar la configuración regional, elimínela.La
<()
construcción obtiene el descriptor para leer de la tubería generada y<
redirige la entrada estándar delwhile
bucle hacia ella. Si necesita acceder a la entrada estándar dentro de la tubería, puede usar otro descriptor: ejercicio para el lector :).Ahora, de vuelta al principio. La
read
salida de lectura incorporada del stdin redirigido. Establecer vacíoIFS
desactiva la división de palabras, lo cual es innecesario aquí: como resultado, seread
lee toda la 'línea' de entrada a la variable proporcionada.-r
la opción deshabilita el procesamiento de escape que no es deseado aquí también Finalmente,-d ''
establece el delimitador de línea en NUL, es decir, le diceread
que lea cadenas terminadas en cero.Como resultado, el bucle se ejecuta una vez por cada elemento de matriz terminado en cero sucesivamente, con el valor almacenado en
e
. El ejemplo solo coloca los elementos en otra matriz, pero es posible que prefiera procesarlos directamente :).Por supuesto, esa es solo una de las muchas formas de lograr el mismo objetivo. A mi entender, es más simple que implementar un algoritmo de clasificación completo en bash y, en algunos casos, será más rápido. Maneja todos los caracteres especiales, incluidas las nuevas líneas, y debería funcionar en la mayoría de los sistemas comunes. Lo más importante es que puede enseñarte algo nuevo e increíble sobre bash :).
fuente
e
y establecer un IFS vacío, use la variable REPLY.prueba esto:
La salida será:
Problema resuelto.
fuente
Si puede calcular un número entero único para cada elemento de la matriz, así:
luego, puede usar estos enteros como índices de matriz, porque Bash siempre usa una matriz dispersa, por lo que no debe preocuparse por los índices no utilizados:
fuente
tipo min:
fuente
Los contenidos de eco de new_array serán:
fuente
Hay una solución para el problema habitual de espacios y líneas nuevas:
Use un carácter que no esté en la matriz original (como
$'\1'
o$'\4'
o similar).Esta función hace el trabajo:
Esto ordenará la matriz:
Esto se quejará de que la matriz fuente contiene el carácter de solución alternativa:
descripción
wa
(char alternativa) y un IFS nulo$*
.[[ $* =~ [$wa] ]]
.exit 1
set -f
IFS=$'\n'
), una variable de buclex
y una nueva línea var (nl=$'\n'
).$@
)."${@//$nl/$wa}"
.sort -n
.set --
.for x
sorted+=(…)
"${x//$wa/$nl}"
.fuente
Esta pregunta parece estar muy relacionada. Y por cierto, aquí hay un mergesort en Bash (sin procesos externos):
fuente
No estoy convencido de que necesite un programa de clasificación externo en Bash.
Aquí está mi implementación para el algoritmo simple de clasificación de burbujas.
Esto imprimirá:
fuente
O(n^2)
. Me parece recordar que la mayoría de los algoritmos de clasificación utilizanO(n lg(n))
hasta la última docena de elementos más o menos. Para los elementos finales, se utiliza el orden de selección.fuente
sorted=($(echo ${array[@]} | tr " " "\n" | sort))
En el espíritu de bash / linux, canalizaría la mejor herramienta de línea de comandos para cada paso.
sort
realiza el trabajo principal pero necesita una entrada separada por una nueva línea en lugar de espacio, por lo que la simple tubería de arriba simplemente hace:Contenido de matriz de eco -> reemplazar espacio por nueva línea -> ordenar
$()
es hacer eco del resultado($())
es poner el "resultado repetido" en una matrizNota : como @sorontar mencionó en un comentario a una pregunta diferente:
fuente
mapfile -t sorted < <(printf '%s\n' "${array[@]}" | sort)
contrariosorted=(); while IFS= read -r line; do sorted+=( "$line" ); done < <(printf '%s\n' | sort)
.echo ${array[@]} | tr " " "\n"
esto se romperá si los campos de la matriz contienen espacios en blanco y caracteres globales. Además, genera una subshell y usa un comando externo inútil. Y debido aecho
ser tonto, se romperá si su matriz comienza con-e
,-E
o-n
. En lugar de utilizar:printf '%s\n' "${array[@]}"
. El otro antipatrón es:($())
es poner el "resultado repetido" en una matriz . ¡Ciertamente no! Este es un antipatrón horrible que se rompe debido a la expansión del nombre de ruta (globalización) y la división de palabras. Nunca uses este horror.