Führen Sie eine interaktive Bash-Subshell mit anfänglichen Befehlen aus, ohne sofort zur (Super-) Shell zurückzukehren


86

Ich möchte eine Bash-Subshell ausführen, (1) einige Befehle ausführen, (2) und dann in dieser Subshell bleiben, um zu tun, was ich möchte. Ich kann jedes von diesen einzeln tun:

  1. Befehl mit -cFlag ausführen:

    $> bash -c "ls; pwd; <other commands...>"

    Es kehrt jedoch sofort zur "super" Shell zurück, nachdem die Befehle ausgeführt wurden. Ich kann auch einfach eine interaktive Subshell ausführen:

  2. Neuen bashProzess starten :

    $> bash

    und es wird die Subshell nicht verlassen, bis ich es explizit sage ... aber ich kann keine anfänglichen Befehle ausführen. Die nächste Lösung, die ich gefunden habe, ist:

    $> bash -c "ls; pwd; <other commands>; exec bash"

    Das funktioniert, aber nicht so, wie ich es wollte, da es die angegebenen Befehle in einer Subshell ausführt und dann eine separate für die Interaktion öffnet.

Ich möchte dies in einer einzigen Zeile tun. Sobald ich die Subshell verlasse, sollte ich ohne Zwischenfälle zur regulären "Super" -Shell zurückkehren. Es muss einen Weg geben ~~

NB: Was ich nicht frage ...

  1. Ich fragte nicht, wo ich die Bash-Manpage finden sollte
  2. Ich frage nicht, wie man Initialisierungsbefehle aus einer Datei liest ... Ich weiß, wie man das macht, es ist nicht die Lösung, nach der ich suche
  3. nicht daran interessiert, tmux oder gnu screen zu benutzen
  4. nicht daran interessiert, Kontext zu geben. Das heißt, die Frage soll allgemein und nicht für einen bestimmten Zweck sein
  5. Wenn möglich, möchte ich vermeiden, Problemumgehungen zu verwenden, die das erreichen, was ich will, aber auf "schmutzige" Weise. Ich möchte dies nur in einer einzigen Zeile tun. Insbesondere möchte ich so etwas nicht tunxterm -e 'ls'

Ich kann mir eine Expect-Lösung vorstellen, aber es ist kaum der Einzeiler, den Sie wollen. Inwiefern ist die exec bashLösung für Sie ungeeignet?
Glenn Jackman

@glennjackman Entschuldigung, ich kenne den Jargon nicht. Was ist eine "Expect-Lösung"? Die exec bashLösung umfasst auch zwei separate Unterschalen. Ich möchte eine durchgehende Unterschale.
SABBATINI Luca

3
Die Schönheit execist , dass es ersetzt die erste Sub - Shell mit dem zweiten, so dass Sie nur links 1 Schale unter dem Elternteil. Wenn Ihre Initialisierungsbefehle Umgebungsvariablen festlegen, sind diese in der ausgeführten Shell vorhanden.
Glenn Jackman


2
Und das Problem dabei execist, dass Sie alles verlieren, was nicht über die Umgebung an Subshells weitergegeben wird, wie nicht exportierte Variablen, Funktionen, Aliase, ...
Curt J. Sampson,

Antworten:


77

Dies kann einfach mit temporären Named Pipes durchgeführt werden :

bash --init-file <(echo "ls; pwd")

Dank für diese Antwort geht an den Kommentar von Lie Ryan . Ich fand das wirklich nützlich und es ist in den Kommentaren weniger auffällig, daher dachte ich, es sollte seine eigene Antwort sein.


9
Dies bedeutet vermutlich, dass dies $HOME/.bashrcjedoch nicht ausgeführt wird. Es müsste aus der temporären Named Pipe aufgenommen werden.
Hubro

9
Zur Verdeutlichung:bash --init-file <(echo ". \"$HOME/.bashrc\"; ls; pwd")
Hubro

5
Das ist so ekelhaft, aber es funktioniert. Ich kann nicht glauben, dass Bash dies nicht direkt unterstützt.
Pat Niemeyer

2
@ Gus, das .ist ein Synonym für den sourceBefehl: ss64.com/bash/source.html .
Jonathan Potter

1
Gibt es eine Möglichkeit, es mit dem Benutzerwechsel zu machen, wie zum Beispiel sudo bash --init-file <(echo "ls; pwd")oder sudo -iu username bash --init-file <(echo "ls; pwd")?
Jeremy Profile

10

Sie können dies auf Umwegen mit einer temporären Datei tun, obwohl es zwei Zeilen dauern wird:

echo "ls; pwd" > initfile
bash --init-file initfile

Für einen schönen Effekt können Sie die temporäre Datei dazu bringen, sich selbst zu entfernen, indem Sie sie einschließen rm $BASH_SOURCE.
Eduardo Ivanec

Eduardo, danke. Das ist eine schöne Lösung, aber ... sagen Sie, dass dies nicht möglich ist, ohne mit Datei-E / A zu experimentieren? Es gibt offensichtliche Gründe, warum ich es vorziehen würde, diesen Befehl als eigenständigen Befehl beizubehalten, da ich mich in dem Moment, in dem Dateien in den Mix aufgenommen werden, Gedanken darüber machen muss, wie zufällige temporäre Dateien erstellt und diese dann, wie Sie erwähnt haben, gelöscht werden. Auf diese Weise ist einfach so viel mehr Aufwand erforderlich, wenn ich streng sein möchte. Daher der Wunsch nach einer minimalistischeren, eleganteren Lösung.
SABBATINI Luca

