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 -m
para 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 4for
ya 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.
fuente
Respuestas:
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:
Explicación
perl -lne
:-n
significa "leer el archivo de entrada línea por línea",-l
hace que se eliminen las nuevas líneas finales de cada línea de entrada y se agregue una nueva línea a cadaprint
llamada; y-e
es el script que se aplicará a cada línea.$m//=$_
: establecido$m
en la línea actual ($_
) a menos que$m
esté definido. El//=
operador está disponible desde Perl 5.10.0.$m=$_ if length()<length($m)
: si la longitud del valor actual de$m
es 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$m
la línea más corta. Estoif $.
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:
Explicación
@K=sort{length($a) <=> length($b)}<>
:<>
aquí hay una matriz cuyos elementos son las líneas del archivo. Lossort
ordenará 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
fuente
-C
para 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).Con
sqlite3
:fuente
strace
indica). Si necesita trabajar con archivos realmente grandes (y su sistema no se intercambia), puede forzarlo simplemente agregando un nombre de archivo comosqlite3 $(mktemp)
y todos los datos se escribirán en el disco.Aquí hay una variante de una
awk
solución para imprimir la primera línea mínima encontrada:que simplemente se puede extender una condición para imprimir todas las líneas mínimas:
fuente
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'))"
fuente
Siempre me encantan las soluciones con scripting de shell puro (¡no exec!).
Nota :
Hay un problema con los bytes NUL en la entrada. Entonces,
printf "ab\0\0\ncd\n" | bash this_script
impresiones enab
lugar decd
.fuente
bash
me convencería de canalizar un resultado intermedio en susort
lugar.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.$IFS
no es discriminatorio por dígitos, incluso si no hay ninguno en un$IFS
valor predeterminado , aunque muchos shells aceptarán una configuración de entorno preestablecida para$IFS
, por lo que no es un valor predeterminado particularmente confiable./bin/sh
disponibles. Me ha sucedido varias veces con hosts SunOS4 con/usr
pérdida o algunos.so
dañ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.Aquí una
zsh
solución pura (imprime todas las líneas con la longitud mínima, desdefile
):Entrada de ejemplo:
Salida es:
Creo que necesita una breve explicación :-)
Primero, establecemos el separador de campo interno en nueva línea:
Hasta ahora todo bien, ahora la parte difícil.
print
usa la-l
bandera para imprimir el resultado separado por nuevas líneas en lugar de espacios.Ahora, comenzamos en el interior:
El archivo se lee línea por línea y se trata como una matriz. Luego:
La
o
bandera 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:Tomamos el primer elemento de matriz
[1]
, que es el más corto, en su caso es ahora????
.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:
fuente
... y el ganador es ... la línea 2, parece.
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
unexpand
hacerlo:Eso imprime ...
Otro, solo
sed
: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:
Que combina diferentes cadenas de diferentes maneras. Por ejemplo:
... coincide con
s
in\1
y''
la cadena nula en\2
.... coincide con
1
in\1
y\nstring2\nstring3
in\2
... coincide con
\n
in\1
y''
la cadena nula en\2
. Esto sería problemático si hubiera alguna posibilidad de que se\n
produzca una línea en la cabecera del espacio del patrón, pero los comandos/^\n/D
, y//!g
se 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.... coinciden
\n
ys
nuevamente\1
y ambos obtienen la''
cadena nula\2
. Las líneas vacías no coinciden en absoluto.Cuando el patrón se aplica de forma
g
lobular, los dos sesgos, tanto el sesgo estándar más a la izquierda como el sesgo menor a la derecha del lado\n
derecho, se contrarrestan para efectuar un salto. Algunos ejemplos:... si todo se aplica (no en sucesión) a la siguiente cadena ...
... lo transformará a ...
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 ...
... y luego la
n
lí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:
Por último, se aplica una sustitución a ese espacio de patró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
D
elige 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 ...... 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 ...
... comenzará el siguiente ciclo, y la primera sustitución le quitará la cadena ...
...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:
Pero, en serio, usa
tr
.fuente
REINPUT | sort -t: -nk1,1 | cut -d: -f3-
. Y el segundo es una simple cuestión de incluir otrosed
--expression
script en la cola.sort
el 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.Tratar:
La idea es utilizar
awk
para imprimir la longitud de cada línea primero. Esto aparecerá como:Luego, use el recuento de caracteres para ordenar las líneas
sort
,cut
deshacerse del recuento yhead
mantener la primera línea (la que tenga menos caracteres). Por supuesto, puede usartail
para obtener la línea con más caracteres en este caso.(Esto fue adoptado de esta respuesta )
fuente
head -1
tail
(ya quehead
puede salir tan pronto como termine su trabajo, sin leer el resto de su entrada).Con POSIX awk:
fuente
L
la mejor letra para elegir nombrar la variable: D Algo asímin
haría las cosas más clarasPedir prestado algunas de las ideas de @mikeserv:
El primero
sed
hace lo siguiente:h
guarda la línea original en el búfer de retención:
- esto es para eliminar cualquier peligro de inyección de códigoexpr length "whole line"
: esta es una expresión de shell que se puede evaluars
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óns
reemplaza la nueva línea con una pestañaEl número de caracteres ahora es un número al comienzo de cada línea, por lo que se
sort -n
ordena por longitud de línea.La final
sed
elimina todas las líneas excepto la primera (más corta) y la longitud de la línea e imprime el resultado.fuente
expr
es mejor aquí. Sí,e
generará 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.xargs expr
personalmente, pero, aparte de evitar un caparazón intermedio, probablemente sea más estilístico. Me gusta, de todos modos.Se me ocurrió que todo es posible en una sola
sed
expresión. No es bonito:Desglosando esto:
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:
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
fuente
man sed
en 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\n
en la expresión regular y en el reemplazo, mientras que BSD solo permite\n
en la expresión regular y no en el reemplazo.\n
espacio del patrón es una buena idea y funcionaría en la segundas///
expresión, pero las/.*/&\n&/
expresión está insertando un\n
espacio 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.sed
script 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 :\ label2
y así sucesivamente. Como lo está haciendo de1h
todos modos, puede cambiar a una lógica basada enx;H
para 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 conD
.G
primero y cambiando las///
expresión. Dividirlo usando-e
permite que todo vaya en una línea (larga) sin nuevas líneas literales.\n
escape también se especifica parased
el 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.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.
fuente
push @{$lines{+length}};
yprint @{$lines{+min keys %lines}};
por menos escribir :)perl -MList::Util=min -nE'push @{$l{+length}},$_}END{say@{$l{min keys%l}}' sample
perl
vuelve un poco retorcido para aquellos de nosotros que no estamos a la alturaperl
de la naturaleza críptica. Por cierto. el campo de golfsay
imprime una línea en blanco espuria al final de la salida.Para obtener solo la primera línea más corta:
Para obtener todas las pelusas más cortas, simplemente cambie
{p;q}
ap
Otro método (algo inusual) es
sort
hacer 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 1
que abarca toda la líneaCada 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):
que es lo mismo que:
fuente
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:
fuente
¿Qué hay de usar sort?
fuente
Con GNU awk
Lea cada línea en una matriz indexada por longitud de línea.
Se establece
PROCINFO["sorted_in"]
en@ind_num_asc
para forzar que el índice de la matriz ordene el escaneo de la matriz, ordenado numéricamenteLa configuración de
PROCINFO
la 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 salgaEsto tiene la desventaja de ser un
nlogn
tiempo, algunos de los otros enfoques estánn
a tiempofuente
Método de herramientas de shell de nivel medio, sin
sed
oawk
:fuente
$f
variable; Tengo una idea que podría ser posible detee
alguna manera ...