¿Por qué rc.local no ejecuta todos mis comandos y qué puedo hacer al respecto?

35

Tengo el siguiente rc.localscript:

#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

sh /home/incero/startup_script.sh
/bin/chmod +x /script.sh
sh /script.sh

exit 0

La primera línea, en startup_script.shrealidad descarga el script.sharchivo al que se /script.shhace referencia en la tercera línea.

Desafortunadamente, parece que no está haciendo que el script sea ejecutable o que no esté ejecutando el script. Ejecutar el rc.localarchivo manualmente después de haber comenzado funciona perfectamente. ¿No se puede ejecutar chmod al inicio o algo así?

Programador
fuente

Respuestas:

70

Puede saltar hasta A Quick Fix, pero esa no es necesariamente la mejor opción. Así que recomiendo leer todo esto primero.

rc.local No tolera errores.

rc.localno proporciona una forma inteligente de recuperarse de los errores. Si algún comando falla, deja de ejecutarse. La primera línea, #!/bin/sh -ehace que se ejecute en un shell invocado con la -ebandera. La -ebandera es lo que hace que un script (en este caso rc.local) deje de ejecutarse la primera vez que un comando falla dentro de él.

Quieres rc.localcomportarte así. Si un comando falla, no desea que continúe con cualquier otro comando de inicio que pueda confiar en que haya tenido éxito.

Entonces, si algún comando falla, los siguientes comandos no se ejecutarán. El problema aquí es que /script.shno se ejecutó (no es que haya fallado, ver más abajo), por lo que lo más probable es que haya algún comando antes de fallar. ¿Pero cual?

Fue /bin/chmod +x /script.sh?

No.

chmodfunciona bien en cualquier momento. Siempre que /binse haya montado el sistema de archivos que contiene , puede ejecutarlo /bin/chmod. Y /binse monta antes de las rc.localcarreras.

Cuando se ejecuta como root, /bin/chmodrara vez falla. Fallará si el archivo en el que opera es de solo lectura, y podría fallar si el sistema de archivos en el que se encuentra no admite permisos. Tampoco es probable aquí.

Por cierto, sh -ees la única razón por la que realmente sería un problema si chmodfallara. Cuando ejecuta un archivo de script invocando explícitamente a su intérprete, no importa si el archivo está marcado como ejecutable. Solo si decía /script.shque importaría el bit ejecutable del archivo. Como dice sh /script.sh, no lo hace (a menos que, por supuesto, se /script.sh llame a sí mismo mientras se ejecuta, lo que podría fallar si no es ejecutable, pero es poco probable que se llame a sí mismo).

Entonces, ¿qué falló?

sh /home/incero/startup_script.shha fallado. Casi seguro

Sabemos que se ejecutó, porque se descargó /script.sh.

(De lo contrario, sería importante asegurarse de que se ejecutara, en caso de que de alguna manera /binno estuviera en RUTA , rc.localno necesariamente tiene lo mismo PATHque usted tenía cuando inició sesión. Si /binno estaba en rc.localel camino, esto requeriría shejecutarse como /bin/sh. Como se ejecutó, /binestá en PATH, lo que significa que puede ejecutar otros comandos que se encuentran en /bin, sin calificar completamente sus nombres. Por ejemplo, puede ejecutar solo en chmodlugar de /bin/chmod. Sin embargo, de acuerdo con su estilo en rc.local, he usado nombres completos para todos los comandos sh, excepto cuando sugiero que los ejecute).

Podemos estar bastante seguros de que /bin/chmod +x /script.shnunca se ejecutó (o vería que /script.shse ejecutó). Y sabemos que sh /script.shtampoco se ejecutó.

Pero se descargó /script.sh. ¡Lo logró! ¿Cómo podría fallar?

Dos significados del éxito

Hay dos cosas diferentes que una persona puede querer decir cuando dice que un comando tuvo éxito:

  1. Hizo lo que querías que hiciera.
  2. Informó que tuvo éxito.

Y así es por el fracaso. Cuando una persona dice que un comando falló, podría significar:

  1. No hizo lo que querías que hiciera.
  2. Informó que falló.

Un script ejecutado con sh -e, como rc.local, dejará de ejecutarse la primera vez que un comando informa que falló . No importa lo que realmente hizo el comando.

