Cómo encontrar la línea con menos caracteres


22

Estoy escribiendo un script de shell, usando cualquier comando general de UNIX. Tengo que recuperar la línea que tiene menos caracteres (espacios en blanco incluidos). Puede haber hasta alrededor de 20 líneas.

Sé que puedo usar head -$L | tail -1 | wc -mpara encontrar el recuento de caracteres de la línea L. El problema es que el único método en el que puedo pensar, usando eso, sería escribir manualmente un desorden de declaraciones if, comparando los valores.

Datos de ejemplo:

seven/7
4for
8 eight?
five!

Volvería 4forya que esa línea tenía la menor cantidad de caracteres.

En mi caso, si varias líneas tienen la longitud más corta, se debe devolver una sola. No importa cuál se seleccione, siempre que sea de la longitud mínima. Pero no veo el daño en mostrar ambos sentidos para otros usuarios con otras situaciones.


55
¿Qué pasa si hay varias líneas con una longitud de 4? ¿Deberían imprimirse también?
caos

En mi caso, si varias líneas tienen la longitud más corta, se debe devolver una sola. No importa cuál se seleccione, siempre que sea de la longitud mínima. Pero no veo el daño en mostrar ambos sentidos para otros usuarios con otras situaciones.
Matthew D. Scholefield

Respuestas:


13

A la manera de Perl. Tenga en cuenta que si hay muchas líneas de la misma longitud más corta, este enfoque solo imprimirá una de ellas:

perl -lne '$m//=$_; $m=$_ if length()<length($m); END{print $m if $.}' file 

Explicación

  • perl -lne: -nsignifica "leer el archivo de entrada línea por línea", -lhace que se eliminen las nuevas líneas finales de cada línea de entrada y se agregue una nueva línea a cada printllamada; y -ees el script que se aplicará a cada línea.
  • $m//=$_: establecido $men la línea actual ( $_) a menos que $mesté definido. El //=operador está disponible desde Perl 5.10.0.
  • $m=$_ if length()<length($m): si la longitud del valor actual de $mes mayor que la longitud de la línea actual, guarde la línea actual ( $_) como $m.
  • END{print $m if $.}: una vez que se han procesado todas las líneas, imprima el valor actual de $mla línea más corta. Esto if $.asegura que esto solo suceda cuando el número de línea ( $.) esté definido, evitando imprimir una línea vacía para la entrada en blanco.

Alternativamente, dado que su archivo es lo suficientemente pequeño como para caber en la memoria, puede hacer lo siguiente:

perl -e '@K=sort{length($a) <=> length($b)}<>; print "$K[0]"' file 

Explicación

  • @K=sort{length($a) <=> length($b)}<>: <>aquí hay una matriz cuyos elementos son las líneas del archivo. Los sortordenará según su longitud y las líneas ordenadas se guardarán como matriz @K.
  • print "$K[0]": imprime el primer elemento de la matriz @K: la línea más corta.

Si desea imprimir todas las líneas más cortas, puede usar

perl -e '@K=sort{length($a) <=> length($b)}<>; 
         print grep {length($_)==length($K[0])}@K; ' file 

1
Agregue -Cpara medir la longitud en términos de número de caracteres en lugar de número de bytes. En un entorno local UTF-8, $$tiene menos bytes que (2 contra 3), pero más caracteres (2 contra 1).
Stéphane Chazelas

17

Con sqlite3:

sqlite3 <<EOT
CREATE TABLE file(line);
.import "data.txt" file
SELECT line FROM file ORDER BY length(line) LIMIT 1;
EOT

Ese es mi favorito aquí, nunca pensé en SQL ...
caos

2
Este es el estado del código de golf inteligente
shadowtalker

2
¿Esto leerá todo el archivo en la memoria y / o creará una segunda copia en el disco? Si es así, es inteligente pero ineficiente.
John Kugelman apoya a Monica el

1
@JohnKugelman Esto probablemente absorberá las 4 líneas completas en una base de datos solo de memoria temporal (eso es lo que straceindica). Si necesita trabajar con archivos realmente grandes (y su sistema no se intercambia), puede forzarlo simplemente agregando un nombre de archivo como sqlite3 $(mktemp)y todos los datos se escribirán en el disco.
FloHelf

