Entonces, eliminé mi carpeta de inicio (o, más precisamente, todos los archivos a los que tenía acceso de escritura). Lo que pasó es que tuve
build="build"
...
rm -rf "${build}/"*
...
<do other things with $build>
en un script bash y, después de no necesitarlo más $build
, eliminar la declaración y todos sus usos, pero el rm
. Bash felizmente se expande a rm -rf /*
. Sí.
Me sentí estúpido, instalé la copia de seguridad, rehice el trabajo que perdí. Tratando de pasar la vergüenza.
Ahora, me pregunto: ¿cuáles son las técnicas para escribir scripts de bash para que tales errores no puedan ocurrir, o al menos sean menos probables? Por ejemplo, si hubiera escrito
FileUtils.rm_rf("#{build}/*")
En un guión de Ruby, el intérprete se habría quejado de build
no haber sido declarado, por lo que el lenguaje me protege.
Lo que he considerado en bash, además de acorralar rm
(que, como mencionan muchas respuestas en preguntas relacionadas, no es nada problemático):
rm -rf "./${build}/"*
Eso habría matado mi trabajo actual (un repositorio de Git) pero nada más.- Una variante / parametrización de
rm
eso requiere interacción cuando se actúa fuera del directorio actual. (No se pudo encontrar ninguno). Efecto similar.
¿Es eso, o hay otras formas de escribir scripts de bash que son "robustos" en este sentido?
fuente
rm -rf "${build}/*
no importar a dónde van las comillas.rm -rf "${build}
hará lo mismo por elf
.set -u
no está tan mal visto comoset -e
está, pero aún tiene sus problemas.#! /usr/bin/env ruby
en la parte superior de cada script de shell y olvídate de bash;)Respuestas:
o
Esto haría que el shell actual trate las expansiones de variables no definidas como un error:
set -u
yset -o nounset
son opciones de shell POSIX .Un vacío valor sería no provocar un error sin embargo.
Para eso, usa
La expansión de
${variable:?word}
se expandiría al valor de avariable
menos que esté vacía o sin establecer. Si está vacío o sin establecer,word
se mostrará en un error estándar y el shell tratará la expansión como un error (el comando no se ejecutará, y si se ejecuta en un shell no interactivo, esto finalizaría). Dejar la:
salida activaría el error solo para un valor no establecido, al igual que debajoset -u
.${variable:?word}
es una expansión de parámetros POSIX .Ninguno de estos provocaría la terminación de un shell interactivo a menos que
set -e
(oset -o errexit
) también estuviera en vigor.${variable:?word}
hace que las secuencias de comandos salgan si la variable está vacía o sin establecer.set -u
provocaría la salida de un script si se usa junto conset -e
.En cuanto a tu segunda pregunta. No hay forma de limitar
rm
a no trabajar fuera del directorio actual.La implementación de GNU
rm
tiene una--one-file-system
opción que evita que elimine de forma recursiva los sistemas de archivos montados, pero eso es lo más cercano que creo que podemos obtener sin ajustar larm
llamada en una función que realmente verifica los argumentos.Como nota al margen:
${build}
es exactamente equivalente a$build
menos que la expansión se produzca como parte de una cadena donde el carácter inmediatamente siguiente es un carácter válido en un nombre de variable, como en"${build}x"
.fuente
${build:?msg}
o${build?msg}
? 2) En el contexto de algo así como crear scripts, creo que usar una herramienta diferente de rm para una eliminación más segura estaría bien: sabemos que no queremos trabajar fuera del directorio actual, por lo que lo hacemos explícito al usar un método restringido mando. No hay necesidad de hacer rm más seguro en general.:
(que desencadenaría el error sólo para no definidas las variables). No me atrevo a intentar escribir una herramienta que pueda hacer frente a la eliminación de archivos bajo una ruta específica exclusivamente, en todas las circunstancias. Analizar caminos y preocuparse / no preocuparse por enlaces simbólicos, etc., es un poco demasiado complicado para mí en este momento.Voy a sugerir comprobaciones de validación normales usando
test
/[ ]
Hubiera estado a salvo si hubiera escrito su guión como tal:
Los
[ -n "${build}" ]
controles que"${build}"
es una cadena de longitud no cero .El
||
es el operador lógico OR en bash. Hace que se ejecute otro comando si falla el primero.De esta manera, había
${build}
estado vacío / indefinido / etc. el script habría salido (con un código de retorno de 1, que es un error genérico).Esto también lo habría protegido en caso de que eliminara todos los usos
${build}
porque[ -n "" ]
siempre será falso.La ventaja de usar
test
/[ ]
es que hay muchas otras comprobaciones más significativas que también puede usar.Por ejemplo:
fuente
${variable:?word}
@Kusalananda propone?test
(es decir,[
tiene muchas otras comprobaciones que son relevantes, como-d
(el argumento es un directorio),-f
(el argumento es un archivo),-O
(el archivo es propiedad del usuario actual) y así sucesivamente.${build:?word}
? El${variable:?word}
formato no lo protege si elimina todas las instancias de la variable.if
no es completa. 2) "¿no habría eliminado también $ {build:? Word} junto con él" - el escenario es que me perdí el uso de la variable. Usar${v:?w}
me habría protegido del daño. Si hubiera eliminado todos los usos, incluso el acceso simple no habría sido perjudicial, obviamente.En su caso específico, he reelaborado la 'eliminación' en el pasado para mover archivos / directorios en su lugar (suponiendo que / tmp esté en la misma partición que su directorio):
Detrás de escena, esto mueve las referencias de archivo / directorio de nivel superior desde las
$trashdir
estructuras de directorio de origen a destino en la misma partición, y no pasa tiempo recorriendo la estructura del directorio y liberando los bloques de disco por archivo en ese mismo momento. Esto produce una limpieza mucho más rápida mientras el sistema está en uso activo, a cambio de un reinicio un poco más lento (/ tmp se limpia en los reinicios).Alternativamente, una entrada cron para limpiar periódicamente /tmp/.trash-$USER evitará que / tmp se llene, para procesos (por ejemplo, compilaciones) que consumen mucho espacio en disco. Si su directorio está en una partición diferente como / tmp, puede crear un directorio similar a / tmp-like en su partición y hacer que cron limpie eso en su lugar.
Sin embargo, lo más importante es que si arruinas las variables de alguna manera, puedes recuperar el contenido antes de que ocurra la limpieza.
fuente
/tmp
está en otra partición (creo que siempre lo es para mí, al menos para los scripts que se ejecutan en el espacio del usuario); entonces, la carpeta de la papelera debe cambiarse (y no se beneficiará del manejo del sistema operativo/tmp
).mktemp -d
para obtener ese directorio temporal sin pisar los dedos del pie de otro proceso (y honrarlo correctamente$TMPDIR
).Use la sustitución de parámetros bash para asignar valores predeterminados cuando las variables no se inicializan, por ejemplo:
rm -rf $ {variable: - "/ nonexistent"}
fuente
Siempre trato de comenzar mis scripts de Bash con una línea
#!/bin/bash -ue
.-e
medios "fallan en la primera uncathed e rror";-u
medios "fallan en primer uso de u ndeclared variable".Encuentre más detalles en un excelente artículo Utilice el modo estricto de Bash no oficial (a menos que le guste la depuración) . El autor también recomienda usar,
set -o pipefail; IFS=$'\n\t'
pero para mi propósito esto es excesivo.fuente
El consejo general para verificar si su variable está configurada es una herramienta útil para evitar este tipo de problemas. Pero en este caso había una solución más simple.
Es muy probable que no sea necesario englobar el contenido del
$build
directorio para eliminarlos, pero no el$build
directorio en sí. Entonces, si omitió lo extraño*
, se convertiría en un valor no establecidorm -rf /
que, por defecto, la mayoría de las implementaciones de rm en la última década se negarán a realizar (a menos que desactive esta protección con la--no-preserve-root
opción de GNU rm ).Omitir el seguimiento
/
también daría comorm ''
resultado el mensaje de error:Esto funciona incluso si su comando rm no implementa protección para
/
.fuente
Estás pensando en términos de lenguaje de programación, pero bash es un lenguaje de script :-) Entonces, usa un paradigma de instrucción completamente diferente para un paradigma de lenguaje completamente diferente .
En este caso:
Como
rmdir
se negará a eliminar un directorio no vacío, primero debe eliminar los miembros. Sabes cuáles son esos miembros, ¿verdad? Probablemente son parámetros para su script, o derivados de un parámetro, por lo tanto:Ahora, si coloca algunos otros archivos o directorios como un archivo temporal o algo que no debería tener,
rmdir
arrojará un error. Manejarlo adecuadamente, luego:Esta forma de pensar me ha servido bien porque ... sí, he estado allí, hecho eso.
fuente
make clean
utilizará algunos comodines (a menos que un compilador muy diligente cree una lista de cada archivo que crea). Además, me parece que tenerrm -rf ${build}/${parameter}
solo mueve el problema un poco hacia abajo.make
en la pregunta. Escribir una respuesta que solo sea aplicablemake
sería una suposición muy específica y sorprendente. Mi respuesta funciona para otros casosmake
, que no sean el desarrollo de software, que no sea su problema de novato específico que tenía al eliminar sus cosas.