Wie kann man Bash aufrufen, Befehle in der neuen Shell ausführen und dann dem Benutzer die Kontrolle zurückgeben?

85

Dies muss entweder sehr einfach oder sehr komplex sein, aber ich konnte nichts darüber finden ... Ich versuche, eine neue Bash-Instanz zu öffnen, dann ein paar Befehle darin auszuführen und die Steuerung dem Benutzer darin zurückzugeben gleiche Instanz .

Ich habe es versucht:

$ bash -lic "some_command"

Dies wird jedoch some_commandin der neuen Instanz ausgeführt und dann geschlossen. Ich möchte, dass es offen bleibt.

Ein weiteres Detail, das sich auf die Antworten auswirken könnte: Wenn ich dies zum Laufen bringen kann, verwende ich es in meinem .bashrcAlias ​​( meinen Alias), also Bonuspunkte für eine aliasImplementierung!

Félix Saparelli
quelle
Ich verstehe den letzten Teil über Aliase nicht. Aliase werden im Kontext der aktuellen Shell ausgeführt, sodass sie nicht das Problem haben, das Sie lösen möchten (obwohl Funktionen aus einer Reihe von Gründen normalerweise besser sind als Aliase).
Tripleee
2
Ich möchte in der Lage sein, so etwas wie alias shortcut='bash -lic "some_command"'in meine .bashrc zu setzen, dann auszuführen shortcutund eine neue Shell zu haben, in der some_commandbereits ausgeführt wurde.
Félix Saparelli

Antworten:

61
bash --rcfile <(echo '. ~/.bashrc; some_command')

verzichtet auf die Erstellung temporärer Dateien. Frage auf anderen Seiten:

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
Ich habe versucht, dies in Windows 10 zu tun (versucht, eine Batch-Startdatei / ein Startskript zu schreiben), und ich bekommeThe system cannot find the file specified.
Doctor Blue
1
@ Scott debuggen es Schritt für Schritt: 1) Funktioniert cat ~/.bashrc? 2) Funktioniert cat <(echo a)? 3) Funktioniert bash --rcfile somefile?
Ciro Santilli 8 冠状 病 六四 事件 法轮功
1
@Scott Wir sind gerade auf dieses Problem in der WSL gestoßen (Starten von Bash aus einer Start-Batch-Datei). Unsere Lösung bestand darin, einige der Zeichen zu maskieren, damit Batch sie verstehen konnte: bash.exe --rcfile ^<^(echo 'source $HOME/.bashrc; ls; pwd'^)sollte über die Windows-Eingabeaufforderung ausgeführt werden.
user2561747
Gibt es eine Möglichkeit, es mit Benutzerwechsel wie sudo bash --init-file <(echo "ls; pwd")oder zu machen sudo -iu username bash --init-file <(echo "ls; pwd")?
Jeremysprofile
39

Dies ist eine späte Antwort, aber ich hatte genau das gleiche Problem und Google hat mich auf diese Seite geschickt. Der Vollständigkeit halber habe ich hier das Problem umgangen.

Soweit ich das beurteilen kann, bashgibt es keine Möglichkeit, das zu tun, was das Originalplakat wollte. Die -cOption wird immer zurückgegeben, nachdem die Befehle ausgeführt wurden.

Defekte Lösung : Der einfachste und offensichtlichste Versuch, dies zu umgehen, ist:

bash -c 'XXXX ; bash'

Dies funktioniert teilweise (wenn auch mit einer zusätzlichen Unterschalenschicht). Das Problem ist jedoch, dass während eine Sub-Shell die exportierten Umgebungsvariablen erbt, Aliase und Funktionen nicht geerbt werden. Dies könnte für einige Dinge funktionieren, ist jedoch keine allgemeine Lösung.

Besser : Um dies zu umgehen, erstellen Sie dynamisch eine Startdatei und rufen bash mit dieser neuen Initialisierungsdatei auf. Stellen Sie dabei sicher, dass Ihre neue Init-Datei ~/.bashrcbei Bedarf Ihre reguläre Datei aufruft .

# Create a temporary file
TMPFILE=$(mktemp)

# Add stuff to the temporary file
echo "source ~/.bashrc" > $TMPFILE
echo "<other commands>" >> $TMPFILE
echo "rm -f $TMPFILE" >> $TMPFILE

# Start the new bash shell 
bash --rcfile $TMPFILE

