Comment garder Bash en cours d'exécution après l'exécution de la commande?

29

Je voudrais lancer quelque chose comme ceci:

bash -c "some_program with its arguments"

mais pour avoir un bash interactif continuez à courir après la some_programfin.

Je suis sûr que ce -cn'est pas un bon moyen de man bashseys:

Un shell interactif est un shell démarré sans arguments sans option et sans l'option -c

Alors comment le faites vous ?

L'objectif principal est décrit ici

REMARQUE

  • Je dois résilier some_programde temps en temps
  • Je ne veux pas le mettre en arrière-plan
  • Je veux rester sur le bashmoment pour faire autre chose
  • Je veux pouvoir réexécuter le programme
pawel7318
la source
1
si l'objectif est aussi complexe, vous devez également l'expliquer ici. mais ce n'est qu'un conseil
Kiwy
1
J'ai essayé de décrire le plus brièvement possible ici et très précisément. Mais je ne m'attendais pas à ce que la plupart des gens ne se concentrent pas sur les détails et essaient de proposer autre chose. Je vais mettre quelques notes pour que ce soit clair.
pawel7318
À quoi servent les sorties du terminal dans cette autre question? L'objectif que vous décrivez est réalisable, mais il faudra gérer les E / S. Votre sous-shell moyen ne gérera pas facilement les E / S d'échappement de terminal via un fichier normal. Vous devriez examinerpty.
mikeserv
La seule raison pour laquelle cela fonctionne dans ma réponse ci-dessous, soit dit en passant, c'est parce que je vole le terminal. Finalement, cependant, le processus parent est susceptible de le reprendre - et vous regardez ensuite des tampons de 4 Ko.
mikeserv
Pourquoi ne voulez-vous pas mettre un programme en arrière-plan? Commencez-le en arrière-plan, faites quelques bash, mettez-le au premier plan avecfg
Bernhard

Réponses:

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

Il est préférable de le faire à partir d'un script avec exec $0.Or ou si l'un de ces descripteurs de fichiers dirige vers un périphérique terminal qui n'est pas actuellement utilisé, cela vous aidera - vous devez vous rappeler que d'autres processus veulent également vérifier ce terminal.

Et au fait, si votre objectif est, comme je le suppose, de préserver l'environnement du script après l'avoir exécuté, vous seriez probablement beaucoup mieux servi avec:

. ./script

Le shell .dotet bash's sourcene sont pas une seule et même chose - le shell .dotest POSIX spécifié comme un shell spécial intégré et est donc aussi proche que possible de la garantie, bien que ce ne soit en aucun cas une garantie qu'il sera là ...

Bien que ce qui précède devrait faire ce que vous attendez avec peu de problèmes. Par exemple, vous pouvez:

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

Le shell exécutera votre script et vous renverra à l'invite interactive - tant que vous évitez d' exitutiliser le shell de votre script, c'est-à-dire, ou d'arrière-plan de votre processus - qui liera vos E / S à/dev/null.

DÉMO:

% 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$

BEAUCOUP JOBS

Je pense que vous devriez vous familiariser un peu avec les options de gestion des tâches intégrées du shell. @Kiwy et @jillagre ont tous deux déjà abordé ce sujet dans leurs réponses, mais cela pourrait justifier des détails supplémentaires. Et j'ai déjà mentionné un shell spécial spécifié par POSIX intégré, maisset, jobs, fg, et il bgy en a quelques autres, et, comme une autre réponse le démontre, trapet killdeux autres encore.

Si vous ne recevez pas déjà des notifications instantanées sur l'état des processus en arrière-plan exécutés simultanément, c'est parce que vos options de shell actuelles sont définies sur la valeur par défaut spécifiée par POSIX -m, mais vous pouvez les obtenir de manière asynchrone avec 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.

Une caractéristique très fondamentale des systèmes basés sur Unix est leur méthode de gestion des processus signals. J'ai lu une fois un article éclairant sur le sujet qui compare ce processus à la description de la planète par Douglas Adams NowWhat:

"Dans le Guide de l'auto-stoppeur de la galaxie, Douglas Adams mentionne une planète extrêmement terne, habitée par un groupe d'humains déprimés et une certaine race d'animaux aux dents pointues qui communiquent avec les humains en les mordant très fort dans les cuisses. C'est frappant similaire à UNIX, dans lequel le noyau communique avec les processus en leur envoyant des signaux paralysants ou mortels. Les processus peuvent intercepter certains des signaux et essayer de s'adapter à la situation, mais la plupart ne le font pas. "