A menos que tenga la intención de startup_script.shinformar un error cuando hace lo que quiere, esto es un error startup_script.sh.

  • Algunos errores impiden que un script haga lo que usted quiere que haga. Afectan lo que los programadores llaman sus efectos secundarios .
  • Y algunos errores evitan que un script informe correctamente si tuvo éxito o no. Afectan lo que los programadores llaman su valor de retorno (que en este caso es un estado de salida ).

Es muy probable que haya startup_script.shhecho todo lo que debería, excepto que informó que falló.

Cómo se informa el éxito o el fracaso

Un script es una lista de cero o más comandos. Cada comando tiene un estado de salida. Suponiendo que no haya fallas en la ejecución real del script (por ejemplo, si el intérprete no pudo leer la siguiente línea del script mientras lo ejecuta), el estado de salida de un script es:

  • 0 (éxito) si el script estaba en blanco (es decir, no tenía comandos).
  • N, si el script finalizó como resultado del comando , ¿dónde hay algún código de salida?exit NN
  • El código de salida del último comando que se ejecutó en el script, de lo contrario.

Cuando se ejecuta un ejecutable, informa su propio código de salida: no son solo para scripts. (Y técnicamente, los códigos de salida de los scripts son los códigos de salida devueltos por los shells que los ejecutan).

Por ejemplo, si un programa C termina con exit(0);, o return 0;en su main()función, el código 0se entrega al sistema operativo, que lo proporciona al proceso de llamada (que puede ser, por ejemplo, el shell desde el que se ejecutó el programa).

0significa que el programa tuvo éxito. Cualquier otro número significa que falló. (De esta forma, diferentes números a veces pueden referirse a diferentes razones por las que el programa falló).

Comandos destinados a fallar

A veces, ejecuta un programa con la intención de que falle. En estas situaciones, puede pensar que su falla es exitosa, aunque no es un error que el programa informe una falla. Por ejemplo, puede usar rmun archivo que sospecha que ya no existe, solo para asegurarse de que se elimine.

Probablemente ocurra algo como esto startup_script.sh, justo antes de que deje de funcionar. El último comando que se ejecuta en el script probablemente informa un error (aunque su "error" podría ser totalmente correcto o incluso necesario), lo que hace que el script informe un error.

Pruebas destinadas a fallar

Un tipo especial de comando es una prueba , con lo que quiero decir que un comando se ejecuta por su valor de retorno en lugar de sus efectos secundarios. Es decir, una prueba es un comando que se ejecuta para poder examinar su estado de salida (y actuar sobre él).

Por ejemplo, supongamos que olvido si 4 es igual a 5. Afortunadamente, conozco los scripts de shell:

if [ 4 -eq 5 ]; then
    echo "Yeah, they're totally the same."
fi

Aquí, la prueba [ -eq 5 ]falla porque resulta 4 ≠ 5 después de todo. Eso no significa que la prueba no se realizó correctamente; lo hizo. Su trabajo consistía en verificar si 4 = 5, luego informar el éxito si es así y el fracaso si no.

Verá, en los scripts de shell, el éxito también puede significar verdadero , y el fracaso también puede significar falso .

Aunque la echodeclaración nunca se ejecuta, el ifbloque en su conjunto devuelve el éxito.

Sin embargo, supuse que lo había escrito más corto:

[ 4 -eq 5 ] && echo "Yeah, they're totally the same."

Esto es una taquigrafía común. &&es un booleano y operador. Una &&expresión, que consiste &&en declaraciones a ambos lados, devuelve falso (falla) a menos que ambos lados devuelvan verdadero (éxito). Al igual que un normal y .

Si alguien te pregunta, "¿Derek fue al centro comercial y pensó en una mariposa?" y sabes que Derek no fue al centro comercial, no tienes que molestarte en averiguar si pensó en una mariposa.

Del mismo modo, si el comando a la izquierda de &&falla (falso), la &&expresión completa falla inmediatamente (falso). La declaración en el lado derecho de &&nunca se ejecuta.

Aquí [ 4 -eq 5 ]corre. "Falla" (devuelve falso). Entonces toda la &&expresión falla. echo "Yeah, they're totally the same."nunca corre. Todo se comportó como debería ser, pero este comando informa de un error (aunque el ifcondicional equivalente por encima de lo anterior informa de éxito).

Si esa fuera la última declaración en un guión (y el guión llegó a ella, en lugar de terminar en algún momento antes), todo el guión informaría un fallo .

Hay muchas pruebas además de esto. Por ejemplo, hay pruebas con ||("o"). Sin embargo, el ejemplo anterior debería ser suficiente para explicar qué son las pruebas y permitirle utilizar de manera efectiva la documentación para determinar si una instrucción / comando en particular es una prueba.

shvs. sh -e, revisitado

Desde la #!línea (vea también esta pregunta ) en la parte superior de /etc/rc.localhas sh -e, el sistema operativo ejecuta el script como si fuera invocado con el comando:

sh -e /etc/rc.local

Por el contrario, sus otros scripts, como startup_script.sh, se ejecutan sin la -ebandera:

sh /home/incero/startup_script.sh

En consecuencia, siguen ejecutándose incluso cuando un comando en ellos informa de un error.

Esto es normal y bueno. rc.localdebe invocarse con sh -ey la mayoría de los otros scripts, incluida la mayoría de los scripts ejecutados por, rc.localno debe.

Solo asegúrate de recordar la diferencia:

  • Las secuencias de comandos se ejecutan con un sh -eerror de informe de salida la primera vez que un comando que contienen sale informando un error.

    Es como si el script fuera un único comando largo que consta de todos los comandos del script unidos a &&operadores.

  • Las secuencias de comandos que se ejecutan con sh(sin -e) continúan ejecutándose hasta que llegan a un comando que termina (sale de ellas) o hasta el final de la secuencia de comandos. El éxito o el fracaso de cada comando es esencialmente irrelevante (a menos que el siguiente comando lo verifique). El script sale con el estado de salida del último comando ejecutado.

Ayudar a su guión a comprender que no es un fracaso después de todo

¿Cómo puedes evitar que tu guión piense que falló cuando no lo hizo?

Miras lo que sucede justo antes de que termine de ejecutarse.

  1. Si un comando falla cuando debería haber tenido éxito, descubra por qué y solucione el problema.

  2. Si un comando falla, y eso fue lo correcto, entonces evite que el estado de falla se propague.

    • Una forma de evitar que se propague un estado de falla es ejecutar otro comando que tenga éxito. /bin/trueno tiene efectos secundarios e informa de éxito (al igual /bin/falseque no hace nada y también falla).

    • Otra es asegurarse de que el script finalice exit 0.

      Eso no es necesariamente lo mismo que exit 0estar al final del guión. Por ejemplo, puede haber un ifbloque donde el script sale adentro.

Es mejor saber qué causa que su secuencia de comandos informe fallas, antes de hacer que informe éxito. Si realmente está fallando de alguna manera (en el sentido de no hacer lo que quieres que haga), realmente no quieres que informe de éxito.

Una solución rápida

Si no puede hacer que el startup_script.shinforme de salida sea exitoso, puede cambiar el comando rc.localque lo ejecuta, de modo que ese comando informa el éxito aunque startup_script.shno lo hizo.

Actualmente tienes:

sh /home/incero/startup_script.sh

Este comando tiene los mismos efectos secundarios (es decir, el efecto secundario de la ejecución startup_script.sh), pero siempre informa el éxito:

sh /home/incero/startup_script.sh || /bin/true

Recuerde, es mejor saber por qué startup_script.shinforma un error y solucionarlo.

Cómo funciona la solución rápida

Este es en realidad un ejemplo de una ||prueba, una o prueba.

Supongamos que preguntas si saqué la basura o rocé la lechuza. Si saqué la basura, puedo decir sinceramente "sí", incluso si no recuerdo si rocé o no la lechuza.

El comando a la izquierda de ||carreras. Si tiene éxito (verdadero), entonces el lado derecho no tiene que correr. Entonces, si startup_script.shinforma éxito, el truecomando nunca se ejecuta.

Sin embargo, si startup_script.shinforma un fallo [no saqué la basura] , entonces el resultado de /bin/true [si cepillé la lechuza] es importante.

/bin/truesiempre devuelve el éxito (o verdadero , como a veces lo llamamos). En consecuencia, todo el comando tiene éxito y rc.localpuede ejecutarse el siguiente comando .

Una nota adicional sobre éxito / fracaso, verdadero / falso, cero / distinto de cero.

