usage tutorial print ejemplos and bash shell unix awk sed

bash - tutorial - awk variables shell



Herramienta Bash para obtener la lĂ­nea nth de un archivo (19)

¿Hay una manera "canónica" de hacer eso? He estado usando head -n | tail -1 head -n | tail -1 que hace el truco, pero me he estado preguntando si hay una herramienta Bash que extrae 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.


Como seguimiento de la muy útil respuesta de evaluación comparativa de CaffeineConnoisseur ... Tenía curiosidad por lo rápido que se comparaba el método ''mapfile'' con otros (ya que no estaba probado), así que intenté una comparación de velocidad rápida y sucia como Tengo bash 4 a la mano. Hice una prueba del método de "cola | cabeza" (en lugar de cabeza | cola) mencionado en uno de los comentarios en la respuesta principal mientras estaba en ello, ya que la gente está cantando sus alabanzas. No tengo nada del tamaño del archivo de prueba usado; Lo mejor que pude encontrar en poco tiempo fue un archivo de pedigrí de 14M (líneas largas que están separadas por espacios en blanco, justo debajo de 12000 líneas).

Versión corta: mapfile parece más rápido que el método de corte, pero más lento que todo lo demás, así 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.

$ time head -11000 [filename] | tail -1 [output redacted] real 0m0.117s $ time cut -f11000 -d$''/n'' [filename] [output redacted] real 0m1.081s $ time awk ''NR == 11000 {print; exit}'' [filename] [output redacted] real 0m0.058s $ time perl -wnl -e ''$.== 11000 && print && exit;'' [filename] [output redacted] real 0m0.085s $ time sed "11000q;d" [filename] [output redacted] real 0m0.031s $ time (mapfile -s 11000 -n 1 ary < [filename]; echo ${ary[0]}) [output redacted] real 0m0.309s $ time tail -n+11000 [filename] | head -n1 [output redacted] real 0m0.028s

¡Espero que esto ayude!


Con awk es bastante rápido:

awk ''NR == num_line'' file

Cuando esto es cierto, se realiza el comportamiento predeterminado de awk : {print $0} .

Versiones alternativas

Si su archivo resulta ser enorme, es mejor que exit después de leer la línea requerida. De esta manera ahorras tiempo de CPU.

awk ''NR == num_line {print; exit}'' file

Si desea dar el número de línea de una variable bash puede usar:

awk ''NR == n'' n=$num file awk -v n=$num ''NR == n'' file # equivalent


Esta pregunta se etiqueta como Bash, aquí está la forma de hacer Bash (≥4): usar mapfile con las mapfile -s (saltar) y -n (contar).

Si necesita obtener la línea 42 de un file :

mapfile -s 41 -n 1 ary < file

En este punto, tendrá una matriz en los campos que contienen las líneas del file (incluida la nueva línea final), donde hemos omitido las primeras 41 líneas ( -s 41 ) y se detuvo después de leer una línea ( -n 1 ). Así que esa es realmente la línea 42. Para imprimirlo:

printf ''%s'' "${ary[0]}"

Si necesita un rango de líneas, diga el rango 42–666 (inclusive), y diga que no quiere hacer las cuentas usted mismo, e imprímalas en la salida estándar:

mapfile -s $((42-1)) -n $((666-42+1)) ary < file printf ''%s'' "${ary[@]}"

Si necesita procesar estas líneas también, no es realmente conveniente almacenar la nueva línea final. En este caso use la opción -t (recortar):

mapfile -t -s $((42-1)) -n $((666-42+1)) ary < file # do stuff printf ''%s/n'' "${ary[@]}"

Puedes hacer que una función haga eso por ti:

