¿Se eliminó la línea in situ en un sistema de archivos completo?

11

Debido a un error de aplicación aún no diagnosticado, tengo varios cientos de servidores con un disco lleno. Hay un archivo que se ha llenado con líneas duplicadas, no un archivo de registro, sino un archivo de entorno de usuario con definiciones variables (por lo que no puedo eliminar el archivo).

Escribí un sedcomando simple para verificar las líneas agregadas erróneamente y eliminarlas, y lo probé en una copia local del archivo. Funcionó según lo previsto.

Sin embargo, cuando lo probé en el servidor con el disco lleno, recibí aproximadamente el siguiente error (es de memoria, no copiar y pegar):

sed: couldn't flush /path/to/file/sed8923ABC: No space left on deviceServerHostname

Por supuesto, que no queda espacio. ¡Es por eso que estoy tratando de eliminar cosas! (El sedcomando que estoy usando reducirá un archivo de más de 4000 líneas a aproximadamente 90 líneas).

Mi sedcomando es solosed -i '/myregex/d' /path/to/file/filename

¿Hay alguna manera de aplicar este comando a pesar del disco lleno?

(Debe ser automatizado, ya que necesito aplicarlo a varios cientos de servidores como una solución rápida).

(Obviamente, el error de la aplicación debe diagnosticarse, pero mientras tanto los servidores no funcionan correctamente ...)


Actualización: La situación que enfrenté se resolvió eliminando algo más que descubrí que podía eliminar, pero todavía me gustaría la respuesta a esta pregunta, que sería útil en el futuro y para otras personas.

/tmpes un no-go; Está en el mismo sistema de archivos.

Antes de liberar espacio en el disco, probé y descubrí que podía eliminar las líneas viabriendo el archivo y ejecutándolo :g/myregex/dy luego guardando los cambios con éxito :wq. Parece que debería ser posible automatizar esto, sin recurrir a un sistema de archivos separado para contener un archivo temporal ... (?)

Comodín
fuente
1
sed -icrea una copia temporal para operar. Sospecho que edsería mejor para esto, aunque no estoy lo suficientemente familiarizado para proscribir una solución real
Eric Renouf
2
Con la edejecución: printf %s\\n g/myregex/d w q | ed -s infilepero tenga en cuenta que algunas implementaciones también usan archivos temporales como sed(puede probar busybox ed - afaik no crea un archivo temporal)
don_crissti
1
@Wildcard - no es confiable w / echo. uso printf. y sedagregue algunos caracteres que suelte en la última línea para evitar perder espacios en blanco finales. Además, su shell debe ser capaz de manejar todo el archivo en una sola línea de comandos. ese es su riesgo, pruebe primero. bashes especialmente malo en eso (creo que es hacer w / espacio de pila?) y puede enfermarte en cualquier momento. los dos sed'si recomendados al menos usarían el buffer de tubería del núcleo para un buen efecto entre ellos, pero el método es bastante similar. su subcomando comando también se truncará filesi el w / in sed es exitoso o no.
mikeserv
1
@Wildcard: inténtalo sed '/regex/!H;$!d;x' <file|{ read v && cat >file;}y, si funciona, lee el resto de mi respuesta.
mikeserv

Respuestas:

10

La -iopción realmente no sobrescribe el archivo original. Crea un nuevo archivo con la salida, luego lo renombra al nombre de archivo original. Como no tiene espacio en el sistema de archivos para este nuevo archivo, falla.

Deberá hacerlo usted mismo en su script, pero cree el nuevo archivo en un sistema de archivos diferente.

Además, si solo está eliminando líneas que coinciden con una expresión regular, puede usar en greplugar de sed.

grep -v 'myregex' /path/to/filename > /tmp/filename && mv /tmp/filename /path/to/filename

En general, rara vez es posible que los programas utilicen el mismo archivo como entrada y salida: tan pronto como comience a escribir en el archivo, la parte del programa que está leyendo el archivo ya no verá el contenido original. Por lo tanto, primero debe copiar el archivo original en algún lugar o escribir en un archivo nuevo y cambiarle el nombre cuando esté listo.

Si no desea utilizar un archivo temporal, puede intentar almacenar en caché el contenido del archivo en la memoria:

file=$(< /path/to/filename)
echo "$file" | grep -v 'myregex' > /path/to/filename
Barmar
fuente
1
¿Conserva los permisos, la propiedad y las marcas de tiempo? Quizás rsync -a --no-owner --no-group --remove-source-files "$backupfile" "$destination"desde aquí
Hastur el
@Hastur: ¿quieres decir que eso sed -ipreserva esas cosas?
mikeserv
2
@Hastur sed -ino conserva ninguna de esas cosas. Acabo de probarlo con un archivo que no soy dueño, pero que se encuentra en un directorio que sí tengo, y me permitió reemplazar el archivo. El reemplazo es de mi propiedad, no del propietario original.
Barmar
1
@ RalphRönnquist Para estar seguro, deberías hacerlo en dos pasos:var=$(< FILE); echo "$FILE" | grep '^"' > FILE
Barmar
1
@Barmar: no funciona, ni siquiera sabe que ha abierto correctamente la entrada. El muy menos que podría hacer es v=$(<file)&& printf %s\\n "$v" >file, sino que ni siquiera usar &&. El autor de la pregunta habla de ejecutarlo en un script, automatizando la sobrescritura de un archivo con una parte de sí mismo. al menos debe validar que puede abrir con éxito la entrada y la salida. Además, la cáscara podría explotar.
mikeserv
4

Así es como sedfunciona. Si se usa con -i(editar en el lugar) sedcrea un archivo temporal con los nuevos contenidos del archivo procesado. Cuando termine sed, reemplaza el archivo de trabajo actual con el archivo temporal. La utilidad no edita el archivo en el lugar . Ese es exactamente el comportamiento de cada editor.

Es como si realizaras la siguiente tarea en un shell:

sed 'whatever' file >tmp_file
mv tmp_file file

En este punto sed, intenta vaciar los datos almacenados en el archivo mencionado en el mensaje de error con la fflush()llamada al sistema:

Para las secuencias de salida, fflush()fuerza una escritura de todos los datos almacenados en el espacio de usuario para la salida dada o la secuencia de actualización a través de la función de escritura subyacente de la secuencia.


Para su problema, veo una solución para montar un sistema de archivos separado (por ejemplo tmpfs, si tiene suficiente memoria o un dispositivo de almacenamiento externo) y mover algunos archivos allí, procesarlos allí y volverlos a mover.

caos
fuente
3

Desde que publiqué esta pregunta, he aprendido que exes un programa compatible con POSIX. Tiene un enlace simbólico casi universal vim, pero de cualquier manera, lo siguiente es (creo) un punto clave sobre los exsistemas de archivos (tomado de la especificación POSIX):

Esta sección utiliza el término buffer de edición para describir el texto de trabajo actual. Ninguna implementación específica está implícita en este término. Todos los cambios de edición se realizan en el búfer de edición, y ningún cambio afectará a ningún archivo hasta que un comando del editor escriba el archivo.

"... afectará a cualquier archivo ..." Creo que poner algo en el sistema de archivos (en absoluto, incluso un archivo temporal) contaría como "afectar a cualquier archivo". ¿Tal vez?*

El estudio cuidadoso de las especificaciones POSIX paraex indicar algunas "trampas" sobre su uso portátil previsto en comparación con los usos comunes con script de los que se exencuentran en línea (que están llenos de vimcomandos específicos).

  1. La implementación +cmdes opcional según POSIX.
  2. Permitir múltiples -copciones también es opcional.
  3. El comando global :g"come" todo hasta la próxima línea nueva no escapada (y, por lo tanto, lo ejecuta después de cada coincidencia encontrada para la expresión regular en lugar de una vez al final). Entonces -c 'g/regex/d | x'solo elimina una instancia y luego sale del archivo.

Entonces, de acuerdo con lo que he investigado, el método compatible con POSIX para editar in situ un archivo en un sistema de archivos completo para eliminar todas las líneas que coinciden con una expresión regular específica, es:

ex -sc 'g/myregex/d
x' /path/to/file/filename

Esto debería funcionar siempre que tenga suficiente memoria para cargar el archivo en un búfer.

* Si encuentra algo que indique lo contrario, por favor, menciónelo en los comentarios.

Comodín
fuente
2
pero ex escribe a tmpfiles ... siempre. está especificado para escribir sus memorias intermedias en el disco periódicamente. Incluso hay comandos especificados para localizar los búferes de archivos tmp en el disco.
mikeserv
@Wildcard Gracias por compartir, me he vinculado a una publicación similar en SO . ¿Supongo que también ex +g/match/d -scx filees compatible con POSIX?
kenorb
@kenorb, no del todo, de acuerdo con mi lectura de las especificaciones; vea mi punto 1 en la respuesta anterior. La cita exacta de POSIX es "La utilidad ex deberá cumplir con las Directrices de sintaxis de la utilidad XBD, excepto por el uso no especificado de '-', y que '+' puede reconocerse como un delimitador de opción así como '-'".
Comodín el
1
No puedo probarlo, excepto apelando al sentido común, pero creo que está leyendo más en esa declaración de la especificación de lo que realmente está allí. Sugiero que la interpretación más segura es que ningún cambio en el búfer de edición afectará a ningún archivo que existiera antes de que comenzara la sesión de edición, o que el usuario haya nombrado. Ver también mis comentarios sobre mi respuesta.
G-Man dice 'Restablecer a Mónica' el
@ G-Man, realmente creo que tienes razón; mi interpretación inicial fue probablemente una ilusión. Sin embargo, dado que la edición del archivo vi funcionó en un sistema de archivos completo, creo que en la mayoría de los casos también funcionaría ex, aunque tal vez no para un archivo descomunal. sed -ino funciona en un sistema de archivos completo, independientemente del tamaño del archivo.
Comodín
2