Siéntase libre de ignorar esto. Sin embargo, es posible que desee leer esto si programa en más de un idioma (es decir, no solo scripts de shell).

Es un punto de gran confusión para los scripters de shell que utilizan lenguajes de programación como C, y para los programadores de C que utilizan scripts de shell, para descubrir:

  • En scripting de shell:

    1. Un valor de retorno de 0significa éxito y / o verdadero .
    2. Un valor de retorno de algo distinto de 0significa falla y / o falso .
  • En programación en C:

    1. Un valor de retorno de 0significa falso .
    2. Un valor de retorno de algo distinto de 0medios verdaderos .
    3. No existe una regla simple para lo que significa éxito y lo que significa fracaso. A veces 0significa éxito, otras veces significa fracaso, otras veces significa que la suma de los dos números que acaba de sumar es cero. Los valores de retorno en la programación general se utilizan para señalar una amplia variedad de diferentes tipos de información.
    4. El estado de salida numérica de un programa debe indicar el éxito o el fracaso de acuerdo con las reglas para la secuencia de comandos de shell. Es decir, aunque 0significa falso en su programa C, aún hace que su programa regrese 0como su código de salida si desea que informe que tuvo éxito (que el shell luego interpreta como verdadero ).
Eliah Kagan
fuente
3
wow gran respuesta! Hay mucha información allí que no sabía. Devolver 0 al final del primer script hace que se ejecute el resto del script (bueno, puedo decir que se ejecutó el chmod + x). Desafortunadamente, parece que / usr / bin / wget en mi script no puede recuperar una página web para poner en script.sh. Supongo que la red no está lista para cuando se ejecute este script rc.local. ¿Dónde puedo poner este comando para que se ejecute cuando la red se haya iniciado y automáticamente al inicio?
Programador
Lo he 'pirateado' por ahora ejecutando sudo visudo, agregando la siguiente línea: nombre de usuario ALL = (ALL) NOPASSWD: TODO ejecutando el programa de 'aplicaciones de inicio' (¿cuál es el comando CLI para esto?), Y agregando startup_script a esto, mientras que startupscript ahora ejecuta script.sh con el comando sudo de antemano para ejecutarse como root (ya no necesita contraseña). Sin duda, esto es súper inseguro y terrible, pero esto es solo para un disco de distribución en vivo pxe-boot personalizado.
Programador
@ Stu2000 Suponiendo incero(supongo que ese es el nombre de usuario) es un administrador de todos modos , y por lo tanto se le permite ejecutar cualquier comando como root(al ingresar su propia contraseña de usuario), que no es súper inseguro y terrible . Si desea otras soluciones, le recomiendo publicar una nueva pregunta sobre esto . Un problema fue por qué los comandos rc.localno se ejecutaron (y también verificó qué pasaría si los hiciera continuar ejecutándose). Ahora tiene un problema diferente: desea conectar la máquina a la red antes de rc.localejecutarla o programar la ejecución startup_script.shposterior.
Eliah Kagan
1
Desde entonces volví a este problema, edité el rc.local y encontré que wget no 'funcionaba'. Agregar /usr/bin/sudo service networking restartal script de inicio antes del comando wget resultó en wget y luego funcionó.
Programador
3
@EliahKagan maravillosa respuesta exhaustiva. Apenas encontré una mejor documentación sobrerc.local
souravc
1

Puede (en las variantes de Unix que uso) anular el comportamiento predeterminado cambiando:

#!/bin/sh -e

A:

#!/bin/sh

Al comienzo del guión. La bandera "-e" indica a sh que salga en el primer error. El nombre largo de esa bandera es "errexit", por lo que la línea original es equivalente a:

#!/bin/sh --errexit
Bryce
fuente
sí, eliminar -etrabajos en raspbian jessie.
Oki Erie Rinaldi
El -eestá ahí por una razón. No haría eso si fuera tú. Pero no soy tus padres.
Wyatt8740
-4

cambiar la primera línea de: #!/bin/sh -e a !/bin/sh -e

elJenso
fuente
3
La sintaxis con #!es la correcta. (Al menos, la #!parte es correcta. Y el resto generalmente funciona bien para, para rc.local). Vea este artículo y esta pregunta . De todos modos, ¡bienvenido a Ask Ubuntu!
Eliah Kagan