Eliminar archivos temporales creados en una salida de bash inesperada

89

Estoy creando archivos temporales a partir de un script bash. Los estoy eliminando al final del procesamiento, pero dado que el script se está ejecutando durante bastante tiempo, si lo mato o simplemente CTRL-C durante la ejecución, los archivos temporales no se eliminan.
¿Hay alguna forma de detectar esos eventos y limpiar los archivos antes de que finalice la ejecución?

Además, ¿existe algún tipo de práctica recomendada para el nombre y la ubicación de esos archivos temporales?
Actualmente no estoy seguro entre usar:

TMP1=`mktemp -p /tmp`
TMP2=`mktemp -p /tmp`
...

y

TMP1=/tmp/`basename $0`1.$$
TMP2=/tmp/`basename $0`2.$$
...

¿O tal vez hay mejores soluciones?

skinp
fuente

Respuestas:

97

Puede establecer una " trampa " para ejecutar al salir o en un control-c para limpiar.

trap "{ rm -f $LOCKFILE; }" EXIT

Alternativamente, uno de mis unix-isms favoritos es abrir un archivo y luego eliminarlo mientras aún lo tienes abierto. El archivo permanece en el sistema de archivos y puede leerlo y escribirlo, pero tan pronto como se cierre el programa, el archivo desaparecerá. Sin embargo, no estoy seguro de cómo harías eso en bash.

Por cierto: un argumento que daré a favor de mktemp en lugar de usar su propia solución: si el usuario anticipa que su programa va a crear archivos temporales enormes, es posible que desee configurarlo TMPDIRen un lugar más grande, como / var / tmp. mktemp reconoce eso, su solución hecha a mano (segunda opción) no lo hace. Yo uso con frecuencia TMPDIR=/var/tmp gvim -d foo bar, por ejemplo.

Paul Tomblin
fuente
8
Con Bash, exec 5<>$TMPFILEdescriptor de archivo lazos 5 a $ TMPFILE como lectura y escritura, y se puede utilizar <&5, >&5y /proc/$$/fd/5(Linux) a partir de entonces. El único problema es que Bash carece de seekfunción ...
ephemient
Acepté su respuesta ya que el enlace que proporcionó es lo que mejor explica lo que necesitaba. Gracias
skinp
4
Un par de notas sobre trap: no hay forma de atrapar SIGKILL(por diseño, ya que termina inmediatamente la ejecución). Entonces, si eso pudiera suceder, tenga un plan alternativo (como tmpreaper). En segundo lugar, las trampas no son acumulativas: si tiene más de una acción que realizar, todas deben estar en el trapcomando. Una forma de hacer frente a múltiples acciones de limpieza es definir una función (y se puede redefinir como los fondos de su programa, si es necesario) y la referencia que: trap cleanup_function EXIT.
Toby Speight
1
Tuve que usar trap "rm -f $LOCKFILE" EXITo obtendría un final inesperado de error de archivo.
Jaakko
3
Shellcheck advirtió el uso de comillas simples, que la expresión se expandiría 'ahora' con comillas dobles en lugar de más tarde cuando se invoca la trampa.
LaFayette
110

Por lo general, creo un directorio en el que colocar todos mis archivos temporales, e inmediatamente después, creo un controlador EXIT para limpiar este directorio cuando sale el script.

MYTMPDIR=$(mktemp -d)
trap "rm -rf $MYTMPDIR" EXIT

Si coloca todos sus archivos temporales debajo $MYTMPDIR, todos se eliminarán cuando su secuencia de comandos salga en la mayoría de las circunstancias. Sin embargo, matar un proceso con SIGKILL (kill -9) mata el proceso de inmediato, por lo que su controlador EXIT no se ejecutará en ese caso.

Chris AtLee
fuente
27
+1 Definitivamente usa una trampa en EXIT, no el tonto TERM / INT / HUP / cualquier otra cosa que se te ocurra. Sin embargo, recuerde que debe citar sus Expansiones de parámetros y me gustaría también recomendar sola cita la trampa: trampa 'rm-rf '$ TMPDIR'' SALIR
lhunath
7
Comillas simples, porque entonces su trampa seguirá funcionando si más adelante en su script decide limpiar y cambiar TMPDIR debido a las circunstancias.
lhunath
1
@AaronDigulla ¿Por qué es importante $ () frente a las comillas invertidas?
Ogre Psalm33
3
@ OgrePsalm33: stackoverflow.com/questions/4708549/…
Aaron Digulla
3
El código de @AlexanderTorstling siempre debe estar entre comillas simples para evitar que la inyección resulte en la ejecución de código arbitrario. Si expande los datos en un código bash STRING, entonces esos datos ahora pueden hacer cualquier cosa que haga el código, lo que da como resultado errores inocentes en espacios en blanco, pero también errores destructivos, como limpiar su hogar por razones extrañas o introducir agujeros de seguridad. Tenga en cuenta que trap toma una cadena de código bash que se evaluará como está más adelante. Entonces, más adelante, cuando se active la trampa, las comillas simples desaparecerán y solo quedarán las comillas dobles sintácticas.
lhunath
25

Desea utilizar el comando trap para manejar la salida del script o señales como CTRL-C. Consulte la Wiki de Greg para obtener más detalles.

Para sus archivos temporales, usar basename $0es una buena idea, además de proporcionar una plantilla que proporcione espacio para suficientes archivos temporales:

tempfile() {
    tempprefix=$(basename "$0")
    mktemp /tmp/${tempprefix}.XXXXXX
}

TMP1=$(tempfile)
TMP2=$(tempfile)

