¿Cómo invocar bash, ejecutar comandos dentro del nuevo shell y luego devolver el control al usuario?

86

Esto debe ser realmente simple o realmente complejo, pero no pude encontrar nada al respecto ... Estoy tratando de abrir una nueva instancia de bash, luego ejecutar algunos comandos dentro de ella y devolver el control al usuario dentro de eso misma instancia .

Lo intenté:

$ bash -lic "some_command"

pero esto se ejecuta some_commanddentro de la nueva instancia y luego la cierra. Quiero que permanezca abierto.

Un detalle más que podría afectar las respuestas: si puedo hacer que esto funcione, lo usaré en mi .bashrccomo alias, ¡así que puntos de bonificación por una aliasimplementación!

Félix Saparelli
fuente
No entiendo la última parte sobre los alias. Los alias se ejecutan en el contexto del shell actual, por lo que no tienen el problema que está pidiendo que se resuelva (aunque, por lo general, las funciones son mejores que los alias, por varias razones).
tripleee
2
Quiero poder poner algo como alias shortcut='bash -lic "some_command"'en mi .bashrc y luego ejecutar shortcuty tener un nuevo shell que some_commandya se ejecutó en él.
Félix Saparelli

Respuestas:

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

dispensa la creación de archivos temporales. Pregunta en otros sitios:

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
Intenté hacer esto en Windows 10 (tratando de escribir un archivo / script de "inicio" por lotes), y obtengoThe system cannot find the file specified.
Doctor Blue
1
@Scott lo depura paso a paso: 1) ¿Funciona cat ~/.bashrc? 2) ¿ cat <(echo a)Funciona? 3) ¿ bash --rcfile somefileFunciona?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
@Scott nos encontramos con este problema en WSL (lanzando bash desde un archivo por lotes de inicio). Nuestra solución fue escapar de algunos de los caracteres para que el lote pudiera entenderlo: bash.exe --rcfile ^<^(echo 'source $HOME/.bashrc; ls; pwd'^)debería ejecutarse desde el símbolo del sistema de Windows.
user2561747
¿Hay alguna forma de hacer que funcione con el cambio de usuario, como sudo bash --init-file <(echo "ls; pwd")o sudo -iu username bash --init-file <(echo "ls; pwd")?
jeremysprofile
39

Esta es una respuesta tardía, pero tuve exactamente el mismo problema y Google me envió a esta página, así que para completar, aquí es cómo solucioné el problema.

Por lo que puedo decir, bashno tiene la opción de hacer lo que el cartel original quería hacer. La -copción siempre regresará después de que se hayan ejecutado los comandos.

Solución rota : el intento más simple y obvio en torno a esto es:

bash -c 'XXXX ; bash'

Esto funciona en parte (aunque con una capa de subcapa adicional). Sin embargo, el problema es que si bien un sub-shell heredará las variables de entorno exportadas, los alias y las funciones no se heredan. Entonces, esto podría funcionar para algunas cosas, pero no es una solución general.

Mejor : la forma de evitar esto es crear dinámicamente un archivo de inicio y llamar a bash con este nuevo archivo de inicialización, asegurándose de que su nuevo archivo init llame a su archivo habitual ~/.bashrcsi es necesario.

# 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

Lo bueno es que el archivo de inicio temporal se borrará a sí mismo tan pronto como se use, lo que reduce el riesgo de que no se limpie correctamente.

Nota: no estoy seguro de si / etc / bashrc se suele llamar como parte de un shell normal sin inicio de sesión. Si es así, es posible que desee obtener / etc / bashrc además de su ~/.bashrc.

daveraja
fuente
La rm -f $TMPFILEcosa lo hace. En realidad, no recuerdo el caso de uso que tuve para esto, y ahora uso técnicas de automatización más avanzadas, ¡pero este es el camino a seguir!
Félix Saparelli
7
No hay archivos tmp con sustitución de procesobash --rcfile <(echo ". ~/.bashrc; a=b")
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
3
El archivo temporal solo se limpiará si su script Bash se ejecuta correctamente. Una solución más sólida sería utilizar trap.
tripleee
5

Puede pasar --rcfilea Bash para que lea un archivo de su elección. Este archivo se leerá en lugar de su .bashrc. (Si eso es un problema, obtenga ~/.bashrcla fuente del otro script).

Editar: Entonces, una función para iniciar un nuevo shell con las cosas de ~/.more.shse vería así:

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

... y .more.shtendría los comandos que desea ejecutar cuando se inicie el shell. (Supongo que sería elegante evitar un archivo de inicio separado; no puede usar la entrada estándar porque entonces el shell no será interactivo, pero puede crear un archivo de inicio desde un documento aquí en una ubicación temporal y luego leerlo).

tripleee
fuente
3

Puede obtener la funcionalidad que desee obteniendo el script en lugar de ejecutarlo. p.ej:

$ cat script
cmd1
cmd2
PS guión
$ en este punto cmd1 y cmd2 se han ejecutado dentro de este shell
William Pursell
fuente
Hmm. Eso funciona, pero ¿no hay forma de incrustar todo en una alias=''?
Félix Saparelli
1
Nunca use alias; utilice funciones en su lugar. Puede poner: 'foo () {cmd1; cmd2; } 'dentro de sus scripts de inicio, y luego ejecutar foo ejecutará los dos comandos en el shell actual. Tenga en cuenta que ninguna de estas soluciones le proporciona un nuevo shell, pero puede hacer 'exec bash' y luego 'foo'
William Pursell
1
Nunca digas nunca. los alias tienen su lugar
Glenn Jackman
¿Existe un ejemplo de un caso de uso en el que no se pueda usar una función en lugar de un alias?
William Pursell
3

Agregue a ~/.bashrcuna sección como esta:

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

Entonces puede iniciar la subcapa con sub.

dashohoxha
fuente
Puntos por originalidad. El alias sí es probablemente mejor como env subshell=true bash(como que no se escape $subshella los comandos posteriores): El uso de env vs utilizando la exportación .
Félix Saparelli
Tienes razón. Pero noté que también funciona sin él env. Para verificar si está definido o no, utilizo echo $subshell(en lugar de lo env | grep subshellque usas).
dashohoxha
3

¡La respuesta aceptada es realmente útil! Solo para agregar que la sustitución de procesos (es decir, <(COMMAND)) no es compatible con algunos shells (p dash.

En mi caso, estaba intentando crear una acción personalizada (básicamente un script de shell de una línea) en el administrador de archivos de Thunar para iniciar un shell y activar el entorno virtual de Python seleccionado. Mi primer intento fue:

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

donde %fes la ruta al entorno virtual manejado por Thunar. Recibí un error (al ejecutar Thunar desde la línea de comando):

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

Luego me di cuenta de que mi sh(esencialmente dash) no admite la sustitución de procesos.

Mi solución fue invocar bashal nivel superior para interpretar la sustitución del proceso, a expensas de un nivel extra de shell:

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

Alternativamente, intenté usar here-document para dashpero sin éxito. Algo como:

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

PD: No tengo la reputación suficiente para publicar comentarios, los moderadores pueden moverlo a comentarios o eliminarlo si no es útil con esta pregunta.

Haimo
fuente
Si uno no quiere bash en la parte superior (o no lo tiene), hay varias formas documentadas de implementar la sustitución de procesos directamente, principalmente con quince.
Félix Saparelli
2

De acuerdo con la respuesta de daveraja , aquí hay un script bash que resolverá el propósito.

Considere una situación si está utilizando C-shell y desea ejecutar un comando sin salir del contexto / ventana de C-shell de la siguiente manera,

Comando que se ejecutará : busque la palabra exacta 'Prueba' en el directorio actual de forma recursiva solo en archivos * .h, * .c

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

Solución 1 : ingrese a bash desde C-shell y ejecute el comando

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

Solución 2 : escriba el comando deseado en un archivo de texto y ejecútelo usando bash

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

Solución 3 : ejecute el comando en la misma línea usando bash

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

Solución 4 : cree un sciprt (una vez) y utilícelo para todos los comandos futuros

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

El guión es el siguiente,

#!/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
fuente
2
Esta es una respuesta completamente diferente para un problema completamente diferente. (Es genial que lo hayas descubierto, pero no pertenece a este hilo. Si esto no existía en este sitio, considera abrir una nueva pregunta con tu segunda oración y luego respóndela tú mismo inmediatamente con el resto ... así es como se debe manejar esto ☺) También vea la respuesta de Ciro Santilli para una forma de hacerlo sin construir un archivo temporal en absoluto.
Félix Saparelli
Marcado sin respuesta, ya que no es un intento de responder esta pregunta (¡aunque es un gran intento de responder una diferente!)
Charles Duffy
0

Aquí hay otra variante (funcional):

Esto abre una nueva terminal gnome, luego en la nueva terminal ejecuta bash. Primero se lee el archivo rc del usuario, luego ls -lase envía un comando para su ejecución en el nuevo shell antes de que se vuelva interactivo. El último eco agrega una nueva línea adicional que se necesita para finalizar la ejecución.

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

También me resulta útil a veces decorar el terminal, por ejemplo, con colores para una mejor orientación.

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

Ejecutar comandos en un shell en segundo plano

Simplemente agregue &al final del comando, por ejemplo:

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

En caso de que no pueda o no quiera utilizar la sustitución de procesos:

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

De otra manera:

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

sh-sólo forma ( dash, busybox):

$ ENV=script sh
x-yuri
fuente
Buenas alternativas, pero se beneficiaría de tener la sección superior (antes de la ENV=scriptparte) eliminada o modificada para evitar confundirse con una copia y pegar descarada de la respuesta superior.
Félix Saparelli
@ FélixSaparelli Para ser franco, todas las soluciones, pero la ENV+ shuna, están presentes en las otras respuestas, pero en su mayoría están enterradas en toneladas de texto o rodeadas de código innecesario / no esencial. Creo que tener un resumen breve y claro beneficiaría a todos.
x-yuri