Usando cabeza y cola para tomar diferentes conjuntos de líneas y guardar en el mismo archivo


10

Entonces esto es para la tarea, pero no haré la pregunta específica de la tarea.

Necesito usar cabeza y cola para tomar diferentes conjuntos de líneas de un archivo. Entonces, como las líneas 6-11 y las líneas 19-24, guárdelas en otro archivo. Sé que puedo hacer esto usando append como

head -11 file|tail -6 > file1; head -24 file| tail -6 >> file1. 

Pero no creo que debamos hacerlo.
¿Hay alguna forma específica de combinar los comandos head y tail y luego guardarlos en el archivo?


1
¿Te están pidiendo específicamente que uses heady tail? Si es así, su solución es prácticamente lo mejor que puede hacer. Si tiene permiso para usar otros programas, sedo awkpodría permitir soluciones más agradables (es decir, con menos invocaciones de procesos).
n.st

Sí, nos piden que usemos cabeza y cola. Gracias por su respuesta.
user2709291

Una cosa más que añadir: Usted puede conseguir alrededor de la redirección de la salida añadiendo ( >>) encerrando los dos comandos de paréntesis, a reorientar su producción concatenada: (head -11 file | tail -6; head -24 file | tail -6) > file1. Realmente se reduce a la preferencia personal, que es más agradable.
n.st

Gracias, eso funcionará muy bien. Realmente lo aprecio.
user2709291

Respuestas:


11

Puede hacerlo con headaritmética simple y básica, si agrupa comandos { ... ; }usando una construcción como

{ head -n ...; head -n ...; ...; } < input_file > output_file

donde todos los comandos comparten la misma entrada (gracias @mikeserv ).
Obtener las líneas 6-11 y las líneas 19-24 es equivalente a:

head -n 5 >/dev/null  # dump the first 5 lines to `/dev/null` then
head -n 6             # print the next 6 lines (i.e. from 6 to 11) then
head -n 7 >/dev/null  # dump the next 7 lines to `/dev/null` ( from 12 to 18)
head -n 6             # then print the next 6 lines (19 up to 24)

Entonces, básicamente, ejecutarías:

{ head -n 5 >/dev/null; head -n 6; head -n 7 >/dev/null; head -n 6; } < input_file > output_file

6

Puede usar la { … }construcción de agrupación para aplicar el operador de redirección a un comando compuesto.

{ head -n 11 file | tail -n 6; head -n 24 file | tail -n 6; } >file1

En lugar de duplicar las primeras líneas M + N y mantener solo la última N, puede omitir las primeras líneas M y duplicar la siguiente N. Esto es notablemente más rápido en archivos grandes . Tenga en cuenta que el +Nargumento de tailno es el número de líneas a omitir, sino uno más: es el número de la primera línea a imprimir con líneas numeradas de 1.

{ tail -n +6 file | head -n 6; tail -n +19 file | head -n 6; } >file1

De cualquier manera, el archivo de salida solo se abre una vez, pero el archivo de entrada se recorre una vez para que cada fragmento se extraiga. ¿Qué hay de agrupar las entradas?

{ tail -n +6 | head -n 6; tail -n +14 | head -n 6; } <file >file1

En general, esto no funciona. (Puede funcionar en algunos sistemas, al menos cuando la entrada es un archivo normal). ¿Por qué? Debido al búfer de entrada . La mayoría de los programas, incluidos tail, no leen su entrada byte a byte, sino algunos kilobytes a la vez, porque es más rápido. Entonces taillee unos kilobytes, salta un poco al principio, pasa un poco más heady se detiene, pero lo que se lee se lee y no está disponible para el siguiente comando.

Otro enfoque es utilizar headcanalizado a /dev/nullpara omitir líneas.

{ head -n 5 >/dev/null; head -n 6; head -n 7 >/dev/null; head -n 6; } <file >file1

Una vez más, no se garantiza que funcione, debido al almacenamiento en búfer. Resulta que funciona con el headcomando de GNU coreutils (el que se encuentra en los sistemas Linux no integrados), cuando la entrada es de un archivo normal. Esto se debe a que una vez que esta implementación headha leído lo que quiere, establece la posición del archivo en el primer byte que no generó. Esto no funciona si la entrada es una tubería.

Una forma más sencilla de imprimir varias secuencias de líneas desde un archivo es llamar a una herramienta más generalista como sed o awk . (Esto puede ser más lento, pero solo es importante para archivos extremadamente grandes).

sed -n -e '6,11p' -e '19,24p' <file >file1
sed -e '1,5d' -e '12,18d' -e '24q' <file >file1
awk '6<=NR && NR<=11 || 19<=NR && NR<=24' <file >file1
awk 'NR==6, NR==11; NR==19, NR==24' <file >file1

2
No funciona, es un comportamiento estándar y especificado, aunque ciertamente, como usted dice, una tubería no es una fuente de entrada confiable para la entrada compartida. CONFIGURACIÓN DE LA DESCRIPCIÓN DE LA UTILIDAD : Cuando una utilidad estándar lee un archivo de entrada buscable y finaliza sin un error antes de que llegue al final del archivo, la utilidad se asegurará de que el desplazamiento del archivo en la descripción del archivo abierto se coloque correctamente justo después del último byte procesado por la utilidad.
mikeserv

2

Sé que dijiste que necesitas usar cabeza y cola, pero sed es definitivamente la herramienta más simple para el trabajo aquí.

$ cat foo
a 1 1
a 2 1
b 1 1
a 3 1
c 3 1
c 3 1
$ sed -ne '2,4p;6p' foo
a 2 1
b 1 1
a 3 1
c 3 1

Incluso puede construir los bloques en una cadena con algún otro proceso y ejecutarlo a través de sed.