Das Schöne ist, dass sich die temporäre Init-Datei selbst löscht, sobald sie verwendet wird, wodurch das Risiko verringert wird, dass sie nicht korrekt bereinigt wird.

Hinweis: Ich bin nicht sicher, ob / etc / bashrc normalerweise als Teil einer normalen Nicht-Login-Shell aufgerufen wird. Wenn ja, möchten Sie vielleicht / etc / bashrc sowie Ihre ~/.bashrc.

daveraja
quelle
Das rm -f $TMPFILEDing macht es. Ich erinnere mich nicht wirklich an den Anwendungsfall, den ich dafür hatte, und ich verwende jetzt fortgeschrittenere Automatisierungstechniken, aber das ist der richtige Weg!
Félix Saparelli
7
Keine tmp-Dateien mit Prozessersetzungbash --rcfile <(echo ". ~/.bashrc; a=b")
Ciro Santilli 法轮功 冠状 病 六四 六四 事件 9.
3
Die temporäre Datei wird nur bereinigt, wenn Ihr Bash-Skript erfolgreich ausgeführt wird. Eine robustere Lösung wäre die Verwendung trap.
Tripleee
5

Sie können --rcfilean Bash übergeben, um eine Datei Ihrer Wahl zu lesen. Diese Datei wird anstelle Ihrer gelesen .bashrc. (Wenn das ein Problem ist, beziehen Sie es ~/.bashrcaus dem anderen Skript.)

Bearbeiten: Eine Funktion zum Starten einer neuen Shell mit dem Material von ~/.more.shwürde also ungefähr so ​​aussehen:

more() { bash --rcfile ~/.more.sh ; }

... und .more.shSie hätten die Befehle, die Sie ausführen möchten, wenn die Shell startet. (Ich nehme an, es wäre elegant, eine separate Startdatei zu vermeiden. Sie können keine Standardeingabe verwenden, da die Shell dann nicht interaktiv ist. Sie können jedoch eine Startdatei aus einem Dokument hier an einem temporären Speicherort erstellen und dann lesen.)

Tripleee
quelle
3

Sie können die gewünschte Funktionalität erhalten, indem Sie das Skript beziehen, anstatt es auszuführen. z.B:

$ cat script
cmd1
cmd2
$. Skript
$ Zu diesem Zeitpunkt wurden cmd1 und cmd2 in dieser Shell ausgeführt
William Pursell
quelle
Hmm. Das funktioniert, aber gibt es keine Möglichkeit, alles in ein alias=''einzubetten?
Félix Saparelli
1
Verwenden Sie niemals Aliase. Verwenden Sie stattdessen Funktionen. Sie können setzen: 'foo () {cmd1; cmd2; } 'Wenn Sie in Ihren Startskripten foo ausführen und dann foo ausführen, werden die beiden Befehle in der aktuellen Shell ausgeführt. Beachten Sie, dass keine dieser Lösungen Ihnen eine neue Shell bietet, aber Sie können 'exec bash' und dann 'foo' ausführen
William Pursell
1
Sag niemals nie. Aliase haben ihren Platz
Glenn Jackman
Gibt es ein Beispiel für einen Anwendungsfall, bei dem eine Funktion nicht anstelle eines Alias ​​verwendet werden kann?
William Pursell
3

Fügen Sie ~/.bashrceinen Abschnitt wie diesen hinzu:

if [ "$subshell" = 'true' ]
then
    # commands to execute only on a subshell
    date
fi
alias sub='subshell=true bash'

Dann können Sie die Subshell mit starten sub.

dashohoxha
quelle
Punkte für Originalität. Der Alias ​​selbst ist wahrscheinlich besser als env subshell=true bash(da dies nicht $subshellzu nachfolgenden Befehlen führt): Verwenden von env im Vergleich zum Verwenden von Export .
Félix Saparelli
Du hast recht. Aber mir ist aufgefallen, dass es auch ohne funktioniert env. Um zu überprüfen, ob definiert ist oder nicht, verwende ich echo $subshell(anstelle dessen env | grep subshell, was Sie verwenden).
Dashohoxha
3

Die akzeptierte Antwort ist wirklich hilfreich! Nur um diese Prozesssubstitution (dh <(COMMAND)) hinzuzufügen, wird sie in einigen Shells (z dash. B. ) nicht unterstützt .

In meinem Fall habe ich versucht, eine benutzerdefinierte Aktion (im Grunde ein einzeiliges Shell-Skript) im Thunar-Dateimanager zu erstellen, um eine Shell zu starten und die ausgewählte virtuelle Python-Umgebung zu aktivieren. Mein erster Versuch war:

urxvt -e bash --rcfile <(echo ". $HOME/.bashrc; . %f/bin/activate;")

Wo %fist der Pfad zur virtuellen Umgebung, der von Thunar verwaltet wird? Ich habe einen Fehler erhalten (indem ich Thunar über die Befehlszeile ausgeführt habe):

/bin/sh: 1: Syntax error: "(" unexpected

Dann wurde mir klar, dass mein sh(im Wesentlichen dash) Prozessersatz nicht unterstützt.

Meine Lösung bestand darin, bashauf oberster Ebene aufzurufen , um die Prozessersetzung auf Kosten einer zusätzlichen Shell-Ebene zu interpretieren:

bash -c 'urxvt -e bash --rcfile <(echo "source $HOME/.bashrc; source %f/bin/activate;")'

Alternativ habe ich versucht, hier-Dokument für zu verwenden, dashaber ohne Erfolg. Etwas wie:

echo -e " <<EOF\n. $HOME/.bashrc; . %f/bin/activate;\nEOF\n" | xargs -0 urxvt -e bash --rcfile

PS: Ich habe nicht genug Ruf, um Kommentare zu posten. Moderatoren können diese gerne in Kommentare verschieben oder entfernen, wenn sie bei dieser Frage nicht hilfreich sind.

Haimo
quelle
Wenn man nicht will, dass Bash oben drauf ist (oder es nicht hat), gibt es verschiedene dokumentierte Möglichkeiten, die Prozessersetzung direkt zu implementieren, meistens mit Fifos.
Félix Saparelli
2

In Übereinstimmung mit der Antwort von daveraja ist hier ein Bash-Skript, das den Zweck lösen wird.

Stellen Sie sich eine Situation vor, wenn Sie C-Shell verwenden und einen Befehl ausführen möchten, ohne den Kontext / das Fenster der C-Shell wie folgt zu verlassen:

Auszuführender Befehl : Suchen Sie das genaue Wort 'Testen' im aktuellen Verzeichnis rekursiv nur in * .h, * .c-Dateien

grep -nrs --color -w --include="*.{h,c}" Testing ./

Lösung 1 : Geben Sie bash von der C-Shell aus ein und führen Sie den Befehl aus

bash
grep -nrs --color -w --include="*.{h,c}" Testing ./
exit

Lösung 2 : Schreiben Sie den beabsichtigten Befehl in eine Textdatei und führen Sie ihn mit bash aus

echo 'grep -nrs --color -w --include="*.{h,c}" Testing ./' > tmp_file.txt
bash tmp_file.txt

Lösung 3 : Führen Sie den Befehl mit bash in derselben Zeile aus

bash -c 'grep -nrs --color -w --include="*.{h,c}" Testing ./'

Lösung 4 : Erstellen Sie einen sciprt (einmalig) und verwenden Sie ihn für alle zukünftigen Befehle

alias ebash './execute_command_on_bash.sh'
ebash grep -nrs --color -w --include="*.{h,c}" Testing ./

Das Skript lautet wie folgt:

#!/bin/bash
# =========================================================================
# References:
# https://stackoverflow.com/a/13343457/5409274
# https://stackoverflow.com/a/26733366/5409274
# https://stackoverflow.com/a/2853811/5409274
# https://stackoverflow.com/a/2853811/5409274
# https://www.linuxquestions.org/questions/other-%2Anix-55/how-can-i-run-a-command-on-another-shell-without-changing-the-current-shell-794580/
# https://www.tldp.org/LDP/abs/html/internalvariables.html
# https://stackoverflow.com/a/4277753/5409274
# =========================================================================

# Enable following line to see the script commands
# getting printing along with their execution. This will help for debugging.
#set -o verbose

E_BADARGS=85

if [ ! -n "$1" ]
then
  echo "Usage: `basename $0` grep -nrs --color -w --include=\"*.{h,c}\" Testing ."
  echo "Usage: `basename $0` find . -name \"*.txt\""
  exit $E_BADARGS
fi  

# Create a temporary file
TMPFILE=$(mktemp)

# Add stuff to the temporary file
#echo "echo Hello World...." >> $TMPFILE

#initialize the variable that will contain the whole argument string
argList=""
#iterate on each argument
for arg in "$@"
do
  #if an argument contains a white space, enclose it in double quotes and append to the list
  #otherwise simply append the argument to the list
  if echo $arg | grep -q " "; then
   argList="$argList \"$arg\""
  else
   argList="$argList $arg"
  fi
done

#remove a possible trailing space at the beginning of the list
argList=$(echo $argList | sed 's/^ *//')

# Echoing the command to be executed to tmp file
echo "$argList" >> $TMPFILE

# Note: This should be your last command
# Important last command which deletes the tmp file
last_command="rm -f $TMPFILE"
echo "$last_command" >> $TMPFILE

#echo "---------------------------------------------"
#echo "TMPFILE is $TMPFILE as follows"
#cat $TMPFILE
#echo "---------------------------------------------"

check_for_last_line=$(tail -n 1 $TMPFILE | grep -o "$last_command")
#echo $check_for_last_line

#if tail -n 1 $TMPFILE | grep -o "$last_command"
if [ "$check_for_last_line" == "$last_command" ]
then
  #echo "Okay..."
  bash $TMPFILE
  exit 0
else
  echo "Something is wrong"
  echo "Last command in your tmp file should be removing itself"
  echo "Aborting the process"
  exit 1
fi
Rohan Ghige
quelle
2
Dies ist eine völlig andere Antwort auf ein völlig anderes Problem. (Es ist cool, dass Sie das herausgefunden haben, aber es gehört nicht zu diesem Thread. Wenn dies auf dieser Site nicht vorhanden war, sollten Sie eine neue Frage mit Ihrem zweiten Satz öffnen und diese dann sofort selbst mit dem Rest beantworten ... So sollte das gehandhabt werden ☺) Siehe auch Ciro Santillis Antwort für eine Möglichkeit, dies zu tun, ohne überhaupt eine temporäre Datei zu erstellen.
Félix Saparelli
Als keine Antwort markiert, da es kein Versuch ist, diese Frage zu beantworten (obwohl es ein wirklich großartiger Versuch ist, eine andere zu beantworten!)
Charles Duffy
0

Hier ist noch eine (funktionierende) Variante:

Dies öffnet ein neues Gnome-Terminal und führt dann im neuen Terminal Bash aus. Die RC-Datei des Benutzers wird zuerst gelesen, dann wird ein Befehl ls -lazur Ausführung an die neue Shell gesendet, bevor sie interaktiv wird. Das letzte Echo fügt eine zusätzliche neue Zeile hinzu, die zum Beenden der Ausführung erforderlich ist.

gnome-terminal -- bash -c 'bash --rcfile <( cat ~/.bashrc; echo ls -la ; echo)'

Manchmal finde ich es auch nützlich, das Terminal zu dekorieren, z. B. mit Farbe zur besseren Orientierung.

gnome-terminal --profile green -- bash -c 'bash --rcfile <( cat ~/.bashrc; echo ls -la ; echo)'
user1656671
quelle
-1

Ausführen von Befehlen in einer Hintergrundshell

Fügen Sie einfach &am Ende des Befehls hinzu, z.

bash -c some_command && another_command &
raphtlw
quelle
-1
$ bash --init-file <(echo 'some_command')
$ bash --rcfile <(echo 'some_command')

Falls Sie die Prozessersetzung nicht verwenden können oder möchten:

$ cat script
some_command
$ bash --init-file script

Ein anderer Weg:

$ bash -c 'some_command; exec bash'
$ sh -c 'some_command; exec sh'

sh-nur so ( dash, busybox):

$ ENV=script sh
x-yuri
quelle
Gute Alternativen, würde aber davon profitieren, wenn der obere Abschnitt (vor dem ENV=scriptTeil) entfernt oder umformuliert würde, um nicht mit einem offensichtlichen Kopieren und Einfügen der oberen Antwort verwechselt zu werden.
Félix Saparelli
@ FélixSaparelli Um ehrlich zu sein, sind alle Lösungen außer dem ENV+ shin den anderen Antworten vorhanden, aber meistens in Tonnen von Text vergraben oder von nicht benötigtem / nicht essentiellem Code umgeben. Ich glaube, eine kurze, klare Zusammenfassung wäre für alle von Vorteil.
x-yuri