Il s'agit de 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

Au moins pour moi, la citation ci-dessus a répondu à beaucoup de questions. Par exemple, j'avais toujours considéré comme très étrange et pas du tout intuitif que si je voulais surveiller undd processus, je devais le faire kill. Après avoir lu cela, cela avait du sens.

Je dirais que la plupart d'entre eux n'essaient pas de s'adapter pour une bonne raison - cela peut être bien plus ennuyeux que ce serait une aubaine d'avoir un tas de processus spammant votre terminal avec les informations que leurs développeurs pensaient avoir été importantes pour vous .

En fonction de la configuration de votre terminal (avec laquelle vous pouvez vérifier stty -a) , CTRL+Zest probablement défini pour transmettre un SIGTSTPau leader du groupe de processus de premier plan actuel, qui est probablement votre shell, et qui devrait également être configuré par défaut pourtrap ce signal et suspendre votre dernière commande. Encore une fois, comme le montrent les réponses de @jillagre et @Kiwy, rien ne vous empêche d'adapter cette fonctionnalité à votre objectif comme vous le souhaitez.

SCREEN JOBS

Donc, pour tirer parti de ces fonctionnalités, vous devez d'abord les comprendre et personnaliser leur gestion en fonction de vos propres besoins. Par exemple, je viens de trouver ce screenrc sur Github qui inclut des screenraccourcis clavier pour 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

Cela simplifierait la suspension d'un processus exécuté en tant qu'enfant screen processus ou du screenprocessus enfant lui-même comme vous le souhaitiez.

Et immédiatement après:

% fg  

OU:

% bg

Mettre en avant ou en arrière-plan le processus selon vos préférences. La fonction jobsintégrée peut vous fournir une liste de ces éléments à tout moment. L'ajout de l' -lopérande inclura les détails du pid.

mikeserv
la source
Semble très intéressant. Je pourrai tout tester plus tard dans la journée.
pawel7318
23

Voici une solution plus courte qui accomplit ce que vous voulez, mais qui n'a de sens que si vous comprenez le problème et comment fonctionne bash:

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

Cela lancera un shell bash, démarrera some_programet aprèssome_program sortie, vous serez déposé sur un shell bash.

Fondamentalement, ce que nous faisons est d'alimenter bash une chaîne sur son STDIN. Cette chaîne est some_program with its arguments; exec </dev/tty. Cela indique à bash de se lancer en some_programpremier, puis de s'exécuter ensuite exec </dev/tty. Ainsi, au lieu de continuer à lire les commandes de la chaîne que nous lui transmettons, bash commencera à lire /dev/tty.

C'est -iparce que quand bash démarre, il vérifie si STDIN est un tty, et quand il démarre ce n'est pas le cas. Mais plus tard, ce sera le cas, nous le forçons donc en mode interactif.


Une autre solution

Une autre idée à laquelle je pensais que ce serait très portable serait d'ajouter ce qui suit à la toute fin de votre ~/.bashrcfichier.

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

Ensuite, lorsque vous voulez d'abord lancer un shell avec une commande, faites simplement:

START_COMMAND='some_program with its arguments' bash

Explication:

La plupart de cela devrait être évident, mais le résultat de la modification du nom de variable est de sorte que nous puissions localiser la variable. Puisqu'il $START_COMMANDs'agit d'une variable exportée, elle sera héritée par tous les enfants du shell, et si un autre shell bash est l'un de ces enfants, il exécutera à nouveau la commande. Nous attribuons donc la valeur à une nouvelle variable non exportée ( $start_command) et supprimons l'ancienne.

Patrick
la source
Fais exploser s'il y en a plus d'un quoi? Une commande? non, devrait toujours fonctionner. Vous avez cependant raison sur la portabilité. Il y a 2 facteurs, ce <<<n'est pas POSIX, mais vous pouvez le faire à la echo "string here" | bash -iplace. Ensuite, il y a /dev/ttyce qui est une chose Linux. Mais vous pouvez dupliquer le FD avant de lancer bash, puis rouvrir STDIN, qui ressemble à ce que vous faites, mais j'ai choisi de garder les choses simples.
Patrick
ok ok juste ne vous battez pas. Cela fonctionne juste pour moi et fait tout ce dont j'ai besoin. Je ne me soucie pas beaucoup de POSIX et de la portabilité, j'en ai besoin sur ma boîte et seulement là. Je vais également vérifier la réponse de mikeserv, mais je ne peux pas le faire maintenant.
pawel7318
1
Ne pas se battre :-). Je respecte la réponse de mikserv. Il est allé pour la compatibilité, je suis allé pour la simplicité. Les deux sont complètement valides. Parfois, j'irai pour la compatibilité si ce n'est pas trop complexe. Dans ce cas, je ne pensais pas que cela en valait la peine.
Patrick
2
@Patrick, /dev/ttyn'est pas une chose Linux mais certainement POSIX.
jlliagre
2
Hé, whaddayaknow .
Patrick
8