¡Usa la pipa, Luke!

Leer archivo | filtro | respóndeme

sed 's/PATTERN//' BIGFILE | dd of=BIGFILE conv=notrunc

en este caso sedno crea un nuevo archivo y solo envía una salida canalizada a la ddque se abre el mismo archivo . Por supuesto, uno puede usar grepen casos particulares

grep -v 'PATTERN' BIGFILE | dd of=BIGFILE conv=notrunc

luego truncar el resto.

dd if=/dev/null of=BIGFILE seek=1 bs=BYTES_OF_SED_OUTPUT
Leben Gleben
fuente
1
¿Notó la parte del "sistema de archivos completo" de la pregunta?
Comodín el
1
@Wildcard, ¿ sedsiempre usa archivos temporales? grepde todos modos no
Leben Gleben
Esto parece una alternativa al spongecomando. Sí, sedcon -ililke siempre crea archivos "seduyUdmw" con 000 derechos.
Pablo A
1

Como se señaló en otras respuestas, sed -ifunciona copiando el archivo a un nuevo archivo en el mismo directorio , haciendo cambios en el proceso y luego moviendo el nuevo archivo sobre el original. Por eso no funciona.  ed(el editor de línea original) funciona de manera similar, pero la última vez que lo verifiqué, lo usa /tmppara el archivo scratch. Si /tmpestá en un sistema de archivos diferente del que está lleno, edpuede hacer el trabajo por usted.

Pruebe esto (en su indicador de shell interactivo):

$ ed / ruta / a / archivo / nombre de archivo
PAG
g / myregex / d
w
q

El P(que es una P mayúscula ) no es estrictamente necesario. Se enciende provocando; sin ella, estás trabajando en la oscuridad, y algunas personas encuentran esto desconcertante. El wy qson w rito y q uit.

edEs conocido por el diagnóstico críptico. Si en algún momento muestra algo más que el aviso (que es *) o algo que es claramente una confirmación de operación exitosa ( especialmente si contiene un ?), no escriba el archivo (con w). Solo salga ( q). Si no te deja salir, intenta decirlo de qnuevo.

Si su /tmpdirectorio está en el sistema de archivos que está lleno (o si su sistema de archivos también está lleno), intente encontrar espacio en alguna parte. el caos mencionó el montaje de un tmpfs o un dispositivo de almacenamiento externo (por ejemplo, una unidad flash); pero, si tiene varios sistemas de archivos y no están todos llenos, simplemente puede usar uno de los otros existentes. caos sugiere copiar los archivos al otro sistema de archivos, editarlos allí (con sed) y luego volver a copiarlos. En este punto, esa puede ser la solución más simple. Pero una alternativa sería crear un directorio grabable en un sistema de archivos que tenga algo de espacio libre, establecer la variable de entorno TMPDIRpara que apunte a ese directorio y luego ejecutarlo ed. (Divulgación: no estoy seguro de si esto funcionará, pero no puede doler).

Una vez que empiece a edtrabajar, puede automatizar esto haciendo

nombre de archivo ed << EOF
g / myregex / d
w
q
EOF

en un guion O , como lo sugiere don_crissti.printf '%s\n' 'g/myregex/d' w q | ed -s filename