Recibo los siguientes errores: "" "xaa: 8146:" carácter "" "y" "" xaa: 8825: se esperaba 1 columnas, pero se encontraron 2 - extras ignorados ". El archivo consta de documentos json 1 por cada línea .
Ahmedov

17

Aquí hay una variante de una awksolución para imprimir la primera línea mínima encontrada:

awk '
  NR==1 || length<len {len=length; line=$0}
  END {print line}
'

que simplemente se puede extender una condición para imprimir todas las líneas mínimas:

awk '
  length==len {line=line ORS $0}
  NR==1 || length<len {len=length; line=$0}
  END {print line}'
'

12

Python sale bastante conciso, y el código hace lo que dice en la lata:

python -c "import sys; print min(sys.stdin, key=len),"

La coma final es oscura, lo admito. Impide que la declaración de impresión agregue un salto de línea adicional. Además, puede escribir esto en Python 3 que admite 0 líneas como:

python3 -c "import sys; print(min(sys.stdin, key=len, default='').strip('\n'))"


¿Qué dice la lata?
mikeserv

@mikeserve: dice, "imprime el mínimo de sys.stdin, usando len como clave" ;-)
Steve Jessop

1
ahh ¿Nada sobre el tamaño binario, la dependencia o el tiempo de ejecución?
mikeserv

2
@mikeserv: no, la letra pequeña no está en la lata. Está en un folleto informativo en un archivador cerrado, en un sótano, detrás de una puerta marcada "cuidado con el leopardo".
Steve Jessop

Gotcha, así que en exhibición.
mikeserv

10

Siempre me encantan las soluciones con scripting de shell puro (¡no exec!).

#!/bin/bash
min=
is_empty_input="yes"

while IFS= read -r a; do
    if [ -z "$min" -a "$is_empty_input" = "yes" ] || [ "${#a}" -lt "${#min}" ]; then
        min="$a"
    fi
    is_empty_input="no"
done

if [ -n "$a" ]; then
    if [ "$is_empty_input" = "yes" ]; then
        min="$a"
        is_empty_input="no"
    else
        [ "${#a}" -lt "${#min}" ] && min="$a"
    fi
fi

[ "$is_empty_input" = "no" ] && printf '%s\n' "$min"

Nota :

Hay un problema con los bytes NUL en la entrada. Entonces, printf "ab\0\0\ncd\n" | bash this_scriptimpresiones en ablugar de cd.


Esto realmente es lo más puro. Aunque, la torpeza de las pruebas en bashme convencería de canalizar un resultado intermedio en su sortlugar.
orion

2
¿Has intentado hacer una banca con tu no ejecutivo? solución frente a otros que lo hacen? ¡Aquí hay una comparación de las diferencias de rendimiento entre exec! y no ejecutivo! soluciones para un problema similar. Ejecutar un proceso separado rara vez es ventajoso cuando las arañas, en formas como var=$(get data)porque restringe el flujo de datos a un solo contexto, pero cuando mueve los datos a través de una tubería, en una secuencia, cada ejecutivo aplicado es generalmente útil, porque permite aplicación de programas modulares solo cuando sea necesario.
mikeserv

1
@DigitalTrauma: una cadena de dígitos contigua expandida no está más o menos exenta de las condiciones que hacen que las comillas sean necesarias que cualquier otra cadena expandida. $IFSno es discriminatorio por dígitos, incluso si no hay ninguno en un $IFSvalor predeterminado , aunque muchos shells aceptarán una configuración de entorno preestablecida para $IFS, por lo que no es un valor predeterminado particularmente confiable.
mikeserv