print_file_range() { # $1-$2 is the range of file $3 to be printed to stdout local ary mapfile -s $(($1-1)) -n $(($2-$1+1)) ary < "$3" printf ''%s'' "${ary[@]}" }

No hay comandos externos, solo Bash builtins!


La solución más rápida para archivos grandes es siempre la cabeza, siempre que las dos distancias:

  • Desde el inicio del archivo hasta la línea de inicio. Llamémoslo S
  • La distancia desde la última línea hasta el final del archivo. Ya sea E

son conocidos. Entonces, podríamos usar esto:

mycount="$E"; (( E > S )) && mycount="+$S" howmany="$(( endline - startline + 1 ))" tail -n "$mycount"| head -n "$howmany"

Cuántos son solo el recuento de líneas requeridas.

Algunos detalles más en https://unix.stackexchange.com/a/216614/79743


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 archivo .bash_profile después de esta actualización), tendrá una nueva función "nth" para canalizar sus archivos.

Ejecute esto o colóquelo en su ~ / .bash_profile (si usa bash) y vuelva a abrir bash (o ejecute source ~/.bach_profile )

# print just the nth piped in line nth () { awk -vlnum=${1} ''NR==lnum {print; exit}''; }

Luego, para usarlo, simplemente canalizarlo. P.ej,:

$ yes line | cat -n | nth 5 5 line


Para imprimir la línea n usando sed con una variable como número de línea:

a=4 sed -e $a''q:d'' file

Aquí el distintivo ''-e'' es para agregar un script a un comando para ser ejecutado.


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 desea. Por ejemplo, tail -n+7 input.txt | head -1 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ínea N , y head -1 hará que se detenga después de una línea.

La head -N | tail -1 alternativa head -N | tail -1 head -N | tail -1 es quizás un poco más legible. Por ejemplo, esto imprimirá la séptima línea:

head -7 input.txt | tail -1

Cuando se trata de rendimiento, no hay mucha diferencia para tamaños más pequeños, pero será superado por la tail | head tail | head (desde arriba) cuando los archivos se vuelven enormes.

Es interesante saber cuál es el sed ''NUMq;d'' más votado, pero yo diría que lo entenderán menos personas 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 cola / cabeza superaron a sed ''NUMq;d'' consistentemente. Eso está en línea con los otros puntos de referencia que se publicaron. Es difícil encontrar un caso donde las colas / cabezas fueran realmente malas. Tampoco es sorprendente, ya que estas son operaciones que usted espera que se optimicen en gran medida en un sistema Unix moderno.

Para tener una idea de las diferencias de rendimiento, este es el número que obtengo para un archivo enorme (9.3G):

  • tail -n+N | head -1 tail -n+N | head -1 : 3.7 segundos
  • head -N | tail -1 head -N | tail -1 : 4.6 seg
  • sed Nq;d : 18.8 seg

Los resultados pueden diferir, pero la head | tail rendimiento head | tail head | tail y tail | head tail | head es, en general, comparable para insumos más pequeños, y sed siempre es más lento por un factor significativo (alrededor de 5x o menos).

Para reproducir mi punto de referencia, puede intentar lo siguiente, pero se le advierte que creará un archivo 9.3G en el directorio de trabajo actual:

#!/bin/bash readonly file=tmp-input.txt readonly size=1000000000 readonly pos=500000000 readonly retries=3 seq 1 $size > $file echo "*** head -N | tail -1 ***" for i in $(seq 1 $retries) ; do time head "-$pos" $file | tail -1 done echo "-------------------------" echo echo "*** tail -n+N | head -1 ***" echo seq 1 $size > $file ls -alhg $file for i in $(seq 1 $retries) ; do time tail -n+$pos $file | head -1 done echo "-------------------------" echo echo "*** sed Nq;d ***" echo seq 1 $size > $file ls -alhg $file for i in $(seq 1 $retries) ; do time sed $pos''q;d'' $file done /bin/rm $file

Aquí está la salida 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á de la caché, no del disco:

*** head -N | tail -1 *** 500000000 real 0m9,800s user 0m7,328s sys 0m4,081s 500000000 real 0m4,231s user 0m5,415s sys 0m2,789s 500000000 real 0m4,636s user 0m5,935s sys 0m2,684s ------------------------- *** tail -n+N | head -1 *** -rw-r--r-- 1 phil 9,3G Jan 19 19:49 tmp-input.txt 500000000 real 0m6,452s user 0m3,367s sys 0m1,498s 500000000 real 0m3,890s user 0m2,921s sys 0m0,952s 500000000 real 0m3,763s user 0m3,004s sys 0m0,760s ------------------------- *** sed Nq;d *** -rw-r--r-- 1 phil 9,3G Jan 19 19:50 tmp-input.txt 500000000 real 0m23,675s user 0m21,557s sys 0m1,523s 500000000 real 0m20,328s user 0m18,971s sys 0m1,308s 500000000 real 0m19,835s user 0m18,830s sys 0m1,004s


