Ordenar un archivo de texto por longitud de línea, incluidos espacios


137

Tengo un archivo CSV que se ve así

AS2345, ASDF1232, Mr. Plain Example, 110 Binary ave., Atlantis, RI, 12345, (999) 123-5555,1.56
AS2345, ASDF1232, Sra. Plain Example, 1121110 Ternary st. 110 Binary ave .., Atlantis, RI, 12345, (999) 123-5555,1.56
AS2345, ASDF1232, Mr. Plain Example, 110 Binary ave., Liberty City, RI, 12345, (999) 123-5555,1.56
AS2345, ASDF1232, Mr. 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 forma de modificarlo para que funcione para mí?

cat $@ | awk '{ print length, $0 }' | sort -n | awk '{$1=""; print $0}'

21
Realmente me gustaría vivir en la avenida binaria o ternaria de la calle, esas personas sin duda estaría de acuerdo con cosas como "8192 es un número redondo"
schnaader

Respuestas:


224

Responder

cat testfile | awk '{ print length, $0 }' | sort -n -s | cut -d" " -f2-

O, para hacer su subclasificació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 declarado alejándonos de awk para su corte final.

Líneas de longitud coincidente: qué hacer en caso de empate:

La pregunta no especificaba si se deseaba una clasificación adicional para líneas de longitud coincidente. Supuse que esto no es deseado y sugerí el uso de -s( --stable) para evitar que tales líneas se ordenen entre sí y mantenerlas en el orden relativo en el que ocurren en la entrada.

(Aquellos que quieran tener más control sobre la clasificación de estos lazos podrían considerar la --keyopción de clasificación ).

Por qué falla la solución intentada de la pregunta (reconstrucción de línea awk):

Es interesante notar la diferencia entre:

echo "hello   awk   world" | awk '{print}'
echo "hello   awk   world" | awk '{$1="hello"; print}'

Ceden respectivamente

hello   awk   world
hello awk world

La sección relevante del manual de (gawk) solo menciona como un aparte que awk reconstruirá la totalidad de $ 0 (basado en el separador, etc.) cuando cambie un campo. Supongo que no es un comportamiento loco. Tiene esto:

"Finalmente, hay momentos en los que es conveniente forzar a awk a reconstruir todo el registro, usando el valor actual de los campos y OFS. Para hacer esto, use la asignación aparentemente inocuo:"

 $1 = $1   # force record to be reconstituted
 print $0  # or whatever else with $0

"Esto obliga a awk a reconstruir el registro".

Entrada de prueba que incluye algunas líneas de igual longitud:

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

1
Heemayl, sí, gracias. Intenté igualar la forma del intento de solución de OP cuando fue posible, para permitirle enfocarse solo en diferencias importantes entre la suya y la mía.
neillb

1
Vale la pena señalar que también cat $@está roto. Definitivamente quieres citarlo, comocat "$@"
tripleee

27

La solución AWK de neillb es excelente si realmente quieres usarla awky explica por qué es una molestia allí, pero si lo que quieres es hacer el trabajo rápidamente y no importa lo que hagas, una solución es usar La sort()función de Perl con una rutina de capa personalizada para iterar sobre las líneas de entrada. Aquí hay una línea:

perl -e 'print sort { length($a) <=> length($b) } <>'

Puede poner esto en su tubería donde lo necesite, ya sea recibiendo STDIN (desde cato una redirección de shell) o simplemente dando el nombre de archivo a Perl como otro argumento y deje que abra el archivo.

En mi caso, primero necesitaba las líneas más largas, así que cambié $ay $ben la comparación.


Esta es una mejor solución porque awk causa una ordenación inesperada cuando el archivo de entrada contiene líneas numéricas y alfanuméricas Aquí el comando en línea: $ cat testfile | perl -e 'print sort {longitud ($ a) <=> longitud ($ b)} <>'
alemol

¡Rápido! Hizo un archivo de 465,000 líneas (una palabra por línea) en <1 segundo, cuando la salida fue redirigida a otro archivo - así:cat testfile.txt | perl -e 'print sort { length($a) <=> length($b) } <>' > out.txt
cssyphus

Windows con StrawberryPerl funciona:type testfile.txt | perl -e "print sort { length($a) <=> length($b) } <>" > out.txt
bryc

14

Pruebe este comando en su lugar:

awk '{print length, $0}' your-file | sort -n | cut -d " " -f2-

10

Resultados de referencia

A continuación, se muestran los resultados de un punto de referencia entre soluciones de otras respuestas a esta pregunta.

Método de prueba

  • 10 ejecuciones secuenciales en una máquina rápida, promediadas
  • Perl 5.24
  • awk 3.1.5 (gawk 4.1.0 veces fueron ~ 2% más rápido)
  • El archivo de entrada es una monstruosidad de 550 millones de líneas y 6 millones de líneas (British National Corpus txt)

Resultados

  1. La perlsolución de Caleb tomó 11.2 segundos.
  2. mi perlsolución tardó 11,6 segundos
  3. La awksolución n . ° 1 de neillb tardó 20 segundos
  4. La awksolución de neillb # 2 tomó 23 segundos
  5. La awksolución de Anubhava tomó 24 segundos
  6. La awksolución de Jonathan tomó 25 segundos
  7. La bashsolución de Fretz tarda 400 veces más que las awksoluciones (usando un caso de prueba truncado de 100000 líneas). Funciona bien, solo lleva una eternidad.

