Exécuter un sous-shell bash interactif avec les commandes initiales sans revenir immédiatement au («super») shell

87

Je veux exécuter un sous-shell bash, (1) exécuter quelques commandes, (2), puis rester dans ce sous-shell pour faire ce que je veux. Je peux faire chacune de ces choses individuellement:

  1. Exécutez la commande en utilisant -cflag:

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

    Cependant, il retourne immédiatement au "super" shell après l'exécution des commandes. Je peux aussi simplement lancer un sous-shell interactif:

  2. Démarrer un nouveau bashprocessus:

    $> bash

    et il ne quittera pas le sous-shell tant que je ne le dirai pas explicitement ... mais je ne pourrai exécuter aucune commande initiale. La solution la plus proche que j'ai trouvée est:

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

    qui fonctionne, mais pas comme je le voulais, car il exécute les commandes données dans un sous-shell, puis en ouvre un autre pour l’interaction.

Je veux le faire sur une seule ligne. Une fois que je quitte le sous-shell, je devrais revenir au shell "super" habituel sans incident. Il doit y avoir un moyen ~~

NB: Ce que je ne demande pas ...

  1. ne pas demander où trouver la page de manuel bash
  2. ne demande pas comment lire les commandes d'initialisation à partir d'un fichier ... Je sais comment faire cela, ce n'est pas la solution que je cherche
  3. pas intéressé à utiliser tmux ou gnu screen
  4. pas intéressé à donner un contexte à cela. C'est-à-dire que la question est censée être générale et non à des fins spécifiques
  5. Si possible, je veux éviter d'utiliser des solutions de contournement qui accomplissent ce que je veux, mais d'une manière "sale". Je veux juste faire ceci sur une seule ligne. En particulier, je ne veux pas faire quelque chose commexterm -e 'ls'
SABBATINI Luca
la source
Je peux imaginer une solution Expect, mais ce n’est pas vraiment la solution idéale. En quoi la exec bashsolution ne vous convient-elle pas?
Glenn Jackman
@glennjackman désolé, je ne connais pas le jargon. Qu'est-ce qu'une "solution attend"? En outre, la exec bashsolution implique deux sous-réservoirs distincts. Je veux un sous-shell continu.
SABBATINI Luca
3
La beauté de execest qu'il remplace le premier sous-shell par le second, de sorte qu'il ne vous reste qu'un shell en dessous du parent. Si vos commandes d'initialisation définissent des variables d'environnement, elles existeront dans le shell exécuté.
Glenn Jackman
2
possible même stackoverflow.com/questions/7120426/...
Ciro Santilli新疆改造中心法轮功六四事件
2
Et le problème, execc'est que vous perdez tout ce qui n'est pas transmis aux sous-shell via l'environnement, tels que les variables non exportées, les fonctions, les alias, ...
Curt J. Sampson

Réponses:

78

Cela peut être facilement fait avec des pipes nommées temporaires :

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

Le mérite de cette réponse revient au commentaire de Lie Ryan . J'ai trouvé cela vraiment utile, et c'est moins visible dans les commentaires, alors j'ai pensé que cela devrait être sa propre réponse.

Jonathan Potter
la source
9
Cela signifie probablement que cela $HOME/.bashrcn'est pas exécuté si. Il devrait être inclus à partir du canal nommé temporaire.
Hubro
9
Pour clarifier, quelque chose comme ceci:bash --init-file <(echo ". \"$HOME/.bashrc\"; ls; pwd")
Hubro
5
C'est tellement dégoûtant mais ça marche. Je ne peux pas croire que bash ne supporte pas cela directement.
Pat Niemeyer le
2
@Gus, le .est un synonyme de la sourcecommande: ss64.com/bash/source.html .
Jonathan Potter
1
Y a-t-il un moyen de le faire fonctionner avec le changement d'utilisateur, tel que sudo bash --init-file <(echo "ls; pwd")ou sudo -iu username bash --init-file <(echo "ls; pwd")?
jeremysprofile
10

Vous pouvez le faire de manière détournée avec un fichier temporaire, bien que cela prenne deux lignes:

