¿Cómo truncar el archivo por líneas?

13

Tengo una gran cantidad de archivos, algunos de los cuales son muy largos. Me gustaría truncarlos a un cierto tamaño si son más grandes eliminando el final del archivo. Pero solo quiero eliminar líneas enteras. ¿Cómo puedo hacer esto? Se siente como el tipo de cosa que sería manejada por la cadena de herramientas de Linux, pero no sé cuál es el comando correcto.

Por ejemplo, supongamos que tengo un archivo de 120,000 bytes con líneas de 300 bytes y estoy tratando de truncarlo a 10,000 bytes. Las primeras 33 líneas deben permanecer (9900 bytes) y el resto debe cortarse. No quiero cortar exactamente a 10,000 bytes, ya que eso dejaría una línea parcial.

Por supuesto, los archivos tienen diferentes longitudes y las líneas no tienen la misma longitud.

Idealmente, los archivos resultantes se harían un poco más cortos en lugar de un poco más largos (si el punto de interrupción está en una línea larga), pero eso no es demasiado importante, podría ser un poco más largo si eso fuera más fácil. Me gustaría que los cambios se hicieran directamente a los archivos (bueno, posiblemente el nuevo archivo copiado en otro lugar, el original eliminado y el nuevo archivo movido, pero eso es lo mismo del POV del usuario). Una solución que redirige los datos a un montón de lugares y luego invita a la posibilidad de corromper el archivo y me gustaría evitar eso ...

Charles
fuente
Eliminé mi respuesta ... Supongo que el tamaño del archivo en Bytes no estaba demasiado claro, lo siento. ¿Quizás podría editar su pregunta y aclarar esa parte (por ejemplo, con un ejemplo)?
slhck
@slhck: Lamento ver que pierdes representante solo porque no estaba claro ... déjame ver si puedo solucionarlo.
Charles
No se preocupe, debería haber preguntado, lo siento :)
slhck

Respuestas:

1

La complejidad sed/ wcse puede evitar en respuestas anteriores si awkse usa. Usando el ejemplo proporcionado por OP (que muestra líneas completas antes de 10000 bytes):

awk '{i += (length() + 1); if (i <= 10000) print $ALL}' myfile.txt

También muestra la línea completa que contiene el byte 10000 si ese byte no está al final de la línea:

awk '{i += (length() + 1); print $ALL; if (i >= 10000) exit}' myfile.txt

La respuesta anterior supone:

  1. Los archivos de texto son de terminador de línea Unix ( \n). Para archivos de texto Dos / Windows ( \r\n), cambie length() + 1alength() + 2
  2. El archivo de texto solo contiene caracteres de un solo byte. Si hay caracteres multibyte (como en un entorno unicode), configure el entorno LC_CTYPE=Cpara forzar la interpretación en el nivel de byte.
Abel Cheung
fuente
14

El sedenfoque está bien, pero no es posible recorrer todas las líneas. Si sabe cuántas líneas desea mantener (para tener un ejemplo, uso 99 aquí), puede hacerlo así:

sed -i '100,$ d' myfile.txt

Explicación: sedes un procesador de expresiones regulares. Con la opción -idada, procesa un archivo directamente ("en línea"), en lugar de simplemente leerlo y escribir los resultados en la salida estándar. 100,$solo significa "desde la línea 100 hasta el final del archivo", y le sigue el comando d, que probablemente adivinó correctamente para representar "eliminar". En resumen, el comando significa: "Eliminar todas las líneas desde la línea 100 hasta el final del archivo desde myfile.txt". 100 es la primera línea que se eliminará, ya que desea mantener 99 líneas.

Editar: si, por otro lado, hay archivos de registro donde desea mantener, por ejemplo, las últimas 100 líneas:

[ $(wc -l myfile.txt) -gt 100 ] && sed -i "1,$(($(wc -l myfile.txt|awk '{print $1}') - 100)) d" myfile.txt

Que esta pasando aqui:

  • [ $(wc -l myfile.txt) -gt 100 ]: haga lo siguiente solo si el archivo tiene más de 100 líneas
  • $((100 - $(wc -l myfile.txt|awk '{print $1}'))): calcula el número de líneas para eliminar (es decir, todas las líneas del archivo excepto las (últimas) 100 para mantener)
  • 1, $((..)) d: elimina todas las líneas de la primera a la línea calculada

