Wie kann ich Bash nach der Befehlsausführung laufen lassen?

29

Ich würde gerne so etwas machen:

bash -c "some_program with its arguments"

Aber um eine interaktive Bash zu haben, läuft sie nach dem some_programEnde weiter.

Ich bin mir sicher, dass -cdas kein guter Weg ist man bash:

Eine interaktive Shell wird ohne optionale Argumente und ohne die Option -c gestartet

Wie mache ich das?

Das Hauptziel wird hier beschrieben

HINWEIS

  • Ich muss some_programvon Zeit zu Zeit kündigen
  • Ich möchte es nicht in den Hintergrund stellen
  • Ich möchte auf dem Laufenden bleiben bash, um etwas anderes zu tun
  • Ich möchte das Programm erneut ausführen können
pawel7318
quelle
1
Wenn das Ziel so komplex ist, sollten Sie es auch hier erklären. Aber das ist nur ein
Hinweis
1
Ich habe versucht, hier so kurz wie möglich und sehr genau zu beschreiben. Aber ich hatte nicht erwartet, dass sich die meisten Leute nicht auf die Details konzentrieren und versuchen, etwas anderes vorzuschlagen. Ich werde ein paar Notizen machen, um es klar zu machen.
Pawel7318
Wofür flüchtet das Terminal in dieser anderen Frage? Das Ziel, das Sie beschreiben, ist machbar, erfordert jedoch den Umgang mit I / O. Ihre durchschnittliche Subshell wird es nicht leicht haben, Terminal-Escape-E / A-Vorgänge durch eine reguläre Datei zu verarbeiten. Du solltest nachsehenpty.
mikeserv
Der einzige Grund, warum es in meiner Antwort unten funktioniert, ist übrigens, dass ich das Terminal klaue. Letztendlich wird der übergeordnete Prozess ihn jedoch wahrscheinlich zurücknehmen - und dann sehen Sie sich 4-KB-Puffer an.
MikeServ
Warum willst du kein Programm in den Hintergrund stellen? Starte es im Hintergrund, mache ein paar Bashs, setze es in den Vordergrund mitfg
Bernhard

Antworten:

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

Dies geschieht jedoch besser über ein Skript mit. exec $0.Oder wenn einer dieser Dateideskriptoren auf ein Endgerät verweist, das derzeit nicht verwendet wird, hilft dies - Sie müssen sich daran erinnern, dass auch andere Prozesse dieses Terminal überprüfen möchten.

Übrigens, wenn Ihr Ziel, wie ich annehme, darin besteht, die Umgebung des Skripts nach dessen Ausführung zu erhalten, werden Sie wahrscheinlich viel besser bedient:

. ./script

Die Shells .dotund bash's sourcesind nicht ein und dasselbe - die .dotShells sind POSIX-spezifisch als spezielle eingebaute Shell spezifiziert und daher so nah wie möglich an der Garantie, obwohl dies keineswegs eine Garantie dafür ist, dass sie da sind ...

Obgleich das oben genannte mit wenig Ausgabe tun sollte, wie Sie erwarten. Zum Beispiel können Sie:

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

Die Shell führt Ihr Skript aus und kehrt zur interaktiven Eingabeaufforderung zurück - sofern Sie nicht exitdie Shell aus Ihrem Skript entfernen oder Ihren Prozess im Hintergrund ausführen -, mit der Ihre E / A verknüpft wird/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$

VIELE JOBS

Ich bin der Meinung, dass Sie sich ein wenig mit den integrierten Aufgabenverwaltungsoptionen der Shell vertraut machen sollten. @Kiwy und @jillagre haben dies bereits in ihren Antworten angesprochen, aber es könnte weitere Einzelheiten rechtfertigen. Und ich habe schon ein POSIX-spezifizierte spezielle Shell erwähnt eingebaut, aber set, jobs, fg,und bgsind ein paar mehr, und als eine andere Antwort zeigt , trapund killzwei weitere noch.

Wenn Sie noch keine Sofortbenachrichtigungen zum Status gleichzeitig ausgeführter Hintergrundprozesse erhalten, liegt dies daran, dass Ihre aktuellen Shell-Optionen auf den von POSIX angegebenen Standardwert von festgelegt -msind. Sie können diese jedoch auch asynchron set -babrufen:

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

Ein sehr grundlegendes Merkmal von Unix-basierten Systemen ist die Art und Weise, wie sie verarbeitet werden signals. Ich habe einmal einen aufschlussreichen Artikel zu diesem Thema gelesen , der diesen Prozess mit Douglas Adams 'Beschreibung des Planeten NowWhat in Einklang bringt:

"In" Per Anhalter durch die Galaxis "erwähnt Douglas Adams einen extrem langweiligen Planeten, der von einer Gruppe depressiver Menschen und einer bestimmten Tierrasse mit scharfen Zähnen bewohnt wird, die mit den Menschen kommunizieren, indem sie sich sehr fest in die Oberschenkel beißen. Dies ist auffällig Ähnlich wie UNIX, bei dem der Kernel mit Prozessen kommuniziert, indem er lähmende oder tödliche Signale an sie sendet. Prozesse können einige der Signale abfangen und versuchen, sich an die Situation anzupassen, aber die meisten von ihnen tun dies nicht. "

