¿Por qué verificar la existencia del archivo antes de obtenerlo?

13

Al intentar obtener un archivo, ¿no le gustaría un error que diga que el archivo no existe para que sepa qué solucionar?

Por ejemplo, nvm recomienda agregar esto a su perfil / rc:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm

Con lo anterior, si nvm.shno existe, obtendrá un "error silencioso". Pero si lo intentas . "$NVM_DIR/nvm.sh", la salida será FILE_PATH: No such file or directory.

JBallin
fuente
3
No deberías Es picante. Pruebe la fuente y luego maneje el error, si corresponde
Mikel
2
@Mikel bien! entonces, ¿por qué lo veo en todas partes?
JBallin
3
@FaheemMitha, bueno, si lo que estás haciendo es sensible a la seguridad (es decir, tu programa funciona en nombre de otra persona), entonces realmente debes preocuparte por las condiciones de la carrera ( TOCTOU ). Sin embargo, ese probablemente no sea el caso aquí, ya que tiene mayores problemas si alguien modifica los archivos HOME.
ilkkachu
8
@FaheemMitha Es Nunca mejor para comprobar la existencia en primer lugar. La situación puede cambiar entre la prueba y el uso, produciendo tanto falsos positivos como falsos negativos: y de todos modos aún tiene que manejar la falla en el uso. Y como ha mencionado el rendimiento, las pruebas antes del uso son endémicamente dos veces más lentas que no hacerlo y dejar que el sistema lo haga, lo que de todos modos lo hará. No puede no serlo.
user207421
1
@SimonRichter, en este caso, nvm no funcionará. El usuario tendría que darse cuenta de que el archivo falta por su cuenta.
JBallin

Respuestas:

25

En los shells POSIX, .es un componente integrado especial, por lo que su falla hace que el shell salga (en algunos shells como bash, solo se hace cuando está en modo POSIX).

Lo que califica como error depende del shell. No todos salen con un error de sintaxis al analizar el archivo, pero la mayoría se cierra cuando no se puede encontrar o abrir el archivo de origen. No conozco ninguno que salga si el último comando en el archivo de origen regresa con un estado de salida distinto de cero (a menos que la errexitopción esté activada, por supuesto).

Aquí haciendo:

[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

Es un caso en el que desea obtener el archivo si está allí, y no si no está (o está vacío aquí con -s).

Es decir, no debe considerarse un error (error grave en shells POSIX) si el archivo no está allí, ese archivo se considera un archivo opcional.

Seguiría siendo un error (fatal) si el archivo no fuera legible o si fuera un directorio o (en algunos shells) si hubiera un error de sintaxis al analizarlo, que serían condiciones de error reales que deberían informarse.

Algunos dirían que hay una condición de carrera. Pero lo único que significa sería que el shell saldría con un error si el archivo se elimina entre el [y ., pero diría que es válido considerar que es un error que este archivo de ruta fija desaparezca repentinamente mientras el script está corriendo.

Por otra parte,

command . "$NVM_DIR/nvm.sh" 2> /dev/null

donde command¹ elimina el atributo especial del .comando (para que no salga del shell por error) no funcionaría como:

  • ocultaría .los errores pero también los errores de los comandos ejecutados en el archivo de origen
  • también ocultaría condiciones de error reales como que el archivo tenga los permisos incorrectos.

Otras sintaxis comunes (consulte, por ejemplo, grep -r /etc/default /etc/init*en los sistemas Debian para las secuencias de comandos de inicio que aún no se han convertido systemd(donde EnvironmentFile=-/etc/default/servicese utiliza para especificar un archivo de entorno opcional)) incluyen:

  • [ -e "$file" ] && . "$file"

    Verifique que el archivo esté allí, aún consígalo si está vacío. Sigue siendo un error grave si no se puede abrir (aunque esté allí o estaba allí). Es posible que vea más variantes como [ -f "$file" ](existe y es un archivo normal ), [ -r "$file" ](es legible) o combinaciones de ellas.

  • [ ! -e "$file" ] || . "$file"

    Una versión un poco mejor. Deja en claro que el archivo que no existe es un caso correcto. Eso también significa $?que reflejará el estado de salida del último comando ejecutado $file(en el caso anterior, si obtiene 1, no sabe si es porque $fileno existió o si ese comando falló).

  • command . "$file"

    Espere que el archivo esté allí, pero no salga si no se puede interpretar.

  • [ ! -e "$file" ] || command . "$file"

    Combinación de lo anterior: está bien si el archivo no está allí, y para shells POSIX, las fallas para abrir (o analizar) el archivo se informan pero no son fatales (lo que puede ser más deseable ~/.profile).


¹ Nota: zshSin embargo, no se puede usar commandasí a menos que en la shemulación; tenga en cuenta que en el shell Korn, en sourcerealidad es un alias para command ., una variante no especial de.

Stéphane Chazelas
fuente
¡Interesante! No sabía eso sobre POSIX sh. Pero la pregunta era sobre .bash_profile. Supongo que es mejor prevenir que curar, pero ¿bash alguna vez está en modo POSIX cuando .bash_profilese obtiene?
Mikel
(Me doy cuenta de que podría interpretar esta pregunta como una aplicación más amplia a todos los shells POSIX basada en la lectura del enlace fuente nvm en la pregunta.)
Mikel
@Mikel, mi respuesta aún se aplica bashcuando no está en modo POSIX. Desearía [ -e /file ] && . /filesi no lo considera un error cuando el archivo no existe. La fuente de prueba maneja el error, si no se puede hacer aquí.
Stéphane Chazelas
1
@Mikel, eso es contraproducente. 1) que no impide la salida en caso de error con shells POSIX (o bash en modo POSIX) 2), que duplica el mensaje de error (el suyo en stdout), .ya informará un error (en stderr). Y si la intención es no considerarlo un error cuando el archivo no existe, eso no es correcto (y no es posible, desde el estado de salida, decir si .falló porque el archivo no existía o no era legible, o era no analizable, o el último comando falló), que son los puntos que estoy haciendo aquí en esta respuesta.
Stéphane Chazelas
1
Con respecto a la condición de la carrera: en mi humilde opinión, es mucho menos problemático que el inicio de sesión falle una vez (mientras algo extraño está sucediendo en el sistema) que el inicio de sesión falle constantemente (incluso si hay algo que no está del todo bien en la configuración del usuario -archivos). Por lo tanto, una condición de carrera en este control sigue siendo una mejora respecto a no tener el control.
ruakh
5