EDITAR: como la pregunta se acaba de editar para dar más detalles, incluiré esta información adicional con mi respuesta también. Los hechos adicionales son:

  • un tamaño específico permanecerá con el archivo (10,000 bytes)
  • cada línea tiene un tamaño específico en bytes (300 bytes en el ejemplo)

A partir de estos datos, es posible calcular el número de líneas que quedan como "/", lo que con el ejemplo significaría 33 líneas. El término de shell para el cálculo: $((size_to_remain / linesize))(al menos en Linux usando Bash, el resultado es un entero). El comando ajustado ahora leería:

# keep the start of the file (OPs question)
sed -i '34,$ d' myfile.txt
# keep the end of the file (my second example)
[ $(wc -l myfile.txt) -gt 33 ] && sed -i "1,33 d" myfile.txt

Como los tamaños se conocen de antemano, ya no hay necesidad de un cálculo incrustado en el sedcomando. Pero por flexibilidad, dentro de un script de shell uno puede usar variables.

Para el procesamiento condicional basado en el tamaño del archivo, se puede usar la siguiente construcción de "prueba":

[ "$(ls -lk $file | awk ' {print $5}')" -gt 100 ] &&

lo que significa: "si el tamaño de $fileexcede 100kB, haz ..." ( ls -lkenumera el tamaño del archivo en kB en la posición 5, por awklo tanto, se usa para extraer exactamente esto).

Izzy
fuente
El OP quiere cortar el archivo en función de un determinado tamaño de byte, no solo la longitud en términos de líneas. Eliminé mi respuesta que involucra head -n.
slhck
@slhck Gracias por la notificación. Sí, el OP acaba de editar su pregunta para aclarar la intención. Como tiene medios para calcular cuántos bytes tiene cada línea, mi respuesta sigue siendo válida en principio, ya que puede calcular la cantidad de líneas que quedan y luego usar mi enfoque para manejar los archivos. Quizás haga un breve comentario al respecto en mi respuesta.
Izzy
No, los tamaños no se conocen de antemano. Ese fue un ejemplo. Cada archivo tendrá un tamaño diferente y las líneas son de longitud irregular. Algunos archivos no necesitan ser truncados en absoluto.
Charles
Ah, de nuevo ... Bueno, algunas cosas son difíciles de explicar claramente (demasiadas facetas). En cuanto a los archivos que no necesitan truncarse, ¿eso probablemente se base en el tamaño del archivo? Eso puede ser cubierto. Pero si ni siquiera se conoce un tamaño de línea promedio, esta parte se vuelve difícil: no puedo pensar en una solución fácil (sin demasiada sobrecarga) en este momento.
Izzy
Todo lo que se me ocurre actualmente implicaría, por ejemplo, obtener las primeras n líneas, calcular una longitud promedio basada en ellas y usar este valor. ¿Eso te ayudaría?
Izzy
0

Al no encontrar un comando para hacer esto, escribí un script rápido (no probado):

#!/bin/sh

# Usage: $0 glob.* 25000
# where glob.* is a wildcard pattern and 25000 is the maximum number of bytes.

limit=20000
tmp=/tmp/trim
[[ "$2" == +([0-9]) ]] || limit=$2
limit=`expr $len + 1`
for file in $1;
do
    [[ `wc -c $file` -lt $limit ]] && continue
    head -c $file > $tmp
    sed '$d' $tmp
    $tmp > $file
done
Charles
fuente
-1

Puede usar el comando linux sed para eliminar líneas de un archivo. El siguiente comando elimina la última línea de filename.txt:

sed '$d' filename.txt

Con awk o find puede buscar un patrón que coincida con su comando sed. Primero busca con awk o busca los archivos que deseas acortar y luego puedes eliminar las líneas con sed.

kockiren
fuente
-1

Hice algo similar con la cola. Para mantener solo las últimas 10,000 líneas en este caso:

TMP=$(tail -n 10000 /path/to/some/file 2>/dev/null) && echo "${TMP}" > /path/to/some/file
Bill M
fuente