Dies bezieht sich auf 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

Zumindest für mich beantwortete das obige Zitat viele Fragen. Zum Beispiel hatte ich es immer als sehr seltsam und überhaupt nicht intuitiv empfunden, dass ich einen ddProzess überwachen musste , wenn ich ihn überwachen wollte kill. Nachdem ich das gelesen hatte, ergab es Sinn.

Ich würde sagen, die meisten von ihnen versuchen nicht aus gutem Grund , sich anzupassen - es kann weitaus ärgerlicher sein als es ein Segen, wenn eine Reihe von Prozessen Ihr Terminal mit den Informationen spammt, die die Entwickler für Sie als wichtig erachtet haben .

Je nach Terminalkonfiguration (die Sie mit überprüfen stty -a) , CTRL+Zist wahrscheinlich Satz ein weiterleiten SIGTSTPden aktuellen Vordergrundprozess Gruppenleiter, die Wahrscheinlichkeit , dass Ihr Schal ist und das sollte auch standardmäßig so konfiguriert werden , trapdass Signal und unterbrechen Sie Ihren letzten Befehl. Wie die Antworten von @jillagre und @Kiwy zusammen zeigen, können Sie diese Funktionalität ohne weiteres an Ihren gewünschten Zweck anpassen.

SCREEN JOBS

Um diese Funktionen nutzen zu können, müssen Sie sie zunächst verstehen und ihre Handhabung an Ihre eigenen Bedürfnisse anpassen. Ich habe zum Beispiel gerade diesen Screenrc auf Github gefunden , der screenTastaturbelegungen für Folgendes enthält 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

Das macht es einfach, einen Prozess, der als untergeordneter screenProzess ausgeführt wird, oder den screenuntergeordneten Prozess selbst anzuhalten, wie Sie es wünschen.

Und gleich danach:

% fg  

ODER:

% bg

Wäre Vordergrund oder Hintergrund des Prozesses, wie Sie es vorgezogen haben. Die jobseingebaute kann Ihnen jederzeit eine Liste dieser zur Verfügung stellen. Das Hinzufügen des -lOperanden enthält PID-Details.

mikeserv
quelle
Sieht sehr interessant aus. Ich kann das alles später noch testen.
Pawel7318
23

Hier ist eine kürzere Lösung, die das erreicht, was Sie wollen, aber möglicherweise keinen Sinn ergibt, wenn Sie das Problem nicht verstehen und wissen, wie bash funktioniert:

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

Dadurch wird eine Bash-Shell gestartet some_programund nach dem some_programBeenden wird eine Bash-Shell aufgerufen.

Grundsätzlich füttern wir einen String mit STDIN. Diese Zeichenfolge ist some_program with its arguments; exec </dev/tty. Dies weist bash an, some_programzuerst zu starten und danach auszuführen exec </dev/tty. Anstatt also weiterhin Befehle aus der übergebenen Zeichenfolge zu lesen, beginnt die Bash mit dem Lesen ab /dev/tty.

Das -iliegt daran , dass , wenn bash startet, überprüft er, ob STDIN ein tty ist, und wenn es beginnt , ist es nicht. Aber später wird es so sein, also zwingen wir es in den interaktiven Modus.


Eine andere Lösung

Eine andere Idee, von der ich dachte, dass sie sehr portabel wäre, wäre, das Folgende ganz am Ende Ihrer ~/.bashrcDatei einzufügen.

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

Wenn Sie dann zuerst eine Shell mit einem Befehl starten möchten, tun Sie einfach Folgendes:

START_COMMAND='some_program with its arguments' bash

Erläuterung:

Das meiste davon sollte offensichtlich sein, aber der Grund für das Ändern des Variablennamens ist, dass wir die Variable lokalisieren können. Da $START_COMMANDes sich um eine exportierte Variable handelt, wird sie von allen untergeordneten Elementen der Shell geerbt. Wenn eine andere Bash-Shell eines dieser untergeordneten Elemente ist, wird der Befehl erneut ausgeführt. Also weisen wir den Wert einer neuen nicht exportierten Variablen zu ( $start_command) und löschen die alte.

Patrick
quelle
Explodieren, wenn es mehr als eine gibt was? Ein Befehl? nein sollte noch funktionieren Sie haben jedoch Recht mit der Portabilität. Es gibt 2 Faktoren, das <<<ist nicht POSIX, aber Sie könnten echo "string here" | bash -istattdessen. Dann gibt es /dev/ttywas, was eine Linux-Sache ist. Aber Sie könnten die FD täuschen, bevor Sie bash starten, und dann STDIN erneut öffnen, was ähnlich aussieht wie das, was Sie tun, aber ich habe mich dafür entschieden, die Dinge einfach zu halten.
Patrick
ok ok, kämpfe einfach nicht. Das funktioniert nur für mich und tut alles, was ich brauche. POSIX und Portabilität sind mir egal, ich brauche es auf meiner Box und nur dort. Ich werde auch die Antwort von mikeserv überprüfen, kann dies aber jetzt nicht tun.
Pawel7318
1
Nicht kämpfen :-). Ich respektiere Mikservs Antwort. Er ging für die Vereinbarkeit, ich ging für die Einfachheit. Beides ist voll gültig. Manchmal werde ich Kompatibilität anstreben, wenn es nicht zu komplex ist. In diesem Fall hätte ich nicht gedacht, dass es sich lohnt.
Patrick
2
@Patrick, /dev/ttyist keine Linux-Sache, aber definitiv POSIX.
Juli
2
Hey, Whaddayaknow .
Patrick
8

