Tengo un archivo bastante grande (35 Gb), y me gustaría filtrar este archivo in situ (es decir, no tengo suficiente espacio en disco para otro archivo), específicamente quiero grep e ignorar algunos patrones, ¿hay alguna manera de hacer esto sin usar otro archivo?
Digamos que quiero filtrar todas las líneas que contienen, foo:
por ejemplo ...
Respuestas:
En el nivel de llamada del sistema esto debería ser posible. Un programa puede abrir su archivo de destino para escribir sin truncarlo y comenzar a escribir lo que lee de stdin. Al leer EOF, el archivo de salida se puede truncar.
Como está filtrando líneas desde la entrada, la posición de escritura del archivo de salida siempre debe ser menor que la posición de lectura. Esto significa que no debe corromper su entrada con la nueva salida.
Sin embargo, encontrar un programa que haga esto es el problema.
dd(1)
tiene la opciónconv=notrunc
que no trunca el archivo de salida al abrir, pero tampoco se trunca al final, dejando el contenido del archivo original después del contenido grep (con un comando comogrep pattern bigfile | dd of=bigfile conv=notrunc
)Como es muy simple desde la perspectiva de una llamada al sistema, escribí un pequeño programa y lo probé en un pequeño sistema de archivos de bucle completo (1MiB). Hizo lo que quería, pero primero desea probar esto con otros archivos primero. Siempre será arriesgado sobrescribir un archivo.
overwrite.c
Lo usarías como:
Principalmente publico esto para que otros lo comenten antes de que lo pruebes. Quizás alguien más sepa de un programa que haga algo similar que esté más probado.
fuente
grep
no generará más datos de los que lee, la posición de escritura siempre debe estar detrás de la posición de lectura. Incluso si está escribiendo al mismo ritmo que la lectura, todavía estará bien. Pruebe rot13 con esto en lugar de grep, y luego nuevamente. md5sum el antes y el después y verás que es lo mismo.dd
, pero es engorroso.Puede usar
sed
para editar archivos en su lugar (pero esto crea un archivo temporal intermedio):Para eliminar todas las líneas que contienen
foo
:Para mantener todas las líneas que contienen
foo
:fuente
$HOME
se podrá escribir, pero/tmp
será de solo lectura (por defecto). Por ejemplo, si tiene Ubuntu y ha arrancado en la Consola de recuperación, este suele ser el caso. Además, el operador de documento aquí<<<
tampoco funcionará allí, ya que requiere/tmp
ser r / w porque también escribirá un archivo temporal allí. (cf. esta pregunta incluye unastrace
salida 'd)Asumiré que su comando de filtro es lo que llamaré un filtro de reducción de prefijo , que tiene la propiedad de que el byte N en la salida nunca se escribe antes de haber leído al menos N bytes de entrada.
grep
tiene esta propiedad (siempre que solo filtre y no haga otras cosas como agregar números de línea para coincidencias). Con dicho filtro, puede sobrescribir la entrada a medida que avanza. Por supuesto, debe asegurarse de no cometer ningún error, ya que la parte sobrescrita al comienzo del archivo se perderá para siempre.La mayoría de las herramientas de Unix solo dan la opción de agregar un archivo o truncarlo, sin posibilidad de sobrescribirlo. La única excepción en la caja de herramientas estándar es
dd
, que se puede decir que no trunque su archivo de salida. Entonces, el plan es filtrar el comandodd conv=notrunc
. Esto no cambia el tamaño del archivo, por lo que también tomamos la longitud del nuevo contenido y truncamos el archivo a esa longitud (nuevamente condd
). Tenga en cuenta que esta tarea es inherentemente no robusta: si se produce un error, usted es el único.Puedes escribir Perl con un equivalente áspero. Aquí hay una implementación rápida que no intenta ser eficiente. Por supuesto, es posible que también desee realizar su filtrado inicial directamente en ese idioma.
fuente
Con cualquier caparazón tipo Bourne:
Por alguna razón, parece que las personas tienden a olvidarse de ese operador de redirección de lectura y escritura estándar de 40 años year .
Abrimos
bigfile
en modo lectura + escritura y (lo que más importa aquí) sin truncamientostdout
mientrasbigfile
está abierto (por separado) encat
'sstdin
. Después de quegrep
haya terminado, y si ha eliminado algunas líneas,stdout
ahora apunta a algún lugar dentrobigfile
, debemos deshacernos de lo que está más allá de este punto. De ahí elperl
comando que trunca el archivo (truncate STDOUT
) en la posición actual (tal como lo devuelvetell STDOUT
).(el
cat
es para GNUgrep
que de otro modo se queja si stdin y stdout apuntan al mismo archivo).¹ Bueno, si bien
<>
estuvo en el shell Bourne desde el principio a fines de los años setenta, inicialmente no estaba documentado y no se implementó correctamente . No estaba en la implementación original deash
1989 y, si bien es unsh
operador de redireccionamiento POSIX (desde principios de los 90, ya que POSIXsh
se basa en loksh88
que siempre lo tuvo), no se agregó a FreeBSD,sh
por ejemplo, hasta 2000, por lo que es portátil durante 15 años. viejo es probablemente más exacto. También tenga en cuenta que el descriptor de archivo predeterminado cuando no se especifica está<>
en todos los shells, excepto queksh93
cambió de 0 a 1 en ksh93t + en 2010 (rompiendo la compatibilidad con versiones anteriores y el cumplimiento de POSIX)fuente
perl -e 'truncate STDOUT, tell STDOUT'
? Funciona para mí sin incluir eso. ¿Alguna forma de lograr lo mismo sin usar Perl?redirection "<>" fixed and documented (used in /etc/inittab f.i.).
que es una pista.Aunque esta es una vieja pregunta, me parece que es una pregunta perenne, y hay disponible una solución más general y más clara de lo que se ha sugerido hasta ahora. Crédito donde se debe el crédito: no estoy seguro de haberlo ideado sin tener en cuenta la mención de Stéphane Chazelas del
<>
operador de actualización.Abrir un archivo para actualizarlo en un shell Bourne es de utilidad limitada. El shell no le da forma de buscar en un archivo, y no tiene forma de establecer su nueva longitud (si es más corta que la anterior). Pero eso se soluciona fácilmente, así que me sorprende que no esté entre las utilidades estándar
/usr/bin
.Esto funciona:
Como hace esto (punta de sombrero para Stéphane):
(Estoy usando GNU grep. Quizás algo ha cambiado desde que escribió su respuesta).
Excepto que no tienes / usr / bin / ftruncate . Para un par de docenas de líneas de C, puede ver a continuación. Esta utilidad ftruncate trunca un descriptor de archivo arbitrario a una longitud arbitraria, por defecto a la salida estándar y la posición actual.
El comando anterior (primer ejemplo)
T
para actualización. Al igual que con open (2), al abrir el archivo de esta manera, el desplazamiento actual se sitúa en 0.T
normalmente, y el shell redirige su salida aT
través del descriptor 4.El subshell luego sale, cerrando el descriptor 4. Aquí está ftruncate :
NB, ftruncate (2) no es portátil cuando se usa de esta manera. Para una generalidad absoluta, lea el último byte escrito, vuelva a abrir el archivo O_WRONLY, busque, escriba el byte y cierre.
Dado que la pregunta tiene 5 años, voy a decir que esta solución no es obvia. Se aprovecha el exec para abrir un nuevo descriptor y el
<>
operador, los cuales son arcanos. No puedo pensar en una utilidad estándar que manipule un inodo por descriptor de archivo. (La sintaxis podría serftruncate >&4
, pero no estoy seguro de que sea una mejora). Es considerablemente más corta que la respuesta exploratoria competente de Camh. Es solo un poco más claro que Stéphane's, en mi opinión, a menos que te guste Perl más que a mí. Espero que alguien lo encuentre útil.Una forma diferente de hacer lo mismo sería una versión ejecutable de lseek (2) que informa el desplazamiento actual; la salida podría usarse para / usr / bin / truncate , que algunos Linuxi proporcionan.
fuente
ed
es probablemente la opción correcta para editar un archivo en el lugar:fuente
ed
versiones se comporten de manera diferente ... esto es deman ed
(GNU Ed 1.4) ...If invoked with a file argument, then a copy of file is read into the editor's buffer. Changes are made to this copy and not directly to file itself.
ed
Reconozco que no es una solución gool para editar archivos de 35 GB, ya que el archivo se lee en un búfer.!
), por lo que puede tener algunos trucos más interesantes bajo la manga.ed
trunca el archivo y lo reescribe. Por lo tanto, esto no alterará los datos en el disco en el lugar como lo desea el OP. Además, no puede funcionar si el archivo es demasiado grande para cargarlo en la memoria.Puede usar un descriptor de archivo de lectura / escritura bash para abrir su archivo (para sobrescribirlo in situ), luego
sed
ytruncate
... pero, por supuesto, nunca permita que sus cambios sean mayores que la cantidad de datos leídos hasta ahora .Aquí está el script (usa: bash variable $ BASHPID)
Aquí está la salida de prueba
fuente
Mapearía el archivo en la memoria, haría todo en el lugar usando punteros char * a memoria desnuda, luego desasignaría el archivo y lo truncaría.
fuente
No exactamente in situ, pero esto podría ser útil en circunstancias similares.
Si el espacio en el disco es un problema, comprima primero el archivo (dado que es texto, esto dará una gran reducción) y luego use sed (o grep, o lo que sea) de la manera habitual en medio de una tubería de descompresión / compresión.
fuente
sed -e '/foo/d' MyFile | gzip -c >MyEditedFile.gz && gzip -dc MyEditedFile.gz >MyFile
Para el beneficio de cualquiera que busque en Google esta pregunta, la respuesta correcta es dejar de buscar características de shell oscuras que corran el riesgo de corromper su archivo para obtener un aumento de rendimiento insignificante, y en su lugar use alguna variación de este patrón:
Solo en la situación extremadamente infrecuente de que esto por alguna razón no sea factible, debe considerar seriamente cualquiera de las otras respuestas en esta página (aunque ciertamente son interesantes de leer). Reconozco que el enigma del OP de no tener espacio en disco para crear un segundo archivo es exactamente una situación así. Aunque incluso entonces, hay otras opciones disponibles, por ejemplo, según lo provisto por @Ed Randall y @Basile Starynkevitch.
fuente
echo -e "$(grep pattern bigfile)" >bigfile
fuente
grepped
datos exceden la longitud de lo que permite la línea de comandos. luego corrompe los datos