1
@ Sabbatiniluca: Ich sage so etwas nicht. Dies ist nur eine Möglichkeit und mktemplöst das Problem mit temporären Dateien, wie @cjc hervorhob. Bash könnte das Lesen der Init-Befehle von stdin unterstützen, aber soweit ich das beurteilen kann, nicht. Das Angeben -als Init-Datei und das Weiterleiten zur Hälfte funktionieren, aber Bash wird dann beendet (wahrscheinlich, weil es die Pipeline erkannt hat). Die elegante Lösung, IMHO, ist die Verwendung von exec.
Eduardo Ivanec

1
Überschreibt dies nicht auch Ihre normale Bash-Initialisierung? @SABBATINILuca Was willst du damit erreichen? Warum musst du eine Shell starten, die einige Befehle automatisch ausführt und diese Shell dann offen hält?
user9517

11
Dies ist eine alte Frage, aber Bash kann temporäre Named Pipes mithilfe der folgenden Syntax erstellen: bash --init-file <(echo "ls; pwd").
Lie Ryan

5

Versuchen Sie dies stattdessen:

$> bash -c "ls;pwd;other commands;$SHELL"

$SHELL Es öffnet die Shell im interaktiven Modus und wartet auf ein Ende mit exit.


9
Zu Ihrer Information, dies öffnet anschließend eine neue Shell. Wenn sich also einer der Befehle auf den aktuellen Status der Shell auswirkt (z. B. das Beschaffen einer Datei), funktioniert dies möglicherweise nicht wie erwartet
ThiefMaster,

3

Die "Expect-Lösung", auf die ich mich bezog, programmiert eine Bash-Shell mit der Programmiersprache Expect :

#!/usr/bin/env expect
set init_commands [lindex $argv 0]
set bash_prompt {\$ $}              ;# adjust to suit your own prompt
spawn bash
expect -re $bash_prompt {send -- "$init_commands\r"}
interact
puts "exiting subshell"

Du würdest so laufen: ./subshell.exp "ls; pwd"


Ich denke, dies hätte den Vorteil, dass die Befehle auch in der Historie registriert werden. Irre ich mich? Ich bin auch neugierig, ob bashrc / profile in diesem Fall ausgeführt wird?
Muhuk

Bestätigt, dass Sie auf diese Weise Befehle in den Verlauf einfügen können, was die anderen Lösungen nicht tun. Dies ist ideal, um einen Prozess in einer .screenrc-Datei zu starten. Wenn Sie den gestarteten Prozess beenden, wird das Bildschirmfenster nicht geschlossen.
Dan Sandberg

1

Warum nicht native Subshells verwenden?

$ ( ls; pwd; exec $BASH; )
bar     foo     howdy
/tmp/hello/
bash-4.4$ 
bash-4.4$ exit
$

Durch das Einschließen von Befehlen in Klammern wird Bash-Spawn zu einem Unterprozess, in dem diese Befehle ausgeführt werden. So können Sie beispielsweise die Umgebung ändern, ohne die übergeordnete Shell zu beeinflussen. Dies ist im Prinzip besser lesbar als das bash -c "ls; pwd; exec $BASH".

Wenn das immer noch ausführlich aussieht, gibt es zwei Möglichkeiten. Einer ist, dieses Snippet als Funktion zu haben:

$ run() { ( eval "$@"; exec $BASH; ) }
$ run 'ls; pwd;'
bar     foo     howdy
/tmp/hello/
bash-4.4$ exit
$ run 'ls;' 'pwd;'
bar     foo     howdy
/tmp/hello/
bash-4.4$ exit
$

Eine andere ist, exec $BASHkürzer zu machen :

$ R() { exec $BASH; }
$ ( ls; pwd; R )
bar     foo     howdy
/tmp/hello/
bash-4.4$ exit
$

Ich persönlich mag die RHerangehensweise mehr, da es nicht nötig ist, mit Streichern zu spielen.


1
Ich bin nicht sicher, ob es Caveeats gibt, die exec für das Szenario verwenden, das das OP im Auge hat, aber für mich ist dies die weitaus beste vorgeschlagene Lösung, da es einfache Bash-Befehle verwendet, ohne dass Probleme mit dem Entkommen von Zeichenfolgen auftreten.
Peter

1

Wenn sudo -E bashdas nicht funktioniert, verwende ich das Folgende, was meine Erwartungen bisher erfüllt hat:

sudo HOME=$HOME bash --rcfile $HOME/.bashrc

Ich habe HOME = $ HOME festgelegt, weil ich möchte, dass in meiner neuen Sitzung HOME auf HOME des Benutzers und nicht auf HOME des Roots festgelegt wird, was bei einigen Systemen standardmäßig der Fall ist.


0

weniger elegant als --init-file, aber vielleicht besser instrumentierbar:

fn(){
    echo 'hello from exported function'
}

while read -a commands
do
    eval ${commands[@]}
done
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.