Si tienes varias líneas delimitadas por / n (normalmente una línea nueva). Puedes usar ''cortar'' también:

echo "$data" | cut -f2 -d$''/n''

Obtendrá la segunda línea del archivo. -f3 te da la tercera línea.


También puedes usar Perl para esto:

perl -wnl -e ''$.== NUM && print && exit;'' some.file


También puedes usar sed print y dejar de fumar:

sed -n ''10{p;q;}'' file # print line 10


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 intentado, incluyendo mi acceso a Vim. Necesito subcontratar este archivo para investigar algunos de los valores que he descubierto que comienzan alrededor de la fila ~ 500,000,000.

Porque el archivo tiene tantas filas:

  • Necesito extraer solo un subconjunto de las filas para hacer algo útil con los datos.
  • La lectura de cada fila que conduce a los valores que me importan llevará mucho tiempo.
  • Si la solución supera las filas que me importan y continúa leyendo el resto del archivo, perderá tiempo leyendo casi 3 mil millones de filas irrelevantes y tomará 6 veces más de lo necesario.

Mi mejor de los casos es una solución que extrae solo una línea del archivo sin leer ninguna de las otras filas del archivo, pero no puedo pensar en cómo lograría esto en Bash.

Para los propósitos de mi cordura, no voy a intentar leer las 500,000,000 de líneas completas que necesitaría para mi propio problema. En su lugar, estaré intentando 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 evaluar cada comando.

Base

Primero veamos cómo la solución de tail head :

$ time head -50000000 myfile.ascii | tail -1 pgm_icnt = 0 real 1m15.321s

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ían ~ 12.5 minutos.

cortar

Tengo dudas de esto, pero vale la pena intentarlo:

$ time cut -f50000000 -d$''/n'' myfile.ascii pgm_icnt = 0 real 5m12.156s

Este tomó 00: 05: 12.156 para correr, ¡que 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 antes de parar, pero a pesar de esto no parece ser una solución viable al problema.

AWK

Solo ejecuté la solución con la exit porque no iba a esperar a que se ejecutara el archivo completo:

$ time awk ''NR == 50000000 {print; exit}'' myfile.ascii pgm_icnt = 0 real 1m16.583s

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 esta velocidad, si se hubiera excluido el comando de salida, ¡probablemente hubiera tomado alrededor de ~ 76 minutos leer todo el archivo!

Perl

También corrí la solución Perl existente:

$ time perl -wnl -e ''$.== 50000000 && print && exit;'' myfile.ascii pgm_icnt = 0 real 1m13.146s

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 el total de 500,000,000 probablemente tomaría unos 12 minutos.

sed

La respuesta principal en la pizarra, aquí está mi resultado:

$ time sed "50000000q;d" myfile.ascii pgm_icnt = 0 real 1m12.705s

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 mapas

Tengo bash 3.1 y, por lo tanto, no puedo probar la solución de mapfile.

Conclusión

Parece que, en su mayor parte, es difícil mejorar la solución de la tail la head . En el mejor de los casos, la solución sed proporciona un aumento del ~ 3% en la eficiencia.

(porcentajes calculados con la fórmula % = (runtime/baseline - 1) * 100 )

Fila 50,000,000

  1. 00: 01: 12.705 (-00: 00: 02.616 = -3.47%) sed
  2. 00: 01: 13.146 (-00: 00: 02.175 = -2.89%) perl
  3. 00: 01: 15.321 (+00: 00: 00.000 = + 0.00%) head|tail
  4. 00: 01: 16.583 (+00: 00: 01.262 = + 1.68%) awk
  5. 00: 05: 12.156 (+00: 03: 56.835 = + 314.43%) cut

