Ejecute un subshell bash interactivo con comandos iniciales sin volver al shell ("super") inmediatamente


87

Quiero ejecutar una subshell bash, (1) ejecutar algunos comandos, (2) y luego permanecer en esa subshell para hacer lo que me plazca. Puedo hacer cada uno de estos individualmente:

  1. Ejecute el comando usando la -cbandera:

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

    sin embargo, inmediatamente vuelve al shell "super" después de que se ejecutan los comandos. También puedo ejecutar una subshell interactiva:

  2. Iniciar nuevo bashproceso:

    $> bash

    y no saldrá de la subshell hasta que lo diga explícitamente ... pero no puedo ejecutar ningún comando inicial. La solución más cercana que he encontrado es:

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

    que funciona, pero no de la manera que quería, ya que ejecuta los comandos dados en un subshell y luego abre uno separado para la interacción.

Quiero hacer esto en una sola línea. Una vez que salga del subshell, debería volver al shell "super" normal sin incidentes. Debe haber una manera ~~

NB: lo que no estoy preguntando ...

  1. sin preguntar dónde conseguir la página de manual de bash
  2. sin preguntar cómo leer los comandos de inicialización de un archivo ... Sé cómo hacer esto, no es la solución que estoy buscando
  3. no estoy interesado en usar la pantalla tmux o gnu
  4. No estoy interesado en dar contexto a esto. Es decir, la pregunta pretende ser general, y no para ningún propósito específico.
  5. si es posible, quiero evitar el uso de soluciones alternativas que logren lo que quiero, pero de una manera "sucia". Solo quiero hacer esto en una sola línea. En particular, no quiero hacer algo comoxterm -e 'ls'

Puedo imaginar una solución de Expect, pero no es la línea que quieres. ¿De qué manera la exec bashsolución no es adecuada para usted?
Glenn Jackman

@glennjackman lo siento, no estoy familiarizado con la jerga. ¿Qué es una "solución esperada"? Además, la exec bashsolución involucra dos subcapas separadas. Quiero una subshell continua.
SABBATINI Luca

3
Lo bueno de esto execes que reemplaza el primer subshell con el segundo, por lo que solo te queda 1 shell debajo del padre. Si sus comandos de inicialización establecen variables de entorno, existirán en el shell ejecutado.
Glenn Jackman


2
Y el problema execes que pierde todo lo que no se transmite a subcapas a través del entorno, como variables no exportadas, funciones, alias, ...
Curt J. Sampson

Respuestas:


78

Esto se puede hacer fácilmente con tuberías temporales con nombre :

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

El crédito por esta respuesta va al comentario de Lie Ryan . Encontré esto realmente útil, y es menos notable en los comentarios, así que pensé que debería ser su propia respuesta.


99
Sin $HOME/.bashrcembargo, esto probablemente significa que no se ejecuta. Tendría que incluirse desde la tubería con nombre temporal.
Hubro

99
Para aclarar, algo como esto:bash --init-file <(echo ". \"$HOME/.bashrc\"; ls; pwd")
Hubro

55
Esto es muy asqueroso pero funciona. No puedo creer que bash no lo soporte directamente.
Pat Niemeyer

2
@Gus, el .es un sinónimo del sourcecomando: ss64.com/bash/source.html .
Jonathan Potter el

1
¿Hay alguna manera de hacerlo funcionar con el cambio de usuario, como sudo bash --init-file <(echo "ls; pwd")o sudo -iu username bash --init-file <(echo "ls; pwd")?
jeremysprofile

10

Puede hacerlo de forma indirecta con un archivo temporal, aunque tomará dos líneas:

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

Para un efecto agradable, puede hacer que el archivo temporal se elimine incluyéndolo rm $BASH_SOURCE.
Eduardo Ivanec

Eduardo, gracias. Esa es una buena solución, pero ... ¿estás diciendo que esto no se puede hacer sin tener que jugar con las E / S de archivo. Hay razones obvias por las que preferiría mantener esto como un comando autónomo, porque en el momento en que los archivos entren en la mezcla, tendré que comenzar a preocuparme sobre cómo hacer archivos temporales aleatorios y luego, como mencionaste, eliminarlos. Solo requiere mucho más esfuerzo de esta manera si quiero ser riguroso. De ahí el deseo de una solución más minimalista y elegante.
SABBATINI Luca

1
@SABBATINILuca: No estoy diciendo nada de eso. Esto es solo una forma y mktempresuelve el problema del archivo temporal como señaló @cjc. Bash podría soportar leer los comandos init desde stdin, pero por lo que puedo decir, no lo hace. Especificar -como archivo init y canalizarlos a medias funciona, pero Bash luego sale (probablemente porque detectó la tubería). La solución elegante, en mi humilde opinión, es utilizar exec.
Eduardo Ivanec

1
¿No también esto anula tu inicialización de bash normal? @SABBATINILuca ¿qué estás tratando de lograr con esto? ¿Por qué necesitas generar un shell para ejecutar automáticamente algunos comandos y luego mantener ese shell abierto?
user9517

11
Esta es una pregunta antigua, pero Bash puede crear canalizaciones con nombre temporales utilizando la siguiente sintaxis: bash --init-file <(echo "ls; pwd").
Lie Ryan

5

Intenta esto en su lugar:

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

$SHELL Hace que el shell se abra en modo interactivo, esperando un cierre con exit.


99
Para su información, esto abre un nuevo shell después, por lo que si cualquiera de los comandos afectan el estado del shell actual (por ejemplo, un archivo de aprovisionamiento) podría no funcionar como se esperaba
ThiefMaster

3

La "solución esperada" a la que me refería es programar un shell bash con el lenguaje de programación 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"

Lo ejecutarías como: ./subshell.exp "ls; pwd"


Supongo que esto también tendría la ventaja de registrar los comandos en el historial, ¿me equivoco? ¿También tengo curiosidad si bashrc / profile se ejecuta en este caso?
muhuk

Confirmó que esto le permite poner comandos en el historial, lo que no hacen las otras soluciones. Esto es ideal para iniciar un proceso en un archivo .screenrc; si sale del proceso iniciado, no cierra la ventana de la pantalla.
Dan Sandberg

1

¿Por qué no usar subshell nativos?

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

El encerrar comandos con paréntesis hace que bash spawn sea un subproceso para ejecutar estos comandos, por lo que puede, por ejemplo, alterar el entorno sin afectar el shell principal. Esto es básicamente más legible equivalente a bash -c "ls; pwd; exec $BASH".

Si eso todavía se ve detallado, hay dos opciones. Una es tener este fragmento como una función:

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

Otro es hacer exec $BASHmás corto:

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

Personalmente, me gusta Racercarme más, ya que no hay necesidad de jugar con cuerdas de escape.


1
No estoy seguro de si hay cavernas usando Exec para el escenario que el OP tiene en mente, pero para mí esta es la mejor solución propuesta, porque usa comandos bash simples sin ningún problema de escape de cadena.
Peter

1

Si sudo -E bashno funciona, utilizo lo siguiente, que hasta ahora ha cumplido mis expectativas:

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

Configuré HOME = $ HOME porque quiero que mi nueva sesión tenga HOME configurado en HOME de mi usuario, en lugar de HOME de root, lo que ocurre de manera predeterminada en algunos sistemas.


0

menos elegante que --init-file, pero quizás más instrumentable:

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

while read -a commands
do
    eval ${commands[@]}
done
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.