Cela devrait faire l'affaire:

bash -c "some_program with its arguments;bash"

Modifier:

Voici une nouvelle tentative suite à votre mise à jour:

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
"
  • J'ai besoin de mettre fin à some_program de temps en temps

Utilisez ControlC, vous serez présenté ce petit menu:

1) bash
2) restart
3) exit
  • Je ne veux pas le mettre en arrière-plan

C'est le cas.

  • Je veux rester sur le coup puis faire autre chose

Sélectionnez le choix "bash"

  • Je veux pouvoir réexécuter le programme

Sélectionnez le choix "redémarrer"

jlliagre
la source
pas mal pas mal .. mais ce serait bien aussi de pouvoir revenir à cette dernière commande. Des idées pour ça? Veuillez vérifier ma autre question ici pour voir pourquoi j'en ai besoin. Alors peut-être que vous suggérerez une autre approche
pawel7318
Cela exécute un sous-shell. Je pense que le demandeur essaie de préserver l'environnement de script.
mikeserv
merci jlliagre - belle tentative mais pas très utile pour moi. J'appuie habituellement sur ctrl + C et je m'attends à ce qu'il fasse juste ce qu'il suppose de faire. Le menu supplémentaire est juste trop.
pawel7318
@ pawel7318 le comportement de CTRL+Cn'est qu'un effet secondaire de la configuration par défaut de votre terminal pour l'interpréter comme un SIGINT. Vous pouvez modifier cela à votre guise stty.
mikeserv
@ pawel7318 pour clarifier davantage, SIGINTest trappedci - dessus avec 2.
mikeserv
3

Vous pouvez le faire en passant votre script en tant que fichier d'initialisation:

bash --init-file foo.script

Ou vous pouvez le transmettre sur la ligne de commande:

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

Notez que cela --init-fileétait destiné à la lecture de fichiers d'initialisation à l'échelle du système, de /etc/bash.bashrcsorte que vous souhaitiez peut-être les ' source' dans votre script.

jeroent
la source
0

Je ne vois pas vraiment l'intérêt de faire cela, car vous revenez déjà à un shell après avoir exécuté ce programme. Cependant, vous pouvez:

bash -c "some_program with its arguments; bash"

Cela lancera une bash interactive après l'exécution du programme.

mtak
la source
Cela lance un sous-shell.
mikeserv
0

Vous pouvez mettre la commande en arrière-plan pour garder votre bash actuel ouvert:

some_program with its arguments &

Pour revenir à la course, vous pouvez alors utiliser la fgcommande et la ^+zremettre en arrière-plan

Kiwy
la source
pas cette fois. Vous supposez que je veux exécuter cette bash ... à partir de bash, ce qui n'est pas le cas.
pawel7318
@ pawel7318 si vous expliquez un peu plus nous pourrions vous donner une meilleure réponse peut-être aussi?
Kiwy
Veuillez vérifier ma autre question ici .
pawel7318
@ pawel7318 si votre question est liée, veuillez ajouter le lien vers l'autre question dans votre propre question.
Kiwy
1
@ pawel7318 bashou non, vous utilisez pour moi un shell très étranger s'il ne peut pas gérer l'arrière-plan du processus. Et je suis d'accord avec Kiwi - cette information vous servirait mieux si elle était dans votre question.
mikeserv
0

Vous souhaiterez peut-être utiliser l'écran pour exécuter la commande. Vous pouvez ensuite vous reconnecter à la session une fois la commande terminée.

Sinon, exécutez simplement la commande en arrière-plan some_program with its arguments&. Cela vous laissera la possibilité de réexécuter la commande et d'obtenir le statut de la commande une fois celle-ci terminée.

BillThor
la source
Je l'exécute exactement depuis, screenmais le mettre en arrière-plan ne me sert pas - j'ai parfois besoin de terminer le programme, de faire autre chose et de le relancer. Et l'objectif principal est de le faire rapidement.
pawel7318
@ pawel7318 Vous pouvez tuer le programme d'arrière-plan avec la commande kill %ou kill %1.
BillThor