Como manter o Bash em execução após a execução do comando?

29

Eu gostaria de executar algo como isto:

bash -c "some_program with its arguments"

mas para ter uma festa interativa, continue executando após o some_programtérmino.

Tenho certeza que -cnão é uma boa maneira, como man bashseys:

Um shell interativo é aquele iniciado sem argumentos de não opção e sem a opção -c

Então, como fazer isso?

O objetivo principal é descrito aqui

NOTA

  • Preciso terminar some_programde tempos em tempos
  • Eu não quero colocá-lo em segundo plano
  • Eu quero ficar no bashentão para fazer outra coisa
  • Quero poder executar o programa novamente
pawel7318
fonte
1
se o objetivo é tão complexo, você também deve explicá-lo aqui. mas isso é apenas um conselho
kiwy
1
Tentei descrever o mais curto possível aqui e com muita precisão. Mas eu não esperava que a maioria das pessoas não se concentrasse nos detalhes e tentasse propor outra coisa. Vou colocar algumas anotações para deixar claro.
precisa saber é o seguinte
Para que servem os escapes do terminal nessa outra questão? O objetivo que você descreve é ​​factível, mas exigirá manipulação de E / S. Seu subshell médio não manipula facilmente a E / S com escape de terminal por meio de um arquivo regular. Você deve olhar parapty.
mikeserv 04/04
A única razão pela qual funciona na minha resposta abaixo, a propósito, é porque eu roubo o terminal. Eventualmente, porém, é provável que o processo pai o retire - e então você está vendo buffers de 4kb.
precisa saber é o seguinte
Por que você não deseja colocar um programa em segundo plano? Iniciá-lo no fundo, fazer alguma festança, colocá-lo em primeiro plano comfg
Bernhard

Respostas:

8
( exec sh -i 3<<SCRIPT 4<&0 <&3                                        
    echo "do this thing"
    echo "do that thing"
  exec  3>&- <&4
  SCRIPT
)

É melhor fazer isso a partir de um script, embora com exec $0.Ou, se um desses descritores de arquivo direcionar para um dispositivo de terminal que não está sendo usado no momento, ajudará - você deve se lembrar, outros processos também querem verificar esse terminal.

E, a propósito, se seu objetivo é, como presumo, preservar o ambiente do script após executá-lo, você provavelmente será muito melhor atendido:

. ./script

O shell .dote bash's sourcenão são o mesmo - o shell .doté POSIX especificado como um shell especial embutido e, portanto, está o mais próximo possível de ser garantido, embora isso não seja de forma alguma uma garantia de que estará lá ...

Embora o acima deve fazer o que você espera com pouco problema. Por exemplo, você pode:

 ( exec sh -i 3<<SCRIPT 4<&0 <&3                                        
    echo "do this thing"
    echo "do that thing"
    $(cat /path/to/script)
    exec  3>&- <&4
    SCRIPT
 )

O shell executará seu script e o levará ao prompt interativo - desde que você evite exito shell do script, ou seja, faça um background de seu processo - que vinculará sua E / S ao/dev/null.

DEMO:

% printf 'echo "%s"\n' "These lines will print out as echo" \
    "statements run from my interactive shell." \
    "This will occur before I'm given the prompt." >|/tmp/script
% ( exec sh -i 3<<SCRIPT 4<&0 <&3
    echo "do this thing"
    echo "do that thing"
    $(cat /tmp/script)
    exec  3>&- <&4
SCRIPT
)
sh-4.3$ echo "do this thing"
    do this thing
sh-4.3$ echo "do that thing"
    do that thing
sh-4.3$ echo "These lines will print out as echo"
    These lines will print out as echo
sh-4.3$ echo "statements run from my interactive shell."
    statements run from my interactive shell.
sh-4.3$ echo "This will occur before I'm given the prompt."
    This will occur before I'm given the prompt.
sh-4.3$ exec  3>&- <&4
sh-4.3$

MUITOS JOBS

É minha opinião que você deve se familiarizar um pouco mais com as opções internas de gerenciamento de tarefas do shell. O @Kiwy e o @jillagre já abordaram isso em suas respostas, mas isso pode exigir mais detalhes. E eu já mencionei um shell especial específico do POSIX embutido, mas set, jobs, fg,e bgsão mais alguns, e, como outra resposta demonstra trape killsão mais dois ainda.

Se você ainda não está recebendo notificações instantâneas sobre o status de processos em segundo plano em execução simultaneamente, é porque suas opções atuais de shell estão definidas no padrão especificado pelo POSIX -m, mas você pode obtê-las de forma assíncrona set -b:

% man set
    b This option shall be supported if the implementation supports the
         User  Portability  Utilities  option. It shall cause the shell to
         notify the user asynchronously of background job completions. The
         following message is written to standard error:
             "[%d]%c %s%s\n", <job-number>, <current>, <status>, <job-name>

         where the fields shall be as follows:

         <current> The  character  '+' identifies the job that would be
                     used as a default for the fg or  bg  utilities;  this
                     job  can  also  be specified using the job_id "%+" or
                     "%%".  The character  '−'  identifies  the  job  that
                     would  become  the default if the current default job
                     were to exit; this job can also  be  specified  using
                     the  job_id  "%−".   For  other jobs, this field is a
                     <space>.  At most one job can be identified with  '+'
                     and  at  most one job can be identified with '−'.  If
                     there is any suspended  job,  then  the  current  job
                     shall  be  a suspended job. If there are at least two
                     suspended jobs, then the previous job also shall be a
   m  This option shall be supported if the implementation supports the
         User Portability Utilities option. All jobs shall be run in their
         own  process groups. Immediately before the shell issues a prompt
         after completion of the background job, a message  reporting  the
         exit  status  of  the background job shall be written to standard
         error. If a foreground job stops, the shell shall write a message
         to  standard  error to that effect, formatted as described by the
         jobs utility. In addition, if a job  changes  status  other  than
         exiting  (for  example,  if  it  stops  for input or output or is
         stopped by a SIGSTOP signal), the shell  shall  write  a  similar
         message immediately prior to writing the next prompt. This option
         is enabled by default for interactive shells.

Uma característica muito fundamental dos sistemas baseados em Unix é o método de manipulação de processos signals. Certa vez, li um artigo esclarecedor sobre o assunto que compara esse processo à descrição de Douglas Adams do planeta NowWhat:

"No Guia do Mochileiro das Galáxias, Douglas Adams menciona um planeta extremamente monótono, habitado por um grupo de humanos deprimidos e uma certa raça de animais com dentes afiados que se comunicam com os humanos mordendo-os com muita força nas coxas. Isso é impressionante. semelhante ao UNIX, no qual o kernel se comunica com os processos enviando sinais paralisantes ou mortais a eles. Os processos podem interceptar alguns dos sinais e tentar se adaptar à situação, mas a maioria não. "

Isso está se referindo kill signals.

% kill -l 
> HUP INT QUIT ILL TRAP ABRT BUS FPE KILL USR1 SEGV USR2 PIPE ALRM TERM STKFLT CHLD CONT STOP TSTP TTIN TTOU URG XCPU XFSZ VTALRM PROF WINCH POLL PWR SYS

Pelo menos para mim, a citação acima respondeu a muitas perguntas. Por exemplo, sempre achei muito estranho e nada intuitivo que, se quisesse monitorar um ddprocesso, fosse necessário kill. Depois de ler isso, fez sentido.

Eu diria que a maioria deles não tenta se adaptar por um bom motivo - pode ser um aborrecimento muito maior do que um benefício ter vários processos enviando spam ao seu terminal com qualquer informação que seus desenvolvedores pensassem que poderia ser importante para você .

Dependendo da configuração do terminal (com a qual você pode verificar stty -a) , CTRL+Zé provável que seja encaminhado a SIGTSTPpara o atual líder do grupo de processos em primeiro plano, que provavelmente é seu shell e que também deve ser configurado por padrão para trapesse sinal e suspender seu último comando. Novamente, como mostram as respostas de @jillagre e @Kiwy juntas, não há como impedir que você adapte essa funcionalidade ao seu objetivo, como preferir.

SCREEN JOBS

Portanto, para tirar proveito desses recursos, é esperado que você os entenda primeiro e personalize seu manuseio de acordo com suas próprias necessidades. Por exemplo, eu encontrei este screenrc no Github que inclui screenligações de teclas para SIGTSTP:

# hitting 'C-z C-z' will run Ctrl+Z (SIGTSTP, suspend as usual)
bind ^Z stuff ^Z

# hitting 'C-z z' will suspend the screen client
bind z suspend

Isso tornaria simples suspender um processo em execução como um screenprocesso filho ou o screenpróprio processo filho, como você desejasse.

E imediatamente depois:

% fg  

OU:

% bg

Primeiro ou segundo plano o processo como você preferir. O jobsbuilt-in pode fornecer uma lista deles a qualquer momento. A adição do -loperando incluirá detalhes do pid.

mikeserv
fonte
Parece muito interessante. Poderei testar tudo hoje mais tarde.
precisa saber é o seguinte
23

Aqui está uma solução mais curta que realiza o que você deseja, mas pode não fazer sentido, a menos que você entenda o problema e como o bash funciona:

bash -i <<< 'some_program with its arguments; exec </dev/tty'

Isso iniciará um shell bash, iniciará some_programe, após as some_programsaídas, você será direcionado para um shell bash.

Basicamente, o que estamos fazendo é alimentar o bash com uma string do STDIN. Essa string é some_program with its arguments; exec </dev/tty. Isso informa ao bash para iniciar some_programprimeiro e depois executar exec </dev/tty. Então, em vez de continuar lendo os comandos da string que passamos, o bash começará a ler /dev/tty.

O -ié porque quando o bash é iniciado, ele verifica para ver se STDIN é um tty, e quando ele começa não é. Mas mais tarde será, então forçamos o modo interativo.


Outra solução

Outra idéia que pensei que seria muito portátil seria adicionar o seguinte ao final do seu ~/.bashrcarquivo.

if [[ -n "START_COMMAND" ]]; then
  start_command="$START_COMMAND"
  unset START_COMMAND
  eval "$start_command"
fi

Então, quando você deseja iniciar um shell com um comando primeiro, basta:

START_COMMAND='some_program with its arguments' bash

Explicação:

A maior parte disso deve ser óbvia, mas o resson para o nome da variável que muda o material é para que possamos localizar a variável. Como $START_COMMANDé uma variável exportada, ela será herdada por qualquer filho do shell e, se outro shell bash for um desses filhos, ele executará o comando novamente. Portanto, atribuímos o valor a uma nova variável não exportada ( $start_command) e excluímos a antiga.

Patrick
fonte
Explodir se houver mais de um quê? Um comando? não, ainda deve funcionar. Você está certo sobre a portabilidade. Existem 2 fatores lá, o <<<não é POSIX, mas você poderia echo "string here" | bash -i. Depois, há o /dev/ttyque é uma coisa do linux. Mas você pode enganar o FD antes de iniciar o bash e reabrir o STDIN, que é semelhante ao que você está fazendo, mas optei por manter as coisas simples.
Patrick
ok ok apenas não lute. Isso funciona para mim e faz tudo o que eu preciso. Não me importo muito com POSIX e portabilidade, preciso na minha caixa e só lá. Vou verificar a resposta de mikeserv também, mas não posso fazer isso agora.
precisa saber é o seguinte
1
Não brigando :-). Eu respeito a resposta do mikserv. Ele foi pela compatibilidade, eu fui pela simplicidade. Ambos são completamente válidos. Às vezes, busco compatibilidade se não for muito complexo. Nesse caso, não achei que valesse a pena.
Patrick
2
@ Patrick, /dev/ttynão é uma coisa do Linux, mas definitivamente POSIX.
jlliagre
2
Ei, whaddayaknow .
Patrick
8

Isso deve fazer o truque:

bash -c "some_program with its arguments;bash"

Editar:

Aqui está uma nova tentativa após sua atualização:

bash -c "
trap 'select wtd in bash restart exit; do [ \$wtd = restart ] && break || \$wtd ; done' 2
while true; do
    some_program with its arguments
done
"
  • Preciso finalizar some_program de tempos em tempos

Use ControlC, você verá este pequeno menu:

1) bash
2) restart
3) exit
  • Eu não quero colocá-lo em segundo plano

Esse é o caso

  • Eu quero ficar na festança e fazer outra coisa

Selecione a opção "bash"

  • Quero poder executar o programa novamente

Selecione a opção "reiniciar"

jlliagre
fonte
não é ruim, não é ruim .. mas seria bom também poder voltar ao último comando. Alguma idéia para isso? Por favor, verifique minha outra pergunta aqui para ver o que eu preciso. Então, talvez você vai sugerir uma outra abordagem
pawel7318
Isso executa um subshell. Eu acho que o solicitante está tentando preservar o ambiente do script.
precisa saber é o seguinte
obrigado jlliagre - boa tentativa, mas não muito útil para mim. Normalmente, pressione ctrl + C e espero que faça apenas o que deveria. Menu adicional é apenas muito.
precisa saber é o seguinte
@ pawel7318 o comportamento de CTRL+Cé apenas um efeito colateral da configuração padrão do seu terminal para interpretá-lo como um SIGINT. Você pode modificar isso como quiser stty.
precisa saber é o seguinte
@ pawel7318 para esclarecer mais, SIGINTestá trappedacima de 2.
precisa saber é o seguinte
3

Você pode fazer isso passando seu script como um arquivo de inicialização:

bash --init-file foo.script

Ou você pode transmiti-lo na linha de comando:

bash --init-file <(echo "ls -al")

Observe que isso --init-filefoi feito para a leitura de arquivos de inicialização em todo o sistema, /etc/bash.bashrcassim, você pode " source" desejá- los em seu script.

jeroent
fonte
0

Eu realmente não vejo o objetivo de fazer isso, já que você já volta para um shell depois de executar este programa. No entanto, você pode fazer isso:

bash -c "some_program with its arguments; bash"

Isso iniciará um bash interativo após a execução do programa.

mtak
fonte
Isso lança um subshell.
precisa saber é o seguinte
0

Você pode colocar o comando em segundo plano para manter seu bash atual aberto:

some_program with its arguments &

Para retornar à execução, você pode usar o fgcomando e ^+zcolocá-lo em segundo plano novamente

Kiwy
fonte
não dessa vez. Você assume que eu quero executar este bash ... do bash, que não é o caso.
precisa saber é o seguinte
@ pawel7318 se você explicar um pouco mais, poderíamos dar uma resposta melhor, talvez também?
Kiwy 04/04
Por favor, verifique minha outra pergunta aqui .
precisa saber é o seguinte
@ pawel7318 se a sua pergunta estiver relacionada, adicione o link à outra pergunta na sua própria pergunta.
Kiwy 04/04
1
@ pawel7318 bashou não, você está usando um shell muito estranho para mim, se ele não consegue lidar com o processo em segundo plano. E eu concordo com Kiwi - essas informações o serviriam melhor se estivessem na sua pergunta.
precisa saber é o seguinte
0

Você pode usar a tela para executar o comando. Em seguida, você pode reconectar à sessão após a conclusão do comando.

Como alternativa, basta executar o comando em segundo plano some_program with its arguments&. Isso permitirá que você execute novamente o comando e obtenha o status do comando depois que ele for concluído.

BillThor
fonte
Estou executando exatamente, screenmas colocá-lo em segundo plano não é útil para mim - às vezes preciso encerrar o programa, fazer outra coisa e executá-lo novamente. E o objetivo principal é fazer isso rápido.
precisa saber é o seguinte
@ pawel7318 Você pode matar o programa em segundo plano com o comando kill %ou kill %1.
BillThor