2009-03-26 21 views
60

Estoy creando archivos temporales a partir de un script bash. Los borro al final del proceso, pero dado que el script se está ejecutando durante bastante tiempo, si lo elimino o simplemente CTRL-C durante la ejecución, los archivos temporales no se eliminan.
¿Hay alguna manera de detectar estos eventos y limpiar los archivos antes de que termine la ejecución?Eliminación de archivos temporales creados en la salida de inicio inesperada

Además, ¿hay algún tipo de práctica recomendada para el nombre y la ubicación de esos archivos temporales?
No estoy seguro actualmente entre el uso de:

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

y

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

O tal vez es que hay algunas soluciones mejores?

Respuesta

55

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

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

Alternativamente, uno de mis UNIX-ismos favoritos es para abrir un archivo y, a continuación, eliminarlo, mientras que todavía tiene abierto. El archivo permanece en el sistema de archivos y usted puede leerlo y escribirlo, pero tan pronto como su programa finalice, el archivo desaparecerá. Sin embargo, no estoy seguro de cómo harías eso en bash.

BTW: Un argumento que daré a favor de mktemp en lugar de usar su propia solución: si el usuario anticipa que su programa creará enormes archivos temporales, podría querer establecer TMPDIR en algún lugar más grande, como/var/tmp. mktemp reconoce que su solución enrollada a mano (segunda opción) no lo hace. Con frecuencia uso TMPDIR=/var/tmp gvim -d foo bar, por ejemplo.

+7

Con Bash, 'exec 5 <> $ TMPFILE' vincula el descriptor de archivo 5 a $ TMPFILE como lectura-escritura , y puede usar '<&5', '> & 5', y'/proc/$$/fd/5' (Linux) a partir de entonces. El único problema es que Bash carece de la función 'seek' ... – ephemient

+0

Aceptado tu respuesta ya que el enlace que proporcionaste es lo que explica mejor lo que necesitaba. Gracias – skinp

+3

Un par de notas sobre 'trap': no hay forma de atrapar' SIGKILL' (por diseño, ya que inmediatamente termina la ejecución). Entonces, si eso pudiera suceder, tenga un plan alternativo (como 'tmpreaper'). En segundo lugar, las trampas no son acumulativas: si tiene que realizar más de una acción, todas deben estar en el comando 'trap'. Una forma de hacer frente a múltiples acciones de limpieza es definir una función (y puede redefinirla a medida que avanza el programa, si es necesario) y hacer referencia a eso: 'trap cleanup_function EXIT'. –

-4

No tiene que molestarse en eliminar esos archivos tmp creados con mktemp. Serán eliminados de todos modos más tarde.

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

+4

Eliminado por quién o qué? – innaM

+0

Eliminado por la operación | sistema de archivos en sí después de un período de tiempo –

+4

Magia? ¿Un cronjob? ¿O una máquina Solaris reiniciada? – innaM

16

Desea utilizar el comando trap para manejar la salida del script o señales como CTRL-C. Vea el Advanced Bash Scripting Guide para más detalles.

Para sus TEMPFILES, utilizando basename $0 es una buena idea, así como proporcionar una plantilla que proporciona espacio para suficientes archivos temporales:

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

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

trap 'rm -f $TMP1 $TMP2' EXIT 
+0

No atrapar en TERM/INT. Trampa en EXIT. Tratando de predecir la condición de salida en base a las señales recibidas es tonto y definitivamente no es un cathall. – lhunath

+2

Punto menor: use $() en lugar de solo backticks. Y ponga comillas dobles alrededor de $ 0 porque podría contener espacios. –

+0

Bueno, los backticks funcionan bien en este comentario, pero ese es un buen punto, es bueno tener el hábito de usar '$()'. Se agregaron las comillas dobles también. –

88

Por lo general crear un directorio en el que colocar todos los archivos temporales, e inmediatamente después, cree un controlador EXIT para limpiar este directorio cuando el script salga.

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

Si pones todos los archivos temporales de bajo $MYTMPDIR, entonces todos ellos se eliminarán cuando el script finaliza en la mayoría de las circunstancias. Matar un proceso con SIGKILL (kill -9) mata el proceso de inmediato, por lo que su controlador EXIT no se ejecutará en ese caso.

+20

+1 Definitivamente usa una trampa en EXIT, no es tonto TERM/INT/HUP/lo que sea que puedas pensar. Aunque, recuerde ** citar ** las expansiones de sus parámetros y yo * también * lo recomiendo * simple * cite su trampa: trap 'rm -rf "$ TMPDIR"' EXIT – lhunath

+4

Comillas simples, porque entonces su trampa se Aún funciona si más tarde en su secuencia de comandos decide limpiar y cambiar TMPDIR debido a circunstancias. – lhunath

+10

Punto menor: use $() en lugar de solo backticks. –

3

La alternativa de usar un nombre de archivo predecible con $$ es un gran agujero de seguridad y nunca, nunca, jamás pienses en usarlo. Incluso si se trata de un simple script personal en tu PC de usuario único. Es un mal hábito que no debes obtener. BugTraq está lleno de incidentes de "archivos temporales inseguros".Consulte here, here y here para obtener más información sobre el aspecto de seguridad de los archivos temporales.

Al principio estaba pensando en citar las inseguras asignaciones de TMP1 y TMP2, pero pensándolo mejor probablemente not be a good idea.

1

Yo prefiero usar tempfile que crea un archivo en/tmp de la manera segura y que no tiene que preocuparse por su nomenclatura:

tmp=$(tempfile -s "your_sufix") 
trap "rm -f '$tmp'" exit 
+0

tempfile es lamentablemente muy poco práctico, aunque más seguro, por lo que a menudo es mejor evitarlo o al menos emularlo. – lericson

3

Hemos de tener en cuenta que la elegida respuesta es bashism, lo que significa solución como

trap "{ rm -f $LOCKFILE }" EXIT 

funcionaría sólo en bash (no va a coger Ctrl + C si la cáscara es dashsh o clásico), pero si quieres compatibilidad entonces usted todavía necesita para enumerar todas las señales que desea tr ap.

También tenga en cuenta que cuando la secuencia de comandos sale de la trampa para la señal "0" (también conocido como EXIT) siempre se realiza dando como resultado la ejecución doble del comando trap.

Ese es el motivo por el que no se apilan todas las señales en una línea si hay una señal EXIT.

Para comprender mejor que mira siguiente script que trabajará a través de 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á un mayor control ya que se puede ejecutar algunos de su código de ocurrencia de señal real justo antes salida final (función preExit) y si es necesario puede ejecutar algún código en la señal EXIT real (etapa final de salida)

Cuestiones relacionadas