perlOpción extra

Además, he agregado otra solución Perl:

perl -ne 'push @a, $_; END{ print sort { length $a <=> length $b } @a }' file

6

Golpe puro:

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

3

La length()función incluye espacios. Haría ajustes menores en su canalización (incluso evitar UUOC ).

awk '{ printf "%d:%s\n", length($0), $0;}' "$@" | sort -n | sed 's/^[0-9]*://'

El sedcomando elimina directamente los dígitos y los dos puntos agregados por el awkcomando. Alternativamente, manteniendo su formato desde awk:

awk '{ print length($0), $0;}' "$@" | sort -n | sed 's/^[0-9]* //'

2

Encontré 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 dar sortel -gindicador (general-numeric-sort) en lugar de -n(numeric-sort):

awk '{ print length, $0 }' lines.txt | sort -g | cut -d" " -f2-

2
Hola markus No observo que el contenido de la línea (numérico o no), a diferencia de la longitud de la línea, tenga ningún efecto en la ordenación, excepto en el caso de líneas con longitudes coincidentes. ¿Es esto lo que quisiste decir? En tales casos, no encontré el cambio de los métodos de clasificación -na los sugeridos -gpara producir ninguna mejora, por lo que espero que no. Ya he abordado, en mi respuesta, cómo prohibir la subclasificación de líneas de igual longitud (usando --stable). Si eso fue lo que quisiste decir, ¡gracias por llamar mi atención! También he agregado una entrada considerada para probar.
neillb

44
No, déjame explicarte desglosándolo. Solo la awkparte generará una lista de líneas con el prefijo de longitud de línea y un espacio. Instalarlo sort -nfuncionará como se espera. Pero si alguna de esas líneas ya tiene un número al principio, esas líneas comenzarán con longitud + espacio + número. sort -nno tiene en cuenta ese espacio y lo tratará como un número concatenado de longitud + número. El uso de la -gbandera se detendrá en el primer espacio, produciendo una clasificación correcta. Pruébelo usted mismo creando un archivo con algunas líneas con prefijos numéricos y ejecute el comando paso a paso.
Markus Amalthea Magnuson el

1
También descubrí que sort -nignora el espacio y produce una clasificación incorrecta. sort -gda salida al orden correcto.
Robert Smith

No puedo reproducir el problema descrito con -nin sort (GNU coreutils) 8.21. La infodocumentación se describe -gcomo menos eficiente y potencialmente menos precisa (convierte números a flotantes), por lo que probablemente no la use si no es necesario.
Phil

nb documentación para -n: "Ordenar numéricamente. El número comienza cada línea y consta de espacios en blanco opcionales, un signo '-' opcional y cero o más dígitos posiblemente separados por miles de separadores, seguidos opcionalmente por un carácter de punto decimal y cero o más dígitos . Un número vacío se trata como '0'. La configuración regional 'LC_NUMERIC' especifica el carácter de punto decimal y el separador de miles. De forma predeterminada, un espacio en blanco es un espacio o una pestaña, pero la configuración regional 'LC_CTYPE' puede cambiar esto ".
Phil


2

1) solución pura de awk. Supongamos que la longitud de la línea no puede ser más de 1024

nombre de archivo del gato | awk 'BEGIN {min = 1024; s = "";} {l = longitud ($ 0); si (l <min) {min = l; s = $ 0;}} END {print s} '

2) una solución bash de línea, suponiendo que todas las líneas tengan solo 1 palabra, pero puede modificarse para cualquier caso en el que todas las líneas tengan el mismo número de palabras:

LÍNEAS = $ (nombre de archivo del gato); para k en $ LINES; hacer printf "$ k"; echo $ k | wc -L; hecho | ordenar -k2 | cabeza -n 1 | cortar -d "" -f1


1

Aquí hay un método compatible con multibyte para clasificar líneas por longitud. Requiere:

  1. wc -m está disponible para usted (macOS lo tiene).
  2. Su configuración regional actual admite caracteres de varios bytes, por ejemplo, configurando LC_ALL=UTF-8. Puede configurar esto en su .bash_profile, o simplemente colocándolo antes del siguiente comando.
  3. testfile tiene una codificación de caracteres que coincide con su entorno local (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);← hace una copia de cada línea en una variable awk ly escapa doble 'para que la línea pueda repetirse de forma segura como un comando de shell ( \047es 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 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 de shell para evitar alcanzar 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 que devuelve wc.
  • { print c, $0 } ← imprime el valor de recuento de caracteres de la línea, un espacio y la línea original.
  • | sort -ns← ordena las líneas (por valores de recuento de caracteres antepuestos) numéricamente ( -n) y mantiene un orden de clasificación estable ( -s).
  • | cut -d" " -f2- ← elimina los valores de recuento de caracteres antepuestos.

Es lento (solo 160 líneas por segundo en un Macbook Pro rápido) porque debe ejecutar un subcomando para cada línea.

Alternativamente, solo haga esto únicamente con gawk(a partir de la versión 3.1.5, gawk es multibyte), que sería significativamente más rápido. Es un gran problema hacer todo el escape y las comillas dobles para pasar las líneas de forma 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 de forma predeterminada en Mac OS).

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.