1
Gracias a todos por los comentarios y las votaciones (algunos de los representantes deben ir a @cuonglm para corregir mi respuesta). En general, no recomiendo a otros que practiquen diariamente secuencias de comandos de shell puro, pero esa habilidad se puede encontrar muy útil en algunas condiciones extremas donde no hay nada más que enlaces estáticos /bin/shdisponibles. Me ha sucedido varias veces con hosts SunOS4 con /usrpérdida o algunos .sodaños, y ahora en la era moderna de Linux todavía encuentro situaciones similares con sistemas embebidos o sistemas de falla de arranque. BusyBox es una de las grandes cosas que adquirimos recientemente.
yaegashi

9

Aquí una zshsolución pura (imprime todas las líneas con la longitud mínima, desde file):

IFS=$'\n'; print -l ${(M)$(<file):#${~${(o@)$(<file)//?/?}[1]}}

Entrada de ejemplo:

seven/7
4for
8 eight?
five!
four

Salida es:

4for
four

Creo que necesita una breve explicación :-)


Primero, establecemos el separador de campo interno en nueva línea:

IFS=$'\n';

Hasta ahora todo bien, ahora la parte difícil. printusa la -lbandera para imprimir el resultado separado por nuevas líneas en lugar de espacios.

Ahora, comenzamos en el interior:

$(<file)

El archivo se lee línea por línea y se trata como una matriz. Luego:

${(o@)...//?/?}

La obandera dice que el resultado debe ordenarse en orden ascendente, el @medio para tratar el resultado como una matriz también. La parte detrás de ( //?/?) es una sustitución y reemplaza todos los caracteres con a ?. Ahora:

${~...[1]}

Tomamos el primer elemento de matriz [1], que es el más corto, en su caso es ahora ????.

${(M)$(<file):#...}

La coincidencia se realiza en cada elemento de matriz por separado, y los elementos de matriz no coincidentes se eliminan ( M). Cada elemento que coincide ????(4 caracteres) permanece en la matriz. Entonces, los elementos restantes son los que tienen 4 caracteres (los más cortos).

Editar: si solo necesita una de las líneas más cortas, esta versión modificada imprime la primera:

IFS=$'\n'; print -l ${${(M)$(<file):#${~${(o@)$(<file)//?/?}[1]}}[1]}

8
tr -c \\n 1 <testfile |   #first transform every [^\n] char to a 1
grep -nF ''           |   #next get line numbers
paste -d: - testfile  |   #then paste it together with itself
sort  -t: -nk2,2          #then sort on second field

... y el ganador es ... la línea 2, parece.

2:1111:4for
4:11111:five!
1:1111111:seven/7
3:11111111:8 eight?

Pero el problema con eso es que cada línea debe tener más del doble de longitud para que funcione, por lo que LINE_MAX se reduce a la mitad. La causa es que está utilizando, ¿qué, una base 1? - para representar la longitud de la línea. Un enfoque similar, y quizás más ordenado, podría ser comprimir esa información en la secuencia. La primera idea en ese sentido que se me ocurre es que debería unexpandhacerlo:

tr -c \\n \  <testfile    |   #transform all [^\n] to <space>
unexpand -t10             |   #squeeze every series of 10 to one tab
grep -nF ''               |   #and get the line numbers
sed    's/:/!d;=;:/;h;:big    #sed compares sequential lines
$P;$!N; /\(:[^ ]*\)\( *\)\n.*\1.*\2/!D     #newest line is shorter or...
        g;/:./!q;b big'   |   #not; quit input entirely for blank line
sed -f - -e q testfile        #print only first occurrence of shortest line

Eso imprime ...

2
4for

Otro, solo sed:

sed -n '/^\n/D;s/\(.\)\(\n.*\)*/\1/g
$p;h;   s// /g;G;x;n;//!g;H;s// /g
G;      s/^\( *\)\(\n \1 *\)\{0,1\}\n//
D'      <infile >outfile

La sintaxis cumple con los estándares, pero eso no garantiza que haya sed maneje \(reference-group\)\{counts\}correctamente, muchos no lo hacen.

Básicamente, aplica la misma expresión regular a la entrada repetidamente, lo que puede ser muy beneficioso cuando es hora de compilarlos. Ese patrón es:

\(.\)\(\n.*\)*

Que combina diferentes cadenas de diferentes maneras. Por ejemplo:

string1\nstring2\nstring3

... coincide con sin \1y ''la cadena nula en \2.

1\nstring2\nstring3

... coincide con 1in \1y \nstring2\nstring3in\2

\nstring2\nstring3

... coincide con \nin \1y ''la cadena nula en \2. Esto sería problemático si hubiera alguna posibilidad de que se \nproduzca una línea en la cabecera del espacio del patrón, pero los comandos /^\n/D, y //!gse utilizan para evitar esto. Utilicé [^\n]pero otras necesidades para este pequeño script hicieron que la portabilidad fuera una preocupación y no estaba satisfecho con las muchas formas en que a menudo se malinterpreta. Además, .es más rápido.

\nstring2
string1

... coinciden \ny snuevamente \1y ambos obtienen la ''cadena nula \2. Las líneas vacías no coinciden en absoluto.

Cuando el patrón se aplica de forma globular, los dos sesgos, tanto el sesgo estándar más a la izquierda como el sesgo menor a la derecha del lado \nderecho, se contrarrestan para efectuar un salto. Algunos ejemplos:

s/\(.\)\(\n.*\)*/\1:\2/g
s/\(.\)\(\n.*\)*/\2\1:/g
s/\(.\)\(\n.*\)*/\1: /g
s/\(.\)\(\n.*\)*/ :\2/g

... si todo se aplica (no en sucesión) a la siguiente cadena ...

string1\nstring2

... lo transformará a ...

s:t:r:i:n:g:1:\nstring2
s:t:r:i:n:g:\nstring21:
s:t:r:i:n:g:1: 
 : : : : : : :\nstring2

Básicamente, uso la expresión regular para manejar siempre solo la primera línea en cualquier espacio de patrón al que la aplique. Eso me permite hacer malabarismos con dos versiones diferentes de una línea retenida más corta hasta ahora y la línea más reciente sin recurrir a bucles de prueba: cada sustitución aplicada maneja todo el espacio de patrones a la vez.

Las diferentes versiones son necesarias para la comparación literal de cadenas / cadenas, por lo que debe haber una versión de cada línea donde se garantice que todos los caracteres sean iguales. Pero, por supuesto, si una u otra termina siendo la primera línea más corta en la entrada, entonces la línea impresa en la salida probablemente debería ser la versión original de la línea, no la que he desinfectado / homogeneizado por el bien de la comparación. Y entonces necesito dos versiones de cada uno.

Es desafortunado que otra necesidad sea una gran cantidad de cambio de búfer para manejar el mismo, pero al menos ninguno de los búferes excede nunca más de las cuatro líneas necesarias para mantenerse actualizado, por lo que tal vez no sea terrible.

De todos modos, para cada ciclo, lo primero que sucede es una transformación en la línea recordada, porque la única copia realmente guardada es el original literal, en ...

^               \nremembered line$

... y luego la nlínea de entrada ext sobrescribe cualquier búfer antiguo. Si no contiene al menos un solo carácter, se ignora efectivamente. Sería mucho más fácil simplementeq pasar a la primera línea en blanco, pero, bueno, mis datos de prueba tenían muchos de esos y quería manejar múltiples párrafos.

Entonces, si contiene un carácter, su versión literal se agrega a la línea recordada y su versión de comparación espaciada se coloca en la cabecera del espacio del patrón, de esta manera:

^   \n               \nremembered line\nnew$

Por último, se aplica una sustitución a ese espacio de patrón:

s/^\( *\)\(\n \1 *\)\{0,1\}\n//

Entonces, si la nueva línea puede caber dentro del espacio necesario para contener la línea recordada con al menos un carácter libre, entonces las dos primeras líneas se sustituyen, de lo contrario solo la primera.

Independientemente del resultado, la primera línea en el espacio del patrón siempre se Delige al final del ciclo antes de comenzar de nuevo. Esto significa que si la nueva línea es más corta que la última, la cadena ...

new

... se envía de vuelta a la primera sustitución en el ciclo, que siempre se eliminará solo del primer carácter de nueva línea en adelante, por lo que permanece completo. Pero si no es así, la cadena ...

remembered line\nnew

... comenzará el siguiente ciclo, y la primera sustitución le quitará la cadena ...

\nnew

...cada vez.

En la última línea, la línea recordada se imprime con salida estándar y, por lo tanto, para los datos de ejemplo proporcionados, imprime:

4for

Pero, en serio, usa tr.



¿Incluso necesita insertar números de línea? Mi lectura del OP es que solo se requiere la línea más corta, y no necesariamente el número de línea de esa línea. Supongo que no hay daño en mostrarlo para completar.
Trauma digital

@ Digital Trauma - nah, probablemente no. Pero no es muy útil sin ellos, y son muy baratos. Cuando trabajo una secuencia, siempre prefiero incluir un medio para reproducir la entrada original de forma idéntica en la salida: los números de línea lo hacen posible aquí. Por ejemplo, para convertir los resultados de la primera tubería en torno a: REINPUT | sort -t: -nk1,1 | cut -d: -f3-. Y el segundo es una simple cuestión de incluir otro sed --expressionscript en la cola.
mikeserv

@DigitalTrauma - oh, y en el primer ejemplo los números de línea no afecta sortel comportamiento 's como un desempate cuando las líneas de la misma longitud se producen en la entrada - de modo que la línea se producen más temprano siempre flota en la superficie en ese caso.
mikeserv

7

Tratar:

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

La idea es utilizar awkpara imprimir la longitud de cada línea primero. Esto aparecerá como:

echo "This is a line of text" | awk '{print length, $0}'
22 This is a line of text

Luego, use el recuento de caracteres para ordenar las líneas sort, cutdeshacerse del recuento y headmantener la primera línea (la que tenga menos caracteres). Por supuesto, puede usar tailpara obtener la línea con más caracteres en este caso.

(Esto fue adoptado de esta respuesta )


+1 para la lógica pero no funcionará en todos los casos. Si las dos líneas tienen el mismo número de caracteres y cuál es mínimo. Solo le dará la primera línea que se encuentra porhead -1
Thushi

Para obtener la línea más larga, es un poco más eficiente invertir el orden que usarlo tail(ya que headpuede salir tan pronto como termine su trabajo, sin leer el resto de su entrada).
Toby Speight

@Thushi Usando un poco de expresión regular, después de imprimir los números de línea, todo, excepto las líneas con el mismo número que la línea 1, podría eliminarse, generando así todas las líneas más cortas.
Matthew D. Scholefield

5

Con POSIX awk:

awk 'FNR==1{l=$0;next};length<length(l){l=$0};END{print l}' file

No funcionará si más de una línea tiene el mismo número de caracteres y también es mínimo.
Thushi

@Thushi: Informará la primera línea mínima.
Cuonglm

Sí, pero esa no es la salida correcta, ¿verdad? Incluso las otras líneas tienen el número mínimo de caracteres.
Thushi

1
@Thushi: Eso no menciona en el requisito de OP, esperando la actualización de OP.
Cuonglm

3
No creo que sea Lla mejor letra para elegir nombrar la variable: D Algo así minharía las cosas más claras
fedorqui

3

Pedir prestado algunas de las ideas de @mikeserv:

< testfile sed 'h;s/./:/g;s/.*/expr length "&"/e;G;s/\n/\t/' | \
sort -n | \
sed -n '1s/^[0-9]+*\t//p'

El primero sedhace lo siguiente:

  • h guarda la línea original en el búfer de retención
  • Reemplace todos los caracteres en la línea con :- esto es para eliminar cualquier peligro de inyección de código
  • Reemplace toda la línea con expr length "whole line": esta es una expresión de shell que se puede evaluar
  • El comando es es una extensión de sed de GNU para evaluar el espacio del patrón y volver a colocar el resultado en el espacio del patrón.
  • G agrega una nueva línea y el contenido del espacio de espera (la línea original) al espacio del patrón
  • la final sreemplaza la nueva línea con una pestaña

El número de caracteres ahora es un número al comienzo de cada línea, por lo que se sort -nordena por longitud de línea.

La final sedelimina todas las líneas excepto la primera (más corta) y la longitud de la línea e imprime el resultado.


1
@mikeserv Sí, creo que expres mejor aquí. Sí, egenerará un caparazón para cada línea. Edité la expresión sed para que reemplace cada carácter en la cadena con un :antes de la evaluación, que creo que debería eliminar cualquier posibilidad de inyección de código.
Trauma digital

Por lo general, optaría por xargs exprpersonalmente, pero, aparte de evitar un caparazón intermedio, probablemente sea más estilístico. Me gusta, de todos modos.
mikeserv

3

Se me ocurrió que todo es posible en una sola sedexpresión. No es bonito:

$ sed '1h;s/.*/&\n&/;G;:l;s/\n[^\n]\([^\n]*\)\n[^\n]/\n\1\n/;tl;/\n\n/{s/\n.*//;x};${x;p};d' testfile
4for
$ 

Desglosando esto:

1h            # save line 1 in the hold buffer (shortest line so far)
s/.*/&\n&/    # duplicate the line with a newline in between
G             # append newline+hold buffer to current line
:l            # loop start
s/\n[^\n]\([^\n]*\)\n[^\n]/\n\1\n/
              # attempt to remove 1 char both from current line and shortest line
tl            # jump back to l if the above substitution succeeded
/\n\n/{       # matches if current line is shorter
  s/\n.*//    # remove all but original line
  x           # save new shortest line in hold buffer
}
${            # at last line
  x           # get shortest line from hold buffer
  p           # print it
}
d             # don't print any other lines

El BSD sed en OS X es un poco más meticuloso con las nuevas líneas. Esta versión funciona para las versiones BSD y GNU de sed:

$ sed -e '1h;G;s/\([^\n]*\)\(\n\)\(.*\)/\1\2\1\2\3/;:l' -e 's/\(\n\)[^\n]\([^\n]*\n\)[^\n]/\1\2/;tl' -e '/\n\n/{s/\n.*//;x;};${x;p;};d' testfile
4for
$

Tenga en cuenta que esto es más una respuesta "porque es posible" que un intento serio de dar una respuesta de mejores prácticas. Supongo que significa que he estado jugando demasiado code-colf


@mikeserv From man seden OS X: "La secuencia de escape \ n coincide con un carácter de nueva línea incrustado en el espacio del patrón" . Entonces, creo que GNU sed permite \nen la expresión regular y en el reemplazo, mientras que BSD solo permite \nen la expresión regular y no en el reemplazo.
Trauma digital

Pedir prestado el \nespacio del patrón es una buena idea y funcionaría en la segunda s///expresión, pero la s/.*/&\n&/expresión está insertando un \nespacio en el patrón donde antes no había ninguna. También BSD sed parece requerir nuevas líneas literales después de las definiciones de etiqueta y ramas.
Trauma digital

1
Esas líneas nuevas son delimitadores de parámetros: los necesita para delimitar cualquier comando que pueda aceptar un parámetro arbitrario, al menos, eso es lo que dice la especificación. La especificación también dice que un sedscript debe ser un archivo de texto, excepto que no necesita terminar en una nueva línea . Por lo tanto, generalmente puede delimitarlos como argumentos separados también, sed -e :\ label -e :\ label2y así sucesivamente. Como lo está haciendo de 1htodos modos, puede cambiar a una lógica basada en x;Hpara obtener su nueva línea, y puede recortar una nueva línea principal desde el espacio del patrón al final del ciclo sin tirar de una nueva línea con D.
mikeserv

@mikeserv Nice. Sí, inserté la nueva línea que necesitaba haciendo el Gprimero y cambiando la s///expresión. Dividirlo usando -epermite que todo vaya en una línea (larga) sin nuevas líneas literales.
Trauma digital

El \nescape también se especifica para sedel LHS de, y creo que esa es la declaración literal de la especificación, excepto que las expresiones de corchetes POSIX también se especifican de tal manera que todos los caracteres pierden su significado especial - (incluyendo explícitamente \\) - dentro de uno, excepto los corchetes, el guión como un separador de rango y punto, igual, intercalado, dos puntos para la clasificación, equivalencia, negación y clases.
mikeserv

2

Otra solución perl: almacene las líneas en un hash de matrices, siendo la clave hash la longitud de la línea. Luego, imprima las líneas con la clave mínima.

perl -MList::Util=min -ne '
    push @{$lines{ length() }}, $_;
} END {
    print @{$lines{ min keys %lines }};
' sample 
4for

Puede usar push @{$lines{+length}};y print @{$lines{+min keys %lines}};por menos escribir :)
cuonglm

Si jugara al golf, tampoco habría usado el nombre variable "líneas":perl -MList::Util=min -nE'push @{$l{+length}},$_}END{say@{$l{min keys%l}}' sample
Glenn Jackman

+1 para una versión sin golf (¡que funciona!), Aunque solo para la variante print all . - se perlvuelve un poco retorcido para aquellos de nosotros que no estamos a la altura perlde la naturaleza críptica. Por cierto. el campo de golf sayimprime una línea en blanco espuria al final de la salida.
Peter

2

Para obtener solo la primera línea más corta:

f=file; sed -n "/^$(sed 's/./1/g' $f | sort -ns | sed 's/././g;q')$/{p;q}" $f

Para obtener todas las pelusas más cortas, simplemente cambie {p;q}ap


Otro método (algo inusual) es sorthacer el ordenamiento real por longitud . Es relativamente lento incluso con líneas cortas y se vuelve dramáticamente más lento a medida que aumenta la longitud de la línea.
Sin embargo, me parece bastante interesante la idea de ordenar mediante la superposición de teclas . Lo estoy publicando en caso de que otros también lo encuentren interesante / informativo.

Cómo funciona:
Ordenar por variantes de longitud de la misma clave, key 1que abarca toda la línea
Cada variante de clave sucesiva incrementa la longitud de la clave en un carácter, hasta la longitud de la línea más larga del archivo (determinada por wc -L)

Para obtener solo la primera línea más corta (ordenada):

f=file; sort -t'\0' $(seq -f "-k1.%0.0f" $(<"$f" wc -L) -1 1) "$f" | head -n1

que es lo mismo que:

f=file.in; 
l=$(<"$f" wc -L)
k=$(seq -f "-k1.%0.0f" $l -1 1) 
sort -st'\0' $k "$f" | head -n1

2

Suponiendo que las líneas en blanco no se consideran la línea más corta y que pueden existir líneas en blanco, funcionará el siguiente AWK puro:

awk '
    {
        len   = length;
        a[$0] = len
    }
    !len { next }
    !min { min = len }
    len < min { min = len }
    END {
        for (i in a)
            if (min == a[i])
                print i
    }
' infile.txt

2

¿Qué hay de usar sort?

awk '{ print length($0) "\t" $0 }' input.txt | sort -n | head -n 1 | cut -f2-

1

Con GNU awk

gawk '
    {
         a[length]=$0
    };
    END
    {
        PROCINFO["sorted_in"]="@ind_num_asc";
        for (i in a)
        {
            print a[i]; 
            exit
        }
    }
    ' file
  • Lea cada línea en una matriz indexada por longitud de línea.

  • Se establece PROCINFO["sorted_in"]en @ind_num_ascpara forzar que el índice de la matriz ordene el escaneo de la matriz, ordenado numéricamente

  • La configuración de PROCINFOla manera anterior obliga a que la línea con la longitud más pequeña se recoja primero en el recorrido de la matriz. Imprima el primer elemento de la matriz y salga

Esto tiene la desventaja de ser un nlogntiempo, algunos de los otros enfoques están na tiempo


1

Método de herramientas de shell de nivel medio, sin sedo awk:

f=inputfile
head -n $(xargs -d '\n' -L 1 -I % sh -c 'exec echo "%" | wc -c' < $f | 
          cat -n | sort -n -k 2 | head -1 | cut -f 1)  $f | tail -1

Sería bueno no necesitar una $fvariable; Tengo una idea que podría ser posible de teealguna manera ...
agc
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.