G-Man dice 'restablecer a Mónica'
fuente
Hmmm ¿Se puede hacer lo mismo (con edo con ex) de modo que se use la memoria en lugar de un sistema de archivos separado? Eso es lo que realmente estaba buscando (y la razón por la que no he aceptado una respuesta)
Comodín el
Hmm Esto puede ser más complicado de lo que me di cuenta. Estudié la fuente de edhace muchos años. Todavía había cosas como las computadoras de 16 bits, en las que los procesos se limitaban a un espacio de direcciones de 64K (!), Por lo que la idea de un editor que leyera todo el archivo en la memoria no fue un comienzo. Desde entonces, por supuesto, la memoria se ha vuelto más grande, pero también lo han hecho los discos y los archivos. Como los discos son tan grandes, las personas no sienten la necesidad de lidiar con la contingencia de /tmpquedarse sin espacio. Acabo de echar un vistazo rápido al código fuente de una versión reciente de ed, y todavía parece ... (Cont.)
G-Man dice 'Reinstate Monica' el
(Cont.) ... para implementar el "búfer de edición" como un archivo temporal, incondicionalmente, y no puedo encontrar ninguna indicación de que alguna versión de ed( exoo vi) ofrezca una opción para mantener el búfer en la memoria.  Por otro lado, Edición de texto con ed y vi - Capítulo 11: Procesamiento de texto - Parte II: Explorando Red Hat Linux - Red Hat Linux 9 Secretos profesionales - Los sistemas Linux dicen que edel búfer de edición reside en la memoria, ... (Cont. )
G-Man dice 'Restablecer a Mónica' el
(Cont.) ... y UNIX Document Processing and Typesetting by Balasubramaniam Srinivasan dice lo mismo vi(que es el mismo programa que ex). Creo que solo están usando una redacción descuidada e imprecisa, pero, si está en Internet (o en forma impresa), debe ser cierto, ¿verdad? Pagas tu dinero y tomas tu elección.
G-Man dice 'Restablecer a Mónica' el
Pero de todos modos, he agregado una nueva respuesta.
G-Man dice 'Restablecer a Mónica' el
1

Puede truncar el archivo con bastante facilidad si puede obtener el recuento de bytes a su desplazamiento y sus líneas se producen desde un punto inicial hasta el final.

o=$(sed -ne'/regex/q;p' <file|wc -c)
dd if=/dev/null of=file bs="$o" seek=1

O bien, si ${TMPDIR:-/tmp}está en algún otro sistema de archivos, tal vez:

{   cut -c2- | sed "$script" >file
} <file <<FILE
$(paste /dev/null -)
FILE

Porque (la mayoría) los shells ponen sus documentos aquí en un archivo temporal eliminado. Es perfectamente seguro siempre que el <<FILEdescriptor se mantenga de principio a fin y ${TMPDIR:-/tmp}tenga tanto espacio como sea necesario.

Los shells que no usan archivos temporales usan tuberías, por lo que no es seguro usarlos de esta manera. Estos depósitos son típicamente ashderivados como busybox, dash, BSD sh- zsh, bash, ksh, y la cáscara de Bourne, sin embargo, todos los archivos temporales de uso.

aparentemente escribí un pequeño programa de shell en julio pasado para hacer algo como esto


Si /tmpno es viable, entonces, siempre que pueda guardar el archivo en la memoria, algo así como ...

sed 'H;$!d;x' <file | { read v &&
sed "$script" >file;}

... como un caso general, al menos se aseguraría de que el archivo estuviera completamente protegido por el primer sedproceso antes de intentar truncar el archivo de entrada / salida.

Una solución más específica y eficiente podría ser:

sed '/regex/!H;$!d;x' <file|{ read v && cat >file;}

... porque no molestaría las líneas de almacenamiento en búfer que querías eliminar de todos modos.

Una prueba del caso general:

{   nums=/tmp/nums
    seq 1000000 >$nums
    ls -lh "$nums"
    wc -l  "$nums"
    sed 'H;$!d;x' <$nums | { read script &&  ### read always gets a blank
    sed "$script" >$nums;}
    wc -l  "$nums"
    ls -lh "$nums"
}