$ a="2,4p;6p"
$ sed -ne $a foo
a 2 1
b 1 1
a 3 1
c 3 1

-n niega la salida, luego especifica rangos para imprimir con p, con el primer y el último número del rango separados por una coma.

Dicho esto, puede hacer la agrupación de comandos que @don_crissti sugirió, o recorrer el archivo varias veces con la cabeza / cola agarrando un trozo de líneas cada vez que pasa.

$ head -4 foo | tail -3; head -6 foo | tail -1
a 2 1
b 1 1
a 3 1
c 3 1

Cuantas más líneas haya en un archivo y más bloques tenga, más eficiente será el sed.


2

Con sedusted podría hacer:

sed '24q;1,5d;12,18d' <infile >outfile

... Posiblemente se podría tener una solución más eficiente head. Don ya ha demostrado cómo eso podría funcionar muy bien, pero también he estado jugando con eso. Algo que puede hacer para manejar este caso específico:

for   n in 5 6 7 6
do    head -n"$n" >&"$((1+n%2))"
done  <infile >outfile 2>/dev/null

... que llamar head4 veces escrito, ya sea a outfileo /dev/nullen función de si el valor de esa iteración de $nun número par o impar.

Para casos más generales, combiné esto con otras cosas que ya tenía:

somehead()( 
### call it like:
### somehead -[repeat] [-][numlines]* <infile >outfile
    set -e -- "${1#-}" "$@"                             #-e for arg validation
    r=; cd -- "${TMP:-/tmp}"                            #go to tmp
    dd bs=4096 of="$$$$" <&4 2>&3 &                     #dd <in >tmpfile &bg
    until [ -s "$$$$" ]; do :; done                     #wait while tmpfile empty
    exec <"$$$$" 4<&-;   rm "$$$$"                      #<tmpfile; rm tmpfile
    [ "$3${1}0" -ne "$3${2#?}0" ]          ||           #validate args - chk $1
            shift "$(((r=-${1:--1})||1))"; shift        #shift 1||2
    while [ "$(((r+=(_n=1))-1))" -ne 0 ]   &&           #while ! $rptmax &&
          IFS= read -r l                   &&           #      ! EOF     &&
          printf "%.$(($1>0?${#l}+1:0))s" "$l           #      ? printf  do
";  do    for n do [ "${n#-}" -gt 0 ]      || exit      #args all -[nums>0]
          head "-n$((${n#-}-_n))" >&"$((n>(_n=0)?1:3))" #head -n?$1 >?[+-]
    done; done                                          #done and done
)   4<&0 3>/dev/null                                    #4<for dd 3>for head

Esto puede hacer lo tuyo como:

 seq 100 | somehead -1 -5 6 -7 6

... que imprime ...

6
7
8
9
10
11
19
20
21
22
23
24

Espera que su primer argumento sea un recuento repetido con el prefijo a -, o, en su defecto, solo a -. Si se proporciona un conteo, repetirá el patrón de línea dado en los siguientes argumentos tantas veces como se especifique y se detendrá tan pronto como lo haya hecho.

Para cada argumento que sigue, interpretará un entero negativo para indicar un recuento de líneas en el que se debe escribir /dev/nully un entero positivo para indicar un recuento de líneas en el que se debe escribir stdout.

Entonces, en el ejemplo anterior, imprime las primeras 5 líneas en /dev/null, las siguientes 6 en stdout, las siguientes 7 en /dev/nullotra vez y las siguientes 6 una vez más en stdout. Después de haber alcanzado el último de sus argumentos y completar un ciclo completo a través del -1recuento repetido, se cierra. Si el primer argumento hubiera sido -2, habría repetido el proceso una vez más, o -durante el mayor tiempo posible.

Para cada ciclo arg, el whileciclo se procesa una vez. En la parte superior de cada bucle, la primera línea de stdinse lee en la variable de shell $l. Esto es necesario porque while head </dev/null; do :; donese repetirá indefinidamente, headindica en su devolución cuando ha llegado al final del archivo. Por lo tanto, la verificación contra EOF está dedicada ready printfescribirá $lmás una nueva línea stdoutsolo si el segundo argumento es un entero positivo.

La readverificación complica un poco el ciclo porque inmediatamente después de que se llama a otro ciclo, un forciclo que itera sobre args 2-$#como se representa en $ncada iteración de su whileciclo principal . Esto significa que para cada iteración, el primer argumento debe reducirse en uno del valor especificado en la línea de comando, pero todos los demás deben conservar sus valores originales, por lo que el valor del $_nmarcador var se resta de cada uno, pero solo tiene un valor valor mayor que 0 para el primer argumento.

Eso constituye el bucle principal de la función, pero la mayor parte del código está en la parte superior y está destinado a permitir que la función almacene limpiamente incluso una tubería como entrada. Esto funciona llamando primero a un fondo ddpara copiarlo en un archivo tmp en la salida en bloques de 4k por pieza. Luego, la función configura un ciclo de retención, que casi nunca debería completar ni un solo ciclo completo, solo para asegurarse de que ddhaya realizado al menos una sola escritura en el archivo antes de que la función reemplace su stdin con un descriptor de archivo vinculado al archivo tmp y luego inmediatamente desvincula el archivo conrm. Esto permite que la función procese de manera confiable la secuencia sin requerir trampas o de otro modo para la limpieza: tan pronto como la función lo libere en el fd, el archivo tmp dejará de existir porque su único enlace de sistema de archivos con nombre ya se ha eliminado.


0

Use una función bash como esta:

seq 1 30 > input.txt
f(){ head $1 input.txt | tail $2 >> output.txt ;}; f -11 -2; f -24 -3
cat output.txt
10
11
22
23
24

Esto es un poco exagerado en este caso, pero si tus filtros crecen, puede convertirse en una bendición.

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.