Dies sollte den Trick machen:

bash -c "some_program with its arguments;bash"

Bearbeiten:

Hier ist ein neuer Versuch nach Ihrem Update:

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
"
  • Ich muss von Zeit zu Zeit some_program beenden

Verwenden ControlCSie, Sie werden dieses kleine Menü präsentiert:

1) bash
2) restart
3) exit
  • Ich möchte es nicht in den Hintergrund stellen

Das ist der Fall.

  • Ich möchte dann auf der Bash bleiben, um etwas anderes zu tun

Wählen Sie die Option "Bash"

  • Ich möchte das Programm erneut ausführen können

Wählen Sie die Option "Neustart"

jlliagre
quelle
nicht schlecht, nicht schlecht ... aber es wäre auch gut, zu diesem letzten Befehl zurückkehren zu können. Irgendwelche Ideen dafür? Bitte überprüfen Sie meine andere Frage hier, um zu sehen, wozu ich sie benötige. Vielleicht schlagen Sie einen anderen Ansatz vor
pawel7318
Dies führt eine Subshell aus. Ich denke, der Fragesteller versucht, die Skriptumgebung zu erhalten.
mikeserv
danke jlliagre - netter versuch aber nicht viel nützliches für mich. Normalerweise drücke ich Strg + C und erwarte, dass es genau das macht, was es tun soll. Zusätzliches Menü ist einfach zu viel.
Pawel7318
@ pawel7318 Das Verhalten von CTRL+Cist nur ein Nebeneffekt der Standardkonfiguration Ihres Terminals, um es als zu interpretieren SIGINT. Sie können dies wie gewünscht ändern stty.
mikeserv
@pawel7318 weiter zu klären, SIGINTist trappedoben mit 2.
mikeserv
3

Sie können dies tun, indem Sie Ihr Skript als Initialisierungsdatei übergeben:

bash --init-file foo.script

Oder Sie geben es in der Kommandozeile weiter:

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

Beachten Sie, dass --init-filedies zum Lesen systemweiter Initialisierungsdateien gedacht war, /etc/bash.bashrcsodass Sie diese möglicherweise sourcein Ihrem Skript verwenden möchten .

jeroent
quelle
0

Ich verstehe den Sinn nicht wirklich, da Sie nach dem Ausführen dieses Programms bereits zu einer Shell zurückkehren. Sie könnten jedoch dazu:

bash -c "some_program with its arguments; bash"

Dadurch wird eine interaktive Bash gestartet, nachdem das Programm ausgeführt wurde.

mtak
quelle
Das startet eine Subshell.
mikeserv
0

Sie könnten den Befehl in den Hintergrund stellen, um Ihre aktuelle Bash offen zu halten:

some_program with its arguments &

Um zur Ausführung zurückzukehren, können Sie den fgBefehl verwenden und ^+zihn wieder in den Hintergrund stellen

Kiwy
quelle
dieses Mal nicht. Sie nehmen an, ich möchte diese Bash ausführen ... von Bash, was nicht der Fall ist.
Pawel7318
@pawel7318 wenn du etwas mehr erklärst könnten wir dir vielleicht auch eine bessere antwort geben?
Kiwy
Bitte überprüfen Sie meine andere Frage hier .
Pawel7318
@ pawel7318 Wenn Ihre Frage verwandt ist, fügen Sie bitte den Link zu der anderen Frage in Ihrer eigenen Frage hinzu.
Kiwy
1
@ pawel7318 bashoder nicht, du verwendest für mich eine sehr fremde Shell, wenn sie nicht mit Prozesshintergründen umgehen kann. Und ich stimme Kiwi zu - diese Informationen würden Ihnen besser dienen, wenn es in Ihrer Frage wäre.
mikeserv
0

Möglicherweise möchten Sie den Bildschirm verwenden, um den Befehl auszuführen. Sie können dann nach Abschluss des Befehls erneut eine Verbindung zur Sitzung herstellen.

Alternativ können Sie den Befehl auch im Hintergrund ausführen some_program with its arguments&. Dadurch haben Sie die Möglichkeit, den Befehl erneut auszuführen und den Status des Befehls abzurufen, sobald er ausgeführt wurde.

BillThor
quelle
Ich führe es genau aus, screenaber es in den Hintergrund zu stellen ist für mich nicht nützlich - ich muss manchmal das Programm beenden, etwas anderes tun und es erneut ausführen. Und das Hauptziel ist es, dies schnell zu tun.
Pawel7318
@ pawel7318 Sie können das Hintergrundprogramm mit dem Befehl kill %oder beenden kill %1.
BillThor