Fila 500,000,000

  1. 00: 12: 07.050 (-00: 00: 26.160) sed
  2. 00: 12: 11.460 (-00: 00: 21.750) perl
  3. 00: 12: 33.210 (+00: 00: 00.000) head|tail
  4. 00: 12: 45.830 (+00: 00: 12.620) awk
  5. 00: 52: 01.560 (+00: 40: 31.650) cut

Fila 3,338,559,320

  1. 01: 20: 54.599 (-00: 03: 05.327) sed
  2. 01: 21: 24.045 (-00: 02: 25.227) perl
  3. 01: 23: 49.273 (+00: 00: 00.000) head|tail
  4. 01: 25: 13.548 (+00: 02: 35.735) awk
  5. 05: 47: 23.026 (+04: 24: 26.246) cut

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.

Dado que las longitudes de las líneas son arbitrarias, todos los bytes del archivo antes de la línea n deben ser leídos. Si tiene un archivo enorme o necesita repetir esta tarea muchas veces, y este proceso lleva mucho tiempo, entonces debería 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 inicio del archivo. También puede crear un archivo de índice separado para acompañar su archivo de texto grande.

por ejemplo, puede crear una lista de posiciones de caracteres para nuevas líneas:

awk ''BEGIN{c=0;print(c)}{c+=length()+1;print(c+1)}'' file.txt > file.idx

luego lea con tail , que en realidad seek s directamente al punto apropiado en el archivo!

por ejemplo, para obtener la línea 1000:

tail -c +$(awk ''NR=1000'' file.idx) file.txt | head -1

  • Es posible que esto no funcione con caracteres de 2 bytes / multibyte, ya que awk es "consciente de los caracteres" pero la cola no.
  • No he probado esto contra un archivo grande.
  • También vea esta respuesta .
  • Alternativamente, ¡divide tu archivo en archivos más pequeños!

Una de las formas posibles:

sed -n ''NUM{p;q}''

Tenga en cuenta que sin el comando q , si el archivo es grande, sed continúa trabajando, lo que ralentiza el cálculo.


Usando lo que otros mencionaron, quería que esta fuera una función rápida y elegante en mi shell bash.

Crear un archivo: ~/.functions

Añade a ello los contenidos:

getline() { line=$1 sed $line''q;d'' $2 }

Luego agrega esto a tu ~/.bash_profile :

source ~/.functions

Ahora, cuando abre una nueva ventana de bash, puede llamar a la función así:

getline 441 myfile.txt


Wow, todas las posibilidades!

Prueba esto:

sed -n "${lineNum}p" $file

o uno de estos dependiendo de tu versión de Awk:

awk -vlineNum=$lineNum ''NR == lineNum {print $0}'' $file awk -v lineNum=4 ''{if (NR == lineNum) {print $0}}'' $file awk ''{if (NR == lineNum) {print $0}}'' lineNum=$lineNum $file

( Puede que tenga que probar el nawk o gawk ).

¿Hay 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 el más fácil de usar.


head y el tubo con la tail serán lentos para un archivo enorme. Yo sugeriría que sea así:

sed ''NUMq;d'' file

Donde NUM es el número de la línea que desea imprimir; así, por ejemplo, sed ''10q;d'' file para imprimir la décima línea de file .

Explicación:

NUMq se NUMq inmediatamente cuando el número de línea sea NUM .

d borrará la línea en lugar de imprimirla; esto se inhibe en la última línea porque q hace que el resto del script se omita al salir.

Si tiene NUM en una variable, querrá usar comillas dobles en lugar de una sola:

sed "${NUM}q;d" file



echo <filename> | head <n>

Donde n es el número de línea que queremos imprimir.


sed -n ''2p'' < file.txt

imprimirá 2ª línea

sed -n ''2011p'' < file.txt

Línea 2011

sed -n ''10,33p'' < file.txt

línea 10 hasta línea 33

sed -n ''1p;3p'' < file.txt

1ª y 3ª línea

y así...

Para añadir líneas con sed, puedes verificar esto:

sed: insertar una línea en una posición determinada