-rw-r--r-- 1 mikeserv mikeserv 6.6M Dec 22 20:26 /tmp/nums
1000000 /tmp/nums
1000000 /tmp/nums
-rw-r--r-- 1 mikeserv mikeserv 6.6M Dec 22 20:26 /tmp/nums
mikeserv
fuente
Confieso que no había leído su respuesta en detalle antes, porque comienza con soluciones inviables (para mí) que involucran el conteo de bytes (diferente entre cada uno de los muchos servidores) y /tmpque está en el mismo sistema de archivos. Me gusta tu sedversión dual . Creo que una combinación de Barmar y su respuesta probablemente sería mejor, algo así como: myvar="$(sed '/myregex/d' < file)" && [ -n "$myvar" ] && echo "$myvar" > file ; unset myvar (Para este caso, no me importa preservar las nuevas líneas finales).
Comodín
2
@Wildcard: eso podría ser. pero no deberías usar el shell como una base de datos. el sed| catLo anterior nunca abre la salida a menos que sedya haya almacenado en el búfer todo el archivo y esté listo para comenzar a escribir todo en la salida. Si intenta almacenar el archivo en el búfer y falla, readno tiene éxito porque encuentra EOF en la |tubería antes de leer su primera línea nueva y, por lo tanto, cat >out nunca sucede hasta el momento de escribirlo de la memoria por completo. un desbordamiento o algo similar simplemente falla. También toda la tubería devuelve el éxito o el fracaso cada vez. almacenarlo en una var es simplemente más arriesgado.
mikeserv
@Wildcard: si realmente también lo quisiera en una variable, creo que id lo haría así: file=$(sed '/regex/!H;$!d;x' <file | read v && tee file) && cmp - file <<<"$file" || shiteentonces el archivo de salida y el var se escribirían simultáneamente, lo que haría una copia de seguridad efectiva o una , que es la única razón por la que querría complicar las cosas más de lo necesario.
mikeserv
@mikeserv: Estoy lidiando con el mismo problema que el OP ahora y encuentro que su solución es realmente útil. Pero no entiendo el uso de read scripty read ven su respuesta. Si puedes dar más detalles al respecto, te lo agradeceré mucho, ¡gracias!
sylye
1
@sylye: $scriptes el sedscript que usarías para apuntar a cualquier parte de tu archivo que quisieras; es el script que te da el resultado final que deseas en la transmisión. ves solo un marcador de posición para una línea vacía. en un bashshell no es necesario porque bashusará automáticamente la $REPLYvariable de shell en su lugar si no especifica uno, pero POSIXly siempre debe hacerlo. Me alegro de que lo encuentres útil, por cierto. Suerte con ello. im mikeserv @ gmail si necesitas algo en profundidad. Debería tener una computadora nuevamente en unos días
mikeserv
0

Esta respuesta toma prestadas ideas de esta otra respuesta y de esta otra respuesta, pero se basa en ellas, creando una respuesta que es más generalmente aplicable:

num_bytes = $ (sed '/ myregex / d' / path / to / file / filename | wc -c)
sed '/ myregex / d' / path / to / file / filename 1 <> / path / to / file / filename 
dd if = / dev / null of = / path / to / file / filename bs = "$ num_bytes" busque = 1

La primera línea ejecuta el sedcomando con salida escrita en salida estándar (y no en un archivo); específicamente, a una tubería wcpara contar los personajes. La segunda línea también ejecuta el sedcomando con la salida escrita en la salida estándar, que, en este caso, se redirige al archivo de entrada en modo lectura / escritura de sobrescritura (sin truncar), que se trata aquí . Esto es algo peligroso de hacer; es seguro solo cuando el comando de filtro nunca aumenta la cantidad de datos (texto); es decir, por cada n bytes que lee, escribe n o menos bytes. Esto es, por supuesto, cierto para el sed '/myregex/d'comando; por cada línea que lee, escribe exactamente la misma línea, o nada. (Otros ejemplos:s/foo/fu/o s/foo/bar/estaría a salvo, pero s/fu/foo/y s/foo/foobar/no lo estaría).

Por ejemplo:

$ cat filename
It was
a dark and stormy night.
$ sed '/was/d' filename 1<> filename
$ cat filename
a dark and stormy night.
night.

porque estos 32 bytes de datos:

I  t     w  a  s \n  a     d  a  r  k     a  n  d     s  t  o  r  m  y     n  i  g  h  t  . \n

se sobrescribió con estos 25 caracteres:

a     d  a  r  k     a  n  d     s  t  o  r  m  y     n  i  g  h  t  . \n

dejando los siete bytes night.\nrestantes al final.

Finalmente, el ddcomando busca el final de los nuevos datos depurados (byte 25 en este ejemplo) y elimina el resto del archivo; es decir, trunca el archivo en ese punto.


Si, por alguna razón, el 1<>truco no funciona, puedes hacerlo

sed '/ myregex / d' / ruta / a / archivo / nombre de archivo | dd de = / ruta / a / archivo / nombre de archivo conv = notrunc

Además, tenga en cuenta que, siempre que todo lo que esté haciendo sea eliminar líneas, todo lo que necesita es grep -v myregex(como señaló Barmar ).

G-Man dice 'restablecer a Mónica'
fuente
-3

sed -i 'd' / ruta / a / archivo / nombre de archivo

Chiranjeeb
fuente
1
¡Hola! Sería mejor explicar con tanto detalle como sea relevante cómo funciona su solución y responder la pregunta.
dhag
2
Esta es una terrible no respuesta. (a) Fallará en un sistema de archivos completo, al igual que mi comando original; (b) Si tuvo éxito, vaciaría TODO el archivo, en lugar de solo las líneas que coinciden con mi expresión regular.
Comodín el