trap 'rm -f $TMP1 $TMP2' EXIT
Brian Campbell
fuente
1
No atrape en TERM / INT. Trampa en EXIT. Tratar de predecir la condición de salida en función de las señales recibidas es una tontería y definitivamente no es un desastre.
lhunath
3
Punto menor: use $ () en lugar de comillas invertidas individuales. Y ponga comillas dobles alrededor de $ 0 porque podría contener espacios.
Aaron Digulla
Bueno, las comillas inversas funcionan bien en este comentario, pero ese es un punto justo, es bueno tener el hábito de usar $(). También se agregaron las comillas dobles.
Brian Campbell
1
Puede reemplazar toda su subrutina con solo TMP1 = $ (tempfile -s "XXXXXX")
Ruslan Kabalin
4
@RuslanKabalin No todos los sistemas tienen un tempfilecomando, mientras que todos los sistemas modernos razonables que conozco tienen un mktempcomando.
Brian Campbell
9

Solo tenga en cuenta que la respuesta elegida es bashism, lo que significa solución como

trap "{ rm -f $LOCKFILE }" EXIT

funcionaría solo en bash (no detectará Ctrl + c si shell es dasho clásico sh), pero si desea compatibilidad, aún debe enumerar todas las señales que desea atrapar.

También tenga en cuenta que cuando el script sale de la trampa para la señal "0" (también conocido como EXIT) siempre se realiza, lo que resulta en una doble ejecución del trapcomando.

Esa es la razón para no apilar todas las señales en una línea si hay una señal de SALIDA.

Para comprenderlo mejor, consulte el siguiente script que funcionará en diferentes sistemas sin cambios:

#!/bin/sh

on_exit() {
  echo 'Cleaning up...(remove tmp files, etc)'
}

on_preExit() {
  echo
  echo 'Exiting...' # Runs just before actual exit,
                    # shell will execute EXIT(0) after finishing this function
                    # that we hook also in on_exit function
  exit 2
}


trap on_exit EXIT                           # EXIT = 0
trap on_preExit HUP INT QUIT TERM STOP PWR  # 1 2 3 15 30


sleep 3 # some actual code...

exit 

Esta solución le dará más control, ya que puede ejecutar parte de su código en la aparición de la señal real justo antes de la salida final ( preExitfunción) y, si es necesario, puede ejecutar algún código en la señal de SALIDA real (etapa final de salida)

Alex
fuente
4

La alternativa de usar un nombre de archivo predecible con $$ es un enorme agujero de seguridad y nunca, nunca, nunca debería pensar en usarlo. Incluso si es solo un simple script personal en su PC de un solo usuario. Es un hábito muy malo que no debes adquirir. BugTraq está lleno de incidentes de "archivos temporales inseguros". Consulte aquí , aquí y aquí para obtener más información sobre el aspecto de seguridad de los archivos temporales.

Inicialmente estaba pensando en citar las asignaciones inseguras de TMP1 y TMP2, pero pensándolo bien, probablemente no sería una buena idea .

hlovdal
fuente
Daría si pudiera: +1 por el consejo de seguridad y otro +1 por no citar una mala idea y la referencia
TMG
1

Prefiero usar tempfileque crea un archivo en / tmp de manera segura y no tiene que preocuparse por su nombre:

tmp=$(tempfile -s "your_sufix")
trap "rm -f '$tmp'" exit
Ruslan Kabalin
fuente
Lamentablemente, tempfile es muy intransitable aunque más seguro, por lo que a menudo es mejor evitarlo o al menos emularlo.
lericson
1

No puedo creer que tanta gente asuma que un nombre de archivo no contendrá un espacio. El mundo se colapsará si $ TMPDIR se asigna alguna vez al "directorio temporal".

zTemp=$(mktemp --tmpdir "$(basename "$0")-XXX.ps")
trap "rm -f ${zTemp@Q}" EXIT

Los espacios y otros caracteres especiales como comillas simples y retornos de carro en los nombres de archivos deben considerarse en el código como un requisito de un hábito de programación decente.

Pablo
fuente
+1 Si bien las comillas simples trap 'rm -f "${zTemp}"' EXITmanejan correctamente los espacios y otros caracteres especiales, la solución de esta respuesta no difiere la evaluación de zTemp. Por lo tanto, no hay necesidad de preocuparse por el valor de zTempser modificado más adelante en el guión. Además, zTempse puede declarar local a una función; no necesita ser una variable de secuencia de comandos global.
Robin A. Meade
Las comillas dobles alrededor del RHS de la asignación son innecesarias.
Robin A. Meade
Cabe señalar que las ${parameter@operator}expansiones se agregaron en Bash 4.4 (publicado en septiembre de 2016).
Robin A. Meade
-4

No tiene que molestarse en eliminar esos archivos tmp creados con mktemp. De todos modos, se eliminarán más tarde.

Use mktemp si puede, ya que genera más archivos únicos que el prefijo '$$'. Y parece una forma más multiplataforma de crear archivos temporales y luego ponerlos explícitamente en / tmp.

Mykola Golubyev
fuente
4
¿Eliminado por quién o qué?
innaM
Eliminado por operación | sistema de archivos en sí después de un período de tiempo
Mykola Golubyev
4
¿Magia? ¿Un cronjob? ¿O una máquina Solaris reiniciada?
innaM
Probablemente uno de ellos. Si el archivo temporal no fue eliminado por alguna interrupción (no será muy a menudo) algún día se eliminarán los archivos tmp, por eso llamaron a temp.
Mykola Golubyev
22
No puede, no debe, no debe asumir que algo introducido en / tmp permanecerá allí para siempre; al mismo tiempo, no debe asumir que desaparecerá mágicamente.
innaM