¿Hay una forma "canónica" de hacer eso? He estado usando lo head -n | tail -1
que hace el truco, pero me he estado preguntando si hay una herramienta Bash que extraiga específicamente una línea (o un rango de líneas) de un archivo.
Por "canónico" me refiero a un programa cuya función principal es hacer eso.
awk
y,sed
y estoy seguro, que a alguien también se le ocurre una línea de Perl o algo así;)head | tail
solución es subóptima. Se han sugerido otras soluciones más óptimas.head | tail
solución no funciona si consulta una línea que no existe en la entrada: imprimirá la última línea.Respuestas:
head
y la tuberíatail
será lenta para un archivo enorme. Sugeriríased
así:¿Dónde
NUM
está el número de la línea que desea imprimir? así, por ejemplo,sed '10q;d' file
para imprimir la décima línea defile
.Explicación:
NUMq
se cerrará inmediatamente cuando el número de línea seaNUM
.d
eliminará la línea en lugar de imprimirla; esto se inhibe en la última línea porqueq
hace que el resto del script se omita al salir.Si tiene
NUM
una variable, querrá usar comillas dobles en lugar de simples:fuente
sed -n 'NUMp'
ysed 'NUM!d'
soluciones propuestas a continuación.tail -n+NUM file | head -n1
es probable que sea igual o más rápido. Al menos, fue (significativamente) más rápido en mi sistema cuando lo probé con NUM siendo 250000 en un archivo con medio millón de líneas. YMMV, pero realmente no veo por qué lo haría.cat
es más rápido (casi el doble de rápido), pero solo si el archivo aún no se ha almacenado en caché . Una vez que el archivo se almacena en caché , el uso directo del argumento del nombre de archivo es más rápido (aproximadamente 1/3 más rápido), mientras que elcat
rendimiento se mantiene igual. Curiosamente, en OS X 10.9.3 nada de esto parece hacer ninguna diferencia:cat
/ nocat
, archivo en caché o no. @anubhava: es un placer.sed 'NUMq
generará los primerosNUM
archivos y;d
eliminará todos menos la última línea.imprimirá la segunda línea
Línea 2011
línea 10 hasta línea 33
1ra y 3ra línea
y así...
Para agregar líneas con sed, puede verificar esto:
sed: inserte una línea en una posición determinada
fuente
<
en este caso no es necesario. Simplemente, es mi preferencia usar redireccionamientos, porque yo solía usar redireccionamientos comosed -n '100p' < <(some_command)
, por lo tanto, sintaxis universal :). NO es menos efectivo, porque la redirección se realiza con shell cuando se bifurca, así que ... es solo una preferencia ... (y sí, es un carácter más) :)head
/tail
no significa resuelve elsed -n '1p;3p'
escenario - aka imprimir más filas no adyacentes ...Tengo una situación única en la que puedo comparar las soluciones propuestas en esta página, por lo que escribo esta respuesta como una consolidación de las soluciones propuestas con tiempos de ejecución incluidos para cada una.
Preparar
Tengo un archivo de datos de texto ASCII de 3.261 gigabytes con un par clave-valor por fila. El archivo contiene 3,339,550,320 filas en total y desafía la apertura en cualquier editor que haya probado, incluido mi Vim de acceso. Necesito subconjuntar este archivo para investigar algunos de los valores que descubrí que solo comienzan alrededor de la fila ~ 500,000,000.
Debido a que el archivo tiene tantas filas:
Mi mejor escenario es una solución que extrae solo una sola línea del archivo sin leer ninguna de las otras filas del archivo, pero no puedo pensar en cómo podría lograr esto en Bash.
A los fines de mi cordura, no voy a tratar de leer las 500,000,000 líneas completas que necesitaría para mi propio problema. En cambio, intentaré extraer la fila 50,000,000 de 3,339,550,320 (lo que significa que leer el archivo completo tomará 60 veces más de lo necesario).
Usaré el
time
incorporado para comparar cada comando.Base
Primero veamos cómo la
head
tail
solución:La línea de base para la fila 50 millones es 00: 01: 15.321, si hubiera ido directamente a la fila 500 millones probablemente sería ~ 12.5 minutos.
cortar
Dudo de esto, pero vale la pena intentarlo:
Este tomó 00: 05: 12.156 para ejecutarse, ¡lo cual es mucho más lento que la línea de base! No estoy seguro de si leyó todo el archivo o solo hasta 50 millones de líneas antes de detenerse, pero independientemente de esto, no parece una solución viable para el problema.
AWK
Solo ejecuté la solución con el
exit
porque no iba a esperar a que se ejecute el archivo completo:Este código se ejecutó en 00: 01: 16.583, que es solo ~ 1 segundo más lento, pero aún no es una mejora en la línea de base. ¡A este ritmo, si se hubiera excluido el comando de salida, probablemente habría tomado alrededor de ~ 76 minutos leer el archivo completo!
Perl
También ejecuté la solución Perl existente:
Este código se ejecutó en 00: 01: 13.146, que es ~ 2 segundos más rápido que la línea de base. Si lo ejecutara en los 500,000,000 completos, probablemente tomaría ~ 12 minutos.
sed
La respuesta principal en el tablero, aquí está mi resultado:
Este código se ejecutó en 00: 01: 12.705, que es 3 segundos más rápido que la línea de base y ~ 0.4 segundos más rápido que Perl. Si lo hubiera ejecutado en las 500,000,000 filas completas, probablemente hubiera tomado ~ 12 minutos.
archivo de mapa
Tengo bash 3.1 y, por lo tanto, no puedo probar la solución mapfile.
Conclusión
Parece que, en su mayor parte, es difícil mejorar la
head
tail
solución. En el mejor de los casos, lased
solución proporciona un aumento de ~ 3% en la eficiencia.(porcentajes calculados con la fórmula
% = (runtime/baseline - 1) * 100
)Fila 50,000,000
sed
perl
head|tail
awk
cut
Fila 500,000,000
sed
perl
head|tail
awk
cut
Fila 3,338,559,320
sed
perl
head|tail
awk
cut
fuente
Con
awk
esto es bastante rápido:Cuando esto es así, el comportamiento predeterminado de
awk
las que se realiza:{print $0}
.Versiones alternativas
Si su archivo es enorme, será mejor que
exit
lea la línea requerida. De esta forma ahorra tiempo de CPU. Vea la comparación de tiempo al final de la respuesta .Si desea dar el número de línea de una variable bash, puede usar:
Vea cuánto tiempo se ahorra usando
exit
, especialmente si la línea se encuentra en la primera parte del archivo:Entonces, la diferencia es 0.198s contra 1.303s, alrededor de 6 veces más rápido.
fuente
awk 'BEGIN{FS=RS}(NR == num_line) {print; exit}' file
awk 'FNR==n' n=10 file1 n=30 file2 n=60 file3
. Con GNU awk esto se puede acelerar usandoawk 'FNR==n{print;nextfile}' n=10 file1 n=30 file2 n=60 file3
.FS=RS
evitar la división del campo?FS=RS
no evita la división de campos, pero sólo analiza los $ 0 queridos y sólo se asigna un campo porque no hayRS
en$0
FS=RS
y no vi diferencias en los horarios. ¿Qué tal si hago una pregunta al respecto para que pueda expandirse? ¡Gracias!Según mis pruebas, en términos de rendimiento y legibilidad, mi recomendación es:
tail -n+N | head -1
N
es el número de línea que quieres. Por ejemplo,tail -n+7 input.txt | head -1
imprimirá la séptima línea del archivo.tail -n+N
imprimirá todo a partir de la líneaN
yhead -1
hará que se detenga después de una línea.La alternativa
head -N | tail -1
es quizás un poco más legible. Por ejemplo, esto imprimirá la 7ma línea:head -7 input.txt | tail -1
Cuando se trata de rendimiento, no hay mucha diferencia para los tamaños más pequeños, pero será superado por
tail | head
(desde arriba) cuando los archivos se vuelvan enormes.sed 'NUMq;d'
Es interesante saber cuál es el mejor votado , pero diría que será entendido por menos personas fuera de la caja que la solución cabeza / cola y también es más lento que la cola / cabeza.En mis pruebas, ambas versiones de colas / cabezas obtuvieron mejores resultados de forma
sed 'NUMq;d'
consistente. Eso está en línea con los otros puntos de referencia que se publicaron. Es difícil encontrar un caso en el que las colas / cabezas fueran realmente malas. Tampoco es sorprendente, ya que estas son operaciones que cabría esperar que estén muy optimizadas en un sistema Unix moderno.Para tener una idea sobre las diferencias de rendimiento, estos son los números que obtengo para un archivo enorme (9.3G):
tail -n+N | head -1
: 3.7 segundoshead -N | tail -1
: 4.6 segundossed Nq;d
: 18.8 segundosLos resultados pueden diferir, pero el rendimiento
head | tail
ytail | head
, en general, es comparable para entradas más pequeñas, ysed
siempre es más lento en un factor significativo (alrededor de 5 veces más o menos).Para reproducir mi punto de referencia, puede intentar lo siguiente, pero tenga en cuenta que creará un archivo 9.3G en el directorio de trabajo actual:
Aquí está el resultado de una ejecución en mi máquina (ThinkPad X1 Carbon con un SSD y 16G de memoria). Supongo que en la ejecución final todo vendrá del caché, no del disco:
fuente
head | tail
vstail | head
? ¿O depende de qué línea se está imprimiendo (principio del archivo vs final del archivo)?head -5 | tail -1
vstail -n+5 | head -1
. En realidad, encontré otra respuesta que hizo una comparación de prueba y resultótail | head
ser más rápida. stackoverflow.com/a/48189289¡Guau, todas las posibilidades!
Prueba esto:
o uno de estos dependiendo de su versión de Awk:
( Puede que tenga que probar el comando
nawk
ogawk
).¿Existe alguna herramienta que solo imprima esa línea en particular? No es una de las herramientas estándar. Sin embargo,
sed
es probablemente el más cercano y sencillo de usar.fuente
Scripts útiles de una línea para sed
fuente
Esta pregunta está etiquetada como Bash, aquí está la forma de hacer Bash (≥4): usar
mapfile
con la opción-s
(omitir) y-n
(contar).Si necesita obtener la línea 42 de un archivo
file
:En este punto, tendrá una matriz
ary
cuyos campos contienen las líneas defile
(incluida la nueva línea final), donde hemos omitido las primeras 41 líneas (-s 41
) y nos hemos detenido después de leer una línea (-n 1
). Así que esa es realmente la línea 42. Para imprimirlo:Si necesita un rango de líneas, diga el rango 42–666 (inclusive), y diga que no quiere hacer los cálculos usted mismo, e imprímalos en stdout:
Si también necesita procesar estas líneas, no es realmente conveniente almacenar la nueva línea final. En este caso, use la
-t
opción (recortar):Puede hacer que una función haga eso por usted:
¡Sin comandos externos, solo Bash incorporado!
fuente
También puede usar sed print y salir:
fuente
-n
opción deshabilita la acción predeterminada para imprimir cada línea, como seguramente lo habría descubierto con un vistazo rápido a la página del manual.sed
todas lassed
respuestas tienen la misma velocidad. Por lo tanto (para GNUsed
) esta es la mejorsed
respuesta, ya que ahorraría tiempo para archivos grandes y valores pequeños de enésima línea .También puedes usar Perl para esto:
fuente
La solución más rápida para archivos grandes es siempre tail | head, siempre que las dos distancias:
S
E
son conocidos. Entonces, podríamos usar esto:
howmany es solo el recuento de líneas requerido.
Algunos detalles más en https://unix.stackexchange.com/a/216614/79743
fuente
S
yE
(es decir, bytes, caracteres o líneas).Todas las respuestas anteriores responden directamente a la pregunta. Pero aquí hay una solución menos directa, pero una idea potencialmente más importante, para provocar el pensamiento.
Como las longitudes de línea son arbitrarias, todos los bytes del archivo antes de la enésima línea deben leerse. Si tiene un archivo enorme o necesita repetir esta tarea muchas veces, y este proceso lleva mucho tiempo, entonces debe pensar seriamente si debería almacenar sus datos de una manera diferente en primer lugar.
La solución real es tener un índice, por ejemplo, al comienzo del archivo, que indique las posiciones donde comienzan las líneas. Puede usar un formato de base de datos o simplemente agregar una tabla al comienzo del archivo. Alternativamente, cree un archivo de índice separado para acompañar su archivo de texto grande.
por ejemplo, puede crear una lista de posiciones de caracteres para líneas nuevas:
luego lea con
tail
, que en realidadseek
está directamente en el punto apropiado del archivo.Por ejemplo, para obtener la línea 1000:
fuente
Como seguimiento a la muy útil respuesta de evaluación comparativa de CaffeineConnoisseur ... Tenía curiosidad por saber qué tan rápido se comparó el método 'mapfile' con otros (ya que no se probó), así que probé una comparación de velocidad rápida y sucia. Tengo bash 4 a mano. Lancé una prueba del método "cola | cabeza" (en lugar de cabeza | cola) mencionado en uno de los comentarios en la respuesta superior mientras estaba en ello, ya que la gente canta sus alabanzas. No tengo nada del tamaño del archivo de prueba utilizado; lo mejor que pude encontrar a corto plazo fue un archivo de pedigrí de 14M (líneas largas separadas por espacios en blanco, un poco menos de 12000 líneas).
Versión corta: mapfile aparece más rápido que el método de corte, pero más lento que todo lo demás, por lo que lo llamaría un fracaso. cola | head, OTOH, parece que podría ser el más rápido, aunque con un archivo de este tamaño la diferencia no es tan sustancial en comparación con sed.
¡Espero que esto ayude!
fuente
Usando lo que otros mencionaron, quería que esta fuera una función rápida y elegante en mi shell de bash.
Crea un archivo:
~/.functions
Añádele los contenidos:
getline() { line=$1 sed $line'q;d' $2 }
Luego agregue esto a su
~/.bash_profile
:source ~/.functions
Ahora, cuando abre una nueva ventana de bash, puede llamar a la función de la siguiente manera:
getline 441 myfile.txt
fuente
Si tienes varias líneas delimitadas por \ n (normalmente una nueva línea). También puedes usar 'cortar':
Obtendrá la segunda línea del archivo.
-f3
te da la 3ra línea.fuente
cat FILE | cut -f2,5 -d$'\n'
mostrará las líneas 2 y 5 del ARCHIVO. (Pero no preservará el orden.)Para imprimir la enésima línea usando sed con una variable como número de línea:
Aquí la bandera '-e' es para agregar script al comando que se ejecutará.
fuente
Muchas buenas respuestas ya. Yo personalmente voy con awk. Para mayor comodidad, si usa bash, simplemente agregue lo siguiente a su
~/.bash_profile
. Y, la próxima vez que inicie sesión (o si obtiene su .bash_profile después de esta actualización), tendrá una nueva función ingeniosa "enésima" disponible para canalizar sus archivos.Ejecute esto o póngalo en su ~ / .bash_profile (si usa bash) y vuelva a abrir bash (o ejecutar
source ~/.bach_profile
)# print just the nth piped in line nth () { awk -vlnum=${1} 'NR==lnum {print; exit}'; }
Luego, para usarlo, simplemente páselo a través de él. P.ej,:
$ yes line | cat -n | nth 5 5 line
fuente
Después de echar un vistazo a la respuesta superior y al punto de referencia , he implementado una pequeña función auxiliar:
Básicamente puedes usarlo de dos maneras:
fuente
Puse algunas de las respuestas anteriores en un breve script de bash que puede poner en un archivo llamado
get.sh
y vincular/usr/local/bin/get
(o cualquier otro nombre que prefiera).Asegúrese de que sea ejecutable con
Enlázalo para que esté disponible en el
PATH
con¡Disfruta responsablemente!
PAGS
fuente