echo "ls; pwd" > initfile
bash --init-file initfile
Eduardo Ivanec
la source
Pour un effet intéressant, vous pouvez faire en sorte que le fichier temporaire se supprime en l'incluant rm $BASH_SOURCE.
Eduardo Ivanec
Eduardo, merci. C'est une bonne solution, mais ... êtes-vous en train de dire que cela ne peut être fait sans avoir à tripoter le fichier I / O. Il y a des raisons évidentes pour lesquelles je préférerais conserver cette commande en tant que commande autonome, car dès que des fichiers entrent dans le mix, je dois commencer à m'inquiéter de la manière de créer des fichiers temporaires aléatoires, puis, comme vous l'avez mentionné, de les supprimer. Cela demande tellement plus d'efforts de cette façon si je veux être rigoureux. D'où le désir d'une solution plus minimaliste et élégante.
SABBATINI Luca
1
@SABBATINILuca: Je ne dis rien comme ça. Ceci n’est qu’un moyen, et mktemprésout le problème des fichiers temporaires, comme @cjc l’a souligné. Bash pourrait prendre en charge la lecture des commandes init à partir de stdin, mais pour autant que je sache, ce n'est pas le cas. Spécifier -comme un fichier init et les canaliser à moitié fonctionne, mais Bash se ferme alors (probablement parce qu'il a détecté le pipeline). La solution élégante, à mon humble avis, consiste à utiliser exec.
Eduardo Ivanec
1
Cela ne remplace-t-il pas également votre initialisation bash normale? @SABBATINILuca Qu'essayez-vous d'obtenir avec cela, pourquoi avez-vous besoin de générer un shell pour qu'il exécute automatiquement certaines commandes, puis que ce shell reste ouvert?
user9517
11
Cette question est ancienne, mais Bash peut créer des canaux nommés temporaires en utilisant la syntaxe suivante: bash --init-file <(echo "ls; pwd").
Lie Ryan
5

Essayez ceci à la place:

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

$SHELL Cela rend le shell ouvert en mode interactif, en attente d'une fermeture avec exit.

ExpoBi
la source
9
Pour votre information, cela ouvre un nouveau shell par la suite. Ainsi, si l'une des commandes affecte l'état du shell actuel (par exemple, la recherche d'un fichier), elle risque de ne pas fonctionner
correctement.
3

La "solution Expect" à laquelle je faisais référence est la programmation d'un shell bash avec le langage de programmation 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"

Vous courriez ça comme: ./subshell.exp "ls; pwd"

Glenn Jackman
la source
Je suppose que cela aurait aussi l'avantage d'enregistrer les commandes dans l'historique, est-ce que je me trompe? Aussi je suis curieux de savoir si bashrc / profile est exécuté dans ce cas?
Muhuk
Confirmé que cela vous permet de placer des commandes dans l'historique, contrairement aux autres solutions. Ceci est idéal pour démarrer un processus dans un fichier .screenrc - si vous quittez le processus démarré, vous ne fermez pas la fenêtre d'écran.
Dan Sandberg
1

Pourquoi ne pas utiliser les sous-shell natifs?

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

Lorsque les commandes sont entourées de parenthèses, bash génère un sous-processus pour exécuter ces commandes. Vous pouvez ainsi modifier l'environnement sans affecter le shell parent. Ceci est fondamentalement plus lisible équivalent à la bash -c "ls; pwd; exec $BASH".

Si cela semble toujours verbeux, il y a deux options. L'une consiste à avoir cet extrait de code en tant que fonction:

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

Une autre est de faire exec $BASHplus court:

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

Personnellement, j'aime Rmieux aborder, car il n'est pas nécessaire de jouer avec des ficelles qui s'échappent.

Toriningen
la source
1
Je ne sais pas s'il existe des dérogations utilisant exec pour le scénario envisagé par le PO, mais pour moi, c'est la meilleure solution proposée, car elle utilise des commandes plain bash sans aucun problème d'échappement de chaîne.
Peter
1

Si sudo -E bashcela ne fonctionne pas, j'utilise ce qui suit, ce qui a répondu à mes attentes jusqu'à présent:

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

J'ai défini HOME = $ HOME parce que je souhaite que ma nouvelle session ait HOME défini sur HOME de l'utilisateur, plutôt que sur HOME, ce qui se produit par défaut sur certains systèmes.

Jago
la source
0

moins élégant que --init-file, mais peut-être plus instrumentable:

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

while read -a commands
do
    eval ${commands[@]}
done
RubyTuesdayDONO
la source