Tengo un archivo CSV que se parece a esto
AS2345, ASDF1232, Sr. Plain Example, 110 Binary ave., Atlantis, RI, 12345, (999) 123-5555,1.56 AS2345, ASDF1232, Mrs. Plain Example, 1121110 Ternary st. 110 Binary ave .., Atlantis, RI, 12345, (999) 123-5555,1.56 AS2345, ASDF1232, Sr. Plain Example, 110 Binary ave., Liberty City, RI, 12345, (999) 123-5555,1.56 AS2345, ASDF1232, Sr. Plain Example, 110 Ternary ave., Some City, RI, 12345, (999) 123-5555,1.56
Necesito ordenarlo por longitud de línea, incluyendo espacios. El siguiente comando no incluye espacios, ¿hay alguna manera de modificarlo para que funcione para mí?
cat $@ | awk '{ print length, $0 }' | sort -n | awk '{$1=""; print $0}'
cat testfile | awk '{ print length, $0 }' | sort -n -s | cut -d" " -f2-
O bien, para hacer su subordenación original (quizás no intencional) de cualquier línea de igual longitud:
cat testfile | awk '{ print length, $0 }' | sort -n | cut -d" " -f2-
En ambos casos, hemos resuelto su problema establecido alejándonos de awk para su corte final.
La pregunta no especificaba si se deseaba o no una clasificación adicional para las líneas de longitud coincidente. Supuse que esto no era deseado y sugerí el uso de -s
( --stable
) para evitar que esas líneas se --stable
entre sí, y mantenerlas en el orden relativo en que ocurren en la entrada.
(Aquellos que quieran tener más control para clasificar estos vínculos pueden mirar la opción de ordenar --key
).
Es interesante notar la diferencia entre:
echo "hello awk world" | awk '{print}' echo "hello awk world" | awk '{$1="hello"; print}'
Ellos ceden respectivamente
hello awk world hello awk world
La sección relevante del manual de (gawk) solo menciona como un lado que awk va a reconstruir el total de $ 0 (basado en el separador, etc.) cuando cambia un campo. Supongo que no es un comportamiento loco. Tiene esto:
“Finalmente, hay momentos en los que es conveniente forzar awk para reconstruir todo el registro, usando el valor actual de los campos y OFS. Para hacer esto, use la asignación aparentemente inocua:”
$1 = $1 # force record to be reconstituted print $0 # or whatever else with $0
“Esto obliga a awk a reconstruir el récord”.
aa A line with MORE spaces bb The very longest line in the file ccb 9 dd equal len. Orig pos = 1 500 dd equal len. Orig pos = 2 ccz cca ee A line with some spaces 1 dd equal len. Orig pos = 3 ff 5 dd equal len. Orig pos = 4 g
Pruebe este comando en su lugar:
awk '{print length, $0}' your-file | sort -n | cut -d " " -f2-
La solución AWK de neillb es genial si realmente quieres usar awk
y explica por qué es una molestia allí, pero si lo que quieres es hacer el trabajo rápidamente y no te importa en qué lo hagas, una solución es utilice la función sort()
Perl con una rutina de caparison personalizada para iterar sobre las líneas de entrada. Aquí hay un trazador de líneas:
perl -e 'print sort { length($a) <=> length($b) } <>'
Puedes poner esto en tu canalización donde sea que lo necesites, ya sea recibiendo STDIN (de cat
o un redireccionamiento de shell) o simplemente dale el nombre de archivo a perl como otro argumento y deja que abra el archivo.
En mi caso, yo necesitaba las líneas más largas primero, así que cambié $a
y $b
en la comparación.
Pure Bash:
declare -a sorted while read line; do if [ -z "${sorted[${#line}]}" ] ; then # does line length already exist? sorted[${#line}]="$line" # element for new length else sorted[${#line}]="${sorted[${#line}]}\n$line" # append to lines with equal length fi done < data.csv for key in ${!sorted[*]}; do # iterate over existing indices echo -e "${sorted[$key]}" # echo lines with equal length done
La función length()
incluye espacios. Haría solo pequeños ajustes en tu canalización (incluido evitar UUOC ).
awk '{ printf "%d:%s\n", length($0), $0;}' "$@" | sort -n | sed 's/^[0-9]*://'
El comando sed
elimina directamente los dígitos y los puntos añadidos por el comando awk
. Alternativamente, manteniendo el formato de awk
:
awk '{ print length($0), $0;}' "$@" | sort -n | sed 's/^[0-9]* //'
Descubrí que estas soluciones no funcionarán si su archivo contiene líneas que comienzan con un número, ya que se ordenarán numéricamente junto con todas las líneas contadas. La solución es sort
el indicador -g
(tipo-general-numérico) en lugar de -n
(tipo-numérico):
awk '{ print length, $0 }' lines.txt | sort -g | cut -d" " -f2-
Con POSIX Awk:
{ c = length m[c] = m[c] ? m[c] RS $0 : $0 } END { for (c in m) print m[c] }
Ejemplo
Aquí hay un método compatible con multibyte para clasificar líneas por longitud. Requiere:
wc -m
está disponible para usted (macOS lo tiene). LC_ALL=UTF-8
. Puede configurar esto en su .bash_profile, o simplemente anteponiéndolo antes del siguiente comando. testfile
tiene una encoding de caracteres que coincide con su configuración regional (por ejemplo, UTF-8). Aquí está el comando completo:
cat testfile | awk '{l=$0; gsub(/\047/, "\047\"\047\"\047", l); cmd=sprintf("echo \047%s\047 | wc -m", l); cmd | getline c; close(cmd); sub(/ */, "", c); { print c, $0 }}' | sort -ns | cut -d" " -f2-
Explicando parte por parte:
l=$0; gsub(/\047/, "\047\"\047\"\047", l);
← crea una copia de cada línea en awk variable l
y double-escapes cada '
para que la línea se pueda repetir con seguridad como un comando de shell ( \047
es una comilla simple en notación octal). cmd=sprintf("echo \047%s\047 | wc -m", l);
← Este es el comando que ejecutaremos, que hace eco de la línea escapada a wc -m
. cmd | getline c;
← ejecuta el comando y copia el valor de recuento de caracteres que se devuelve a la variable awk c
. close(cmd);
← cierre la tubería al comando shell para evitar golpear un límite del sistema en la cantidad de archivos abiertos en un proceso. sub(/ */, "", c);
← recorta el espacio en blanco del valor de recuento de caracteres devuelto por wc
. { print c, $0 }
← imprime el valor del recuento de caracteres de la línea, un espacio y la línea original. | sort -ns
| sort -ns
← ordena las líneas (por valores de recuento de caracteres antepuestos) numéricamente ( -n
) y mantiene el orden de clasificación estable ( -s
). | cut -d" " -f2-
| cut -d" " -f2-
← elimina los valores de recuento de caracteres | cut -d" " -f2-
. Es lento (solo 160 líneas por segundo en un Macbook Pro rápido) porque debe ejecutar un subcomando para cada línea.
Alternativamente, haz esto solo con gawk
(a partir de la versión 3.1.5, gawk es multibyte aware), que sería significativamente más rápido. Es un montón de problemas hacer todos los escapes y las comillas dobles para pasar las líneas de manera segura a través de un comando de shell desde awk, pero este es el único método que pude encontrar que no requiere la instalación de software adicional (gawk no está disponible por defecto en Mac OS).
Resultados de referencia
Además, otra solución de Perl:
perl -ne 'push @a, $_; END{ print sort { length $a <=> length $b } @a }' file
Los experimentos se realizaron usando:
Resultados: