Come mantenere Bash in esecuzione dopo l'esecuzione del comando?


29

Vorrei eseguire qualcosa del genere:

bash -c "some_program with its arguments"

ma per avere un bash interattivo continuare a correre dopo le some_programestremità.

Sono sicuro che -cnon è un buon modo come man bashvede:

Una shell interattiva è avviata senza argomenti non opzionali e senza l'opzione -c

Quindi come si fa?

L'obiettivo principale è descritto qui

NOTA

  • Ho bisogno di terminare some_programdi volta in volta
  • Non voglio metterlo in secondo piano
  • Voglio continuare basha fare allora qualcos'altro
  • Voglio essere in grado di eseguire nuovamente il programma

1
se l'obiettivo è così complesso dovresti anche spiegarlo qui. ma questo è solo un consiglio
Kiwy

1
Ho cercato di descriverlo il più breve possibile qui e in modo molto preciso. Ma non mi aspettavo che la maggior parte delle persone non si concentrasse sui dettagli e provasse a proporre qualcos'altro. Metterò alcune note per chiarire.
pawel7318,

A cosa servono le fughe del terminale in quell'altra domanda? L'obiettivo che descrivi è fattibile, ma richiederà la gestione degli I / O. La tua subshell media non gestirà facilmente gli I / O con escape di terminale attraverso un file normale. Dovresti dare un'occhiata apty.
mikeserv

L'unico motivo per cui funziona nella mia risposta di seguito, a proposito, è perché rubo il terminale. Alla fine, però, è probabile che il processo genitore lo riprenda - e quindi stai esaminando i buffer da 4kb.
Mikeserv,

Perché non vuoi mettere un programma in background? Inizia in background, fai un po 'di bash, mettilo in primo piano confg
Bernhard

Risposte:


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

Questo è meglio se lo script viene eseguito con exec $0.O se uno di quei descrittori di file indirizza a un dispositivo terminale che non è attualmente utilizzato, sarà di aiuto: devi ricordare che anche altri processi vogliono controllare quel terminale.

E a proposito, se il tuo obiettivo è, come presumo sia, preservare l'ambiente dello script dopo averlo eseguito, probabilmente saresti molto meglio servito con:

. ./script

Della shell .dote bash's sourcenon sono la stessa cosa - della shell .dotè POSIX specificato come uno speciale comando incorporato della shell ed è quindi il più vicino ad essere garantito come si può ottenere, anche se questo non è affatto una garanzia che ci sarà ...

Sebbene quanto sopra dovrebbe fare come ti aspetti con un piccolo problema. Ad esempio, puoi:

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

La shell eseguirà il tuo script e ti riporterà al prompt interattivo - a patto che tu eviti di utilizzare exitla shell dal tuo script, ovvero, o in background il tuo processo - che collegherà il tuo I / O a/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$

MOLTI JOBS

Ritengo che dovresti familiarizzare con le opzioni integrate di gestione delle attività della shell. @Kiwy e @jillagre hanno già toccato questo aspetto nelle loro risposte, ma potrebbe giustificare ulteriori dettagli. E ho già detto una conchiglia speciale POSIX specificato built-in, ma set, jobs, fg,e bgsono un po 'di più, e, come un'altra risposta dimostra trape killsono due altri ancora.

Se non stai già ricevendo notifiche istantanee sullo stato di processi in background in esecuzione contemporaneamente, è perché le tue opzioni di shell correnti sono impostate sul valore predefinito specificato da POSIX di -m, ma puoi invece ottenere queste in modo asincrono con 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.

Una caratteristica fondamentale dei sistemi basati su Unix è il loro metodo di gestione dei processi signals. Una volta ho letto un articolo illuminante sull'argomento che paragona questo processo alla descrizione del pianeta di Douglas Adams NowWhat:

"In The Hitchhiker's Guide to the Galaxy, Douglas Adams menziona un pianeta estremamente noioso, abitato da un gruppo di umani depressi e una certa razza di animali con denti aguzzi che comunicano con gli umani mordendoli molto duramente nelle cosce. Questo è sorprendentemente simile a UNIX, in cui il kernel comunica con i processi inviando loro segnali paralizzanti o mortali. I processi possono intercettare alcuni segnali e provare ad adattarsi alla situazione, ma la maggior parte di loro no. "

Questo si riferisce a 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

Almeno per me, la citazione sopra ha risposto a molte domande. Ad esempio, avevo sempre considerato molto strano e per nulla intuitivo che se avessi voluto monitorare un ddprocesso, dovevo killfarlo. Dopo aver letto che aveva senso.

Direi che la maggior parte di loro non cerca di adattarsi per una buona ragione - può essere un fastidio molto maggiore di quanto sarebbe un vantaggio avere un sacco di processi che inviano spam al tuo terminale con qualsiasi informazione che i loro sviluppatori pensassero potesse essere importante per te .

A seconda della configurazione del terminale (che è possibile verificare stty -a) , CTRL+Zè probabile che venga inoltrato a un SIGTSTPleader del gruppo di processi in primo piano corrente, che è probabilmente la shell, e che dovrebbe anche essere configurato per impostazione predefinita a trapquel segnale e sospendere l'ultimo comando. Ancora una volta, come mostrano le risposte di @jillagre e @Kiwy insieme, non c'è modo di impedirti di adattare questa funzionalità al tuo scopo come preferisci.

SCREEN JOBS

Pertanto, per trarre vantaggio da queste funzionalità, è necessario prima comprenderle e personalizzarne la gestione in base alle proprie esigenze. Ad esempio, ho appena trovato questo screenrc su Github che include combinazioni discreen tasti per 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

Ciò renderebbe semplice sospendere un processo in esecuzione come screenprocesso figlio o il screenprocesso figlio stesso come desiderato.

E subito dopo:

% fg  

O:

% bg

In primo piano o in background il processo come preferisci. Il jobsbuilt-in può fornirti un elenco di questi in qualsiasi momento. L'aggiunta -ldell'operando includerà i dettagli pid.


Sembra molto interessante Sarò in grado di testarlo tutto più tardi oggi.
pawel7318,

23

Ecco una soluzione più breve che realizza ciò che desideri, ma potrebbe non avere senso se non capisci il problema e come funziona bash:

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

Questo avvierà una shell bash, partirà some_programe dopo le some_programuscite, verrai rilasciato a una shell bash.

Fondamentalmente quello che stiamo facendo è alimentare bash una stringa su STDIN. Quella stringa è some_program with its arguments; exec </dev/tty. Questo dice a bash di avviarsi some_programprima e poi di eseguirlo exec </dev/tty. Quindi invece di continuare a leggere i comandi dalla stringa che la passiamo, bash inizierà a leggere /dev/tty.

Questo -iperché quando bash si avvia, controlla se STDIN è un tty e quando inizia non lo è. Ma dopo lo sarà, quindi lo forziamo in modalità interattiva.


Un'altra soluzione

Un'altra idea che ho pensato che sarebbe molto portabile sarebbe quella di aggiungere quanto segue alla fine del tuo ~/.bashrcfile.

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

Quindi, quando si desidera avviare prima una shell con un comando, fare semplicemente:

START_COMMAND='some_program with its arguments' bash

Spiegazione:

La maggior parte di questo dovrebbe essere ovvio, ma la risonanza per il nome della variabile che cambia roba è in modo che possiamo localizzare la variabile. Poiché $START_COMMANDè una variabile esportata, verrà ereditata da tutti i figli della shell e se un'altra shell bash è una di quelle figlie, eseguirà nuovamente il comando. Quindi assegniamo il valore a una nuova variabile non esportata ( $start_command) ed eliminiamo quella vecchia.


Fai saltare in aria se ce n'è più di una cosa? Un comando? no, dovrebbe ancora funzionare. Hai ragione sulla portabilità però. Ci sono 2 fattori lì, il <<<non è POSIX, ma potresti echo "string here" | bash -iinvece. Poi c'è /dev/ttyqual è una cosa di Linux. Ma potresti duplicare l'FD prima di lanciare bash e quindi riaprire STDIN, che sembra simile a quello che stai facendo, ma ho optato per mantenere le cose semplici.
Patrick,

ok ok non combattere, questo funziona solo per me e fa tutto ciò di cui ho bisogno. Non mi interessa molto di POSIX e portabilità, ne ho bisogno sulla mia scatola e solo lì. Controllerò anche la risposta di Mikeserv, ma non posso farlo ora.
pawel7318,

1
Non combattere :-). Rispetto la risposta di Mikserv. È andato per compatibilità, io per semplicità. Entrambi sono completamente validi. A volte vado per la compatibilità se non è eccessivamente complesso. In questo caso non pensavo ne valesse la pena.
Patrick,

2
@Patrick, /dev/ttynon è una cosa Linux ma sicuramente POSIX.
jlliagre,


8

Questo dovrebbe fare il trucco:

bash -c "some_program with its arguments;bash"

Modificare:

Ecco un nuovo tentativo dopo l'aggiornamento:

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
"
  • Devo terminare di tanto in tanto un programma

Usa ControlC, ti verrà presentato questo piccolo menu:

1) bash
2) restart
3) exit
  • Non voglio metterlo in secondo piano

Questo è il caso.

  • Voglio rimanere sulla bash quindi fare qualcos'altro

Seleziona l'opzione "bash"

  • Voglio essere in grado di eseguire nuovamente il programma

Seleziona l'opzione "riavvia"


non male non male .. ma sarebbe bene anche poter tornare a quell'ultimo comando. Qualche idea per quello? Per favore, controlla la mia altra domanda qui per vedere a cosa mi serve. Quindi forse suggerirai un altro approccio
pawel7318,

Questo esegue una subshell. Penso che il richiedente stia cercando di preservare l'ambiente degli script.
Mikeserv,

grazie jlliagre - bel tentativo ma non molto utile per me. Di solito premo ctrl + C e mi aspetto che faccia esattamente ciò che suppone. Il menu aggiuntivo è troppo.
pawel7318,

@ pawel7318 il comportamento di CTRL+Cè solo un effetto collaterale della configurazione predefinita del tuo terminale per interpretarlo come a SIGINT. Puoi modificarlo come desideri stty.
Mikeserv,

@ pawel7318 per chiarire ulteriormente, SIGINTè trappedsopra con 2.
Mikeserv,

3

Puoi farlo passando lo script come file di inizializzazione:

bash --init-file foo.script

Oppure puoi passarlo sulla riga di comando:

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

Nota che --init-fileera pensato per leggere file di inizializzazione di sistema come questo, /etc/bash.bashrcquindi potresti volerli ' source' nel tuo script.


0

Non vedo davvero il punto di farlo, dato che torni a una shell dopo aver eseguito questo programma. Tuttavia, potresti:

bash -c "some_program with its arguments; bash"

Ciò avvierà una bash interattiva dopo l'esecuzione del programma.


Questo avvia una subshell.
Mikeserv,

0

Potresti mettere il comando in background per mantenere aperto il tuo bash corrente:

some_program with its arguments &

Per tornare alla corsa è quindi possibile utilizzare il fgcomando e ^+zrimetterlo in background


Non questa volta. Supponi che io voglia eseguire questo bash ... da bash che non è il caso.
pawel7318,

@ pawel7318 se spieghi un po 'di più potremmo darti una risposta migliore forse anche?
Kiwy,

Per favore, controlla la mia altra domanda qui .
pawel7318,

@ pawel7318 se la tua domanda è correlata, aggiungi il link all'altra domanda nella tua domanda.
Kiwy,

1
@ pawel7318 basho no, stai usando una shell molto aliena per me se non riesce a gestire lo sfondo del processo. E sono d'accordo con Kiwi - che le informazioni ti servirebbero meglio se fossero nella tua domanda.
Mikeserv,

0

Potresti voler usare lo schermo per eseguire il comando. È quindi possibile ricollegarsi alla sessione al termine del comando.

In alternativa, basta eseguire il comando in background some_program with its arguments&. Questo ti lascerà la possibilità di rieseguire il comando e ottenere lo stato del comando una volta fatto.


Lo sto eseguendo esattamente da screenma metterlo in background non è utile per me - a volte ho bisogno di terminare il programma, fare qualcos'altro ed eseguirlo di nuovo. E l'obiettivo principale è farlo velocemente.
pawel7318,

@ pawel7318 Puoi terminare il programma in background con il comando kill %o kill %1.
BillThor
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.