Mantenedor de nvmla respuesta de:

es fácil desinstalar nvm simplemente borrando el archivo; Forzar trabajo adicional (para rastrear dónde están las líneas que son la fuente nvm) no parece particularmente valioso.

Mi interpretación (combinada con la excelente explicación de Stéphane y el comentario de Kusalananda):

Es más simple y seguro.

Se defiende contra los shells POSIX que salen al inicio debido a la falta de un archivo (por varias razones). Aquellos que usan shells que no son POSIX (por ejemplo, bash) pueden eliminar el condicional si así lo prefieren.

JBallin
fuente
1
Me opongo a su interpretación de que está "dirigida a principiantes". Es defensivo No desea que el shell de inicio de sesión de un usuario finalice inesperadamente en el inicio solo porque ese archivo se ha perdido (por cualquier razón). Si esa línea de código está en uno de los archivos de inicio de shell /etc, entonces permite que algunos usuarios tengan el archivo y otros no. En mi humilde opinión, la nvmrespuesta del mantenedor solo toca un aspecto.
Kusalananda
1

Como JBallin y Stéphane Chazelas han señalado, en shells POSIX, el abastecimiento de un archivo que no existe provocaría un error al iniciar sesión.

Pero agregar una prueba para ver si el archivo existe y luego intentar obtenerlo puede causar algo llamado condición de carrera. Si algo cambia nvm.shentre el [ -s nvm.sh ]y el . nvm.sh, causará exactamente el error que están tratando de prevenir, aunque con mucha menos frecuencia.

En general, la forma de prevenir las condiciones de carrera es simplemente intentar lo que desea hacer, luego manejar el error si falla, por ejemplo

. "$NVM_DIR/nvm.sh" || echo "Sourcing $NVM_DIR/nvm.sh failed" >&2

Resulta que esto no funciona en los shells POSIX, porque, como se indicó anteriormente, la .falla hará que el shell salga inmediatamente, antes de que se pueda ejecutar cualquier manejo de errores.

Mi respuesta argumenta que los shells POSIX no son relevantes para esta pregunta, porque .bash_profilenunca deberían ejecutarse en modo POSIX. Entonces podemos hacer el código anterior de todos modos.

Para estar más seguros, podríamos asegurarnos de que el modo POSIX no esté vigente, o asegurarnos de que el modo POSIX esté deshabilitado utilizando la técnica descrita en /unix//a/383581/3169 .

La respuesta de Stéphane tiene algunas sugerencias útiles sobre cómo manejar todos los shells POSIX, lo que creo que fue la intención del autor nvm, pero fue sutilmente diferente de lo que estaba haciendo la pregunta aquí, y es por eso que tenemos múltiples enfoques posibles, dependiendo de cuál sea su objetivo. .

Mikel
fuente