Por que o rc.local não executa todos os meus comandos e o que posso fazer sobre isso?

35

Eu tenho o seguinte 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

A primeira linha, startup_script.shna verdade , baixa o script.sharquivo /script.shreferido na terceira linha.

Infelizmente, parece que ele não está tornando o script executável ou executando o script. Executar o rc.localarquivo manualmente depois de ter iniciado funciona perfeitamente. O chmod não pode ser executado na inicialização ou algo assim?

Programador
fonte

Respostas:

70

Você pode pular todo o caminho até A Quick Fix, mas essa não é necessariamente a melhor opção. Então, eu recomendo ler tudo isso primeiro.

rc.local não tolera erros.

rc.localnão fornece uma maneira de recuperar de forma inteligente os erros. Se algum comando falhar, ele será interrompido. A primeira linha #!/bin/sh -e,, faz com que seja executada em um shell chamado com o -esinalizador. O -esinalizador é o que faz com que um script (nesse caso rc.local) pare de ser executado na primeira vez que um comando falhar nele.

Você quer rc.localse comportar assim. Se um comando falhar, você não deseja que ele continue com os outros comandos de inicialização que possam ter sido bem-sucedidos.

Portanto, se algum comando falhar, os comandos subsequentes não serão executados. O problema aqui é que /script.shnão foi executado (não que tenha falhado, veja abaixo); portanto, provavelmente algum comando antes de falhar. Mas qual deles?

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

Não.

chmodcorre bem a qualquer momento. Desde que o sistema de arquivos que contém /binfoi montado, você pode executar /bin/chmod. E /biné montado antes da rc.localexecução.

Quando executado como root, /bin/chmodraramente falha. Ele falhará se o arquivo em que opera for somente leitura e poderá falhar se o sistema de arquivos em que estiver ligado não tiver permissões suportadas. Nem é provável aqui.

A propósito, sh -eé a única razão pela qual seria realmente um problema se chmodfalhasse. Quando você executa um arquivo de script chamando explicitamente seu intérprete, não importa se o arquivo está marcado como executável. Somente se disser /script.shque o bit executável do arquivo será importante. Desde que diz sh /script.sh, não o faz (a não ser, é claro /script.sh , que se chame enquanto é executado, o que poderia falhar por não ser executável, mas é improvável que se chame).

Então, o que falhou?

sh /home/incero/startup_script.shfalhou. Quase certamente.

Sabemos que funcionou, porque baixou /script.sh.

(Caso contrário, seria importante garantir que ele funcionasse, caso de alguma forma /binnão estivesse no PATH - rc.localnão necessariamente tem o mesmo PATHque você tem quando está conectado. Se /binnão estiver no rc.localcaminho, isso exigiria shser executado como /bin/sh. Desde que executou, /binestá dentro PATH, o que significa que você pode executar outros comandos localizados /bin, sem qualificar totalmente seus nomes. Por exemplo, você pode executar apenas em chmodvez de /bin/chmod. No entanto, de acordo com seu estilo em rc.local, eu usei nomes totalmente qualificados para todos os comandos exceto sh, sempre que eu estou sugerindo que você executá-los.)

Podemos ter certeza de que /bin/chmod +x /script.shnunca funcionou (ou você verá que /script.shfoi executado). E sabemos que sh /script.shtambém não foi executado.

Mas ele baixou /script.sh. Conseguiu! Como isso poderia falhar?

Dois significados do sucesso

Há duas coisas diferentes que uma pessoa pode querer dizer quando diz que um comando foi bem-sucedido:

  1. Ele fez o que você queria que ele fizesse.
  2. Informou que conseguiu.

E assim é por fracasso. Quando uma pessoa diz que um comando falhou, isso pode significar:

  1. Não fez o que você queria.
  2. Informou que falhou.

Um script executado com sh -e, por exemplo rc.local, para de executar na primeira vez que um comando relata que falhou . Não faz diferença o que o comando realmente fez.

A menos que você pretenda startup_script.shrelatar falha quando ela faz o que deseja, isso é um bug startup_script.sh.

  • Alguns erros impedem que um script faça o que você deseja. Eles afetam o que os programadores chamam de efeitos colaterais .
  • E alguns erros impedem que um script relate corretamente se foi ou não bem-sucedido. Eles afetam o que os programadores chamam de valor de retorno (que neste caso é um status de saída ).

É mais provável que tenha startup_script.shfeito tudo o que deveria, exceto que relatou que falhou.

Como o sucesso ou o fracasso é relatado

Um script é uma lista de zero ou mais comandos. Cada comando tem um status de saída. Supondo que não haja falhas na execução do script (por exemplo, se o intérprete não puder ler a próxima linha do script durante a execução), o status de saída de um script será:

  • 0 (sucesso) se o script estava em branco (ou seja, não tinha comandos).
  • N, se o script foi finalizado como resultado do comando , onde está algum código de saída.exit NN
  • O código de saída do último comando executado no script, caso contrário.

Quando um executável é executado, ele relata seu próprio código de saída - eles não são apenas para scripts. (E tecnicamente, os códigos de saída dos scripts são os códigos de saída retornados pelos shells que os executam.)

Por exemplo, se um programa C termina com exit(0);, ou return 0;em sua main()função, o código 0é fornecido ao sistema operacional, que o fornece ao processo de chamada (que pode, por exemplo, ser o shell no qual o programa foi executado).

0significa que o programa foi bem-sucedido. Qualquer outro número significa que falhou. (Dessa forma, números diferentes às vezes podem se referir a diferentes razões pelas quais o programa falhou.)

Comandos com falha

Às vezes, você executa um programa com a intenção de que ele falhe. Nessas situações, você pode pensar em sua falha como um sucesso, mesmo que não seja um bug que o programa reporte falha. Por exemplo, você pode usar rmum arquivo que você suspeita que já não exista, apenas para garantir que ele seja excluído.

Algo como isso provavelmente está acontecendo startup_script.sh, pouco antes de parar de funcionar. O último comando a ser executado no script provavelmente está relatando falha (mesmo que sua "falha" possa ser totalmente boa ou até necessária), o que faz com que o script relate falha.

Testes com falha

Um tipo especial de comando é um teste , com o qual quero dizer um comando executado por seu valor de retorno, e não por seus efeitos colaterais. Ou seja, um teste é um comando executado para que seu status de saída possa ser examinado (e adotado).

Por exemplo, suponha que eu esqueça se 4 é igual a 5. Felizmente, eu sei scripts de shell:

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

Aqui, o teste [ -eq 5 ]falha porque, afinal, 4 × 5. Isso não significa que o teste não foi executado corretamente; fez. Seu trabalho era verificar se 4 = 5 e, em caso afirmativo, relatar o sucesso e, se não, o fracasso.

Veja, no script de shell, sucesso também pode significar verdadeiro e falha também pode significar falso .

Mesmo que a echodeclaração nunca seja executada, o ifbloco como um todo retorna com sucesso.

No entanto, eu deveria ter escrito mais curto:

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

Isso é uma abreviação comum. &&é um operador e booleano . Uma expressão, que consiste em declarações de ambos os lados, retorna falso (falha), a menos que ambos os lados retornem verdadeiro (sucesso). Assim como um normal e .&&&&

Se alguém lhe perguntar: "Derek foi ao shopping e pensou em uma borboleta?" e você sabe que Derek não foi ao shopping, não precisa se preocupar em descobrir se ele pensou em uma borboleta.

Da mesma forma, se o comando à esquerda de &&falhar (falso), toda a &&expressão falhará imediatamente (falso). A declaração no lado direito da &&tela nunca é executada.

Aqui, [ 4 -eq 5 ]corre. "Falha" (retornando falso). Então toda a &&expressão falha. echo "Yeah, they're totally the same."nunca corre. Tudo se comportou como deveria ser, mas esse comando relata falha (mesmo que o ifcondicional equivalente acima descrito relate sucesso).

Se essa fosse a última declaração de um script (e o script chegasse a ela, em vez de terminar em algum momento antes dela), o script inteiro relataria falha .

Existem muitos testes além disso. Por exemplo, existem testes com ||("ou"). No entanto, o exemplo acima deve ser suficiente para explicar o que são testes e possibilitar o uso eficaz da documentação para determinar se uma determinada instrução / comando é um teste.

shvs. sh -e, Revisitado

Como a #!linha (veja também esta pergunta ) na parte superior do /etc/rc.localhas sh -e, o sistema operacional executa o script como se tivesse sido chamado com o comando:

sh -e /etc/rc.local

Por outro lado, seus outros scripts, como startup_script.sh, são executados sem o -esinalizador:

sh /home/incero/startup_script.sh

Conseqüentemente, eles continuam sendo executados mesmo quando um comando neles relata falha.

Isso é normal e bom. rc.localdeve ser chamado sh -ee a maioria dos outros scripts - incluindo a maioria dos scripts executados por rc.local- não deveria .

Apenas lembre-se da diferença:

  • Os scripts são executados com sh -efalha no relatório de saída na primeira vez em que um comando contém sai do relatório.

    É como se o script fosse um único comando longo que consistisse em todos os comandos do script associados aos &&operadores.

  • Os scripts executados com sh(sem -e) continuam em execução até chegarem a um comando que os encerra (sai fora) ou até o final do script. O sucesso ou falha de cada comando é essencialmente irrelevante (a menos que o próximo comando verifique isso). O script sai com o status de saída do último comando executado.

Ajudando seu script a entender que não é um fracasso, afinal

Como você pode impedir que seu script pense que falhou quando não o fez?

Você olha o que acontece logo antes de terminar a execução.

  1. Se um comando falhou quando deveria ter sido bem-sucedido, descubra o motivo e corrija o problema.

  2. Se um comando falhar, e isso foi a coisa certa a acontecer, impeça a propagação do status da falha.

    • Uma maneira de impedir a propagação de um status de falha é executar outro comando que tenha êxito. /bin/truenão tem efeitos colaterais e relata sucesso (da mesma forma /bin/falseque nada faz e também falha).

    • Outra é garantir que o script seja finalizado por exit 0.

      Isso não é necessariamente a mesma coisa que exit 0estar no final do script. Por exemplo, pode haver um ifbloco onde o script sai por dentro.

É melhor saber o que faz com que seu script relate falha, antes de torná-lo bem-sucedido. Se realmente está falhando de alguma forma (no sentido de não fazer o que você quer que ele faça), você realmente não quer que ele relate sucesso.

Uma solução rápida

Se você não conseguir obter startup_script.shêxito no relatório de saída, poderá alterar o comando rc.localque o executa, para que o comando relate sucesso, mesmo que startup_script.shnão o tenha feito.

Atualmente você tem:

sh /home/incero/startup_script.sh

Este comando tem os mesmos efeitos colaterais (ou seja, o efeito colateral da execução startup_script.sh), mas sempre informa o sucesso:

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

Lembre-se, é melhor saber por que os startup_script.shrelatórios falham e corrigi-lo.

Como a correção rápida funciona

Este é realmente um exemplo de ||teste, um ou teste.

Suponha que você pergunte se eu tirei o lixo ou escovei a coruja. Se eu joguei o lixo fora, posso dizer "sim", mesmo que não me lembre se escovei ou não a coruja.

O comando à esquerda das ||execuções. Se for bem-sucedido (verdadeiro), o lado direito não precisará ser executado. Portanto, se startup_script.shrelatar êxito, o truecomando nunca será executado.

No entanto, se startup_script.shrelatar falha [eu não joguei o lixo fora] , o resultado de /bin/true [se eu escovei a coruja] será importante.

/bin/truesempre retorna sucesso (ou verdadeiro , como às vezes chamamos). Conseqüentemente, todo o comando é bem-sucedido e o próximo comando rc.localpode ser executado.

Uma observação adicional sobre sucesso / falha, verdadeiro / falso, zero / diferente de zero.

Sinta-se livre para ignorar isso. Você pode querer ler isso se programar em mais de um idioma (no entanto, não apenas scripts de shell).

É um ponto de grande confusão para os scripts de shell que adotam linguagens de programação como C e para os programadores em C que usam scripts de shell para descobrir:

  • No script de shell:

    1. Um valor de retorno 0significa sucesso e / ou verdadeiro .
    2. Um valor de retorno de algo diferente de 0significa falha e / ou falso .
  • Na programação C:

    1. Um valor de retorno 0significa false .
    2. Um valor de retorno de algo que não 0seja verdadeiro .
    3. Não existe uma regra simples para o que significa sucesso e o que significa fracasso. Às vezes 0significa sucesso, outras vezes significa falha, outras vezes significa que a soma dos dois números que você acabou de adicionar é zero. Os valores de retorno na programação geral são usados ​​para sinalizar uma ampla variedade de diferentes tipos de informações.
    4. O status de saída numérica de um programa deve indicar sucesso ou falha de acordo com as regras para scripts de shell. Ou seja, mesmo que isso 0signifique falso no seu programa C, você ainda fará com que o programa retorne 0como seu código de saída, se desejar que ele relate que foi bem-sucedido (que o shell interpreta como verdadeiro ).
Eliah Kagan
fonte
3
uau ótima resposta! Há muita informação lá que eu não sabia. Retornar 0 no final do primeiro script faz com que o restante do script seja executado (bem, posso dizer que o chmod + x foi executado). Infelizmente, parece que o / usr / bin / wget no meu script está falhando ao recuperar uma página da Web para colocar no script.sh. Acho que a rede não está pronta no momento em que esse script rc.local é executado. Onde posso colocar esse comando para que ele seja executado quando a rede for iniciada e automaticamente na inicialização?
Programster
Eu o 'hackeei' por enquanto executando o sudo visudo, adicionando a seguinte linha: nome de usuário ALL = (ALL) NOPASSWD: ALL executando o programa 'startup applications' (qual é o comando CLI para isso?) E adicionando startup_script a isso, enquanto o startupscript agora executa o script.sh com o comando sudo anteriormente para executar como root (não precisa mais de senha). Sem dúvida, isso é super inseguro e terrível, mas é apenas para um disco de distribuição ao vivo personalizado de pxe-boot.
Programster
@ Stu2000 Supondo incero(presumo que esse seja o nome de usuário) seja um administrador de qualquer maneira e, portanto, seja permitido executar qualquer comando como root(digitando sua própria senha de usuário), que não seja super inseguro e terrível . Se você quiser outras soluções, recomendo postar uma nova pergunta sobre isso . Um problema foi o motivo pelo qual os comandos rc.localnão foram executados (e você também verificou o que aconteceria se os fizesse continuar executando). Agora você tem um problema diferente: deseja conectar a máquina à rede antes da rc.localexecução ou agendar a execução startup_script.shmais tarde.
Eliah Kagan
1
Desde então, voltei a esta questão, editei o rc.local e descobri que o wget não 'funcionava'. A adição /usr/bin/sudo service networking restartao script de inicialização antes do comando wget resultou no funcionamento do wget.
Programster
3
@EliahKagan resposta exaustiva maravilhosa. Eu dificilmente me deparei com uma documentação melhor sobrerc.local
souravc
1

Você pode (nas variantes do Unix que eu uso) substituir o comportamento padrão alterando:

#!/bin/sh -e

Para:

#!/bin/sh

No início do script. O sinalizador "-e" instrui sh para sair no primeiro erro. O nome longo desse sinalizador é "errexit"; portanto, a linha original é equivalente a:

#!/bin/sh --errexit
Bryce
fonte
sim, removendo -etrabalhos em raspbian jessie.
Oki Erie Rinaldi
O -eestá lá por uma razão. Eu não faria isso se fosse você. Mas eu não sou seus pais.
Wyatt8740 01/07
-4

alterar a primeira linha de: #!/bin/sh -e para !/bin/sh -e

elJenso
fonte
3
A sintaxe com #!é a correta. (Pelo menos, a #!parte está correta. E o resto geralmente funciona bem para rc.local.) Veja este artigo e esta pergunta . De qualquer forma, bem-vindo ao Ask Ubuntu!
Eliah Kagan