How to invoke bash, run commands inside the new shell, and then give control back to user?
Asked Answered
S

10

132

This must either be really simple or really complex, but I couldn't find anything about it... I am trying to open a new bash instance, then run a few commands inside it, and give the control back to the user inside that same instance.

I tried:

$ bash -lic "some_command"

but this executes some_command inside the new instance, then closes it. I want it to stay open.

One more detail which might affect answers: if I can get this to work I will use it in my .bashrc as alias(es), so bonus points for an alias implementation!

Sansbury answered 19/8, 2011 at 10:41 Comment(3)
I don't understand the last part about aliases. Aliases execute in the current shell's context, so they don't have the problem you are asking to have solved (though usually, functions are better than aliases, for a number of reasons).Apply
I want to be able to put something like alias shortcut='bash -lic "some_command"' in my .bashrc then run shortcut and have a new shell with some_command already ran in it.Rampage
See also #5437399 for various ways to run a new terminal depending on which GUI you are using.Apply
I
106
bash --rcfile <(echo '. ~/.bashrc; some_command')

dispenses the creation of temporary files. Question on other sites:

Intension answered 22/3, 2016 at 10:34 Comment(7)
I attempted to do this in Windows 10 (trying to write a batch "startup" file/script), and I get The system cannot find the file specified.Botchy
@Scott debug it step by step: 1) Does cat ~/.bashrc work? 2) Does cat <(echo a) work? 3) Does bash --rcfile somefile work?Intension
@Scott we just ran into this issue on WSL (launching bash from a startup batch file). Our solution was to escape some of the characters so batch could understand it: bash.exe --rcfile ^<^(echo 'source $HOME/.bashrc; ls; pwd'^) should run from the windows command prompt.Punch
Is there a way to make it work with user switching, such as sudo bash --init-file <(echo "ls; pwd") or sudo -iu username bash --init-file <(echo "ls; pwd")?Peonir
Hint: Difference between rcfile and init-fileAffer
Hint2: Not working for docker, but here is the solution bash -c "cd /usr/; pwd; exec \"\$0\""Affer
Hint 3: if you try this from a service to start something like tmux, the echo could be detected as the main process. Having an actual file avoids the issue bash --rcfile myrcfile. And in the file contents you have . ~/.bashrc; some_commandSpherical
A
59

This is a late answer, but I had the exact same problem and Google sent me to this page, so for completeness here is how I got around the problem.

As far as I can tell, bash does not have an option to do what the original poster wanted to do. The -c option will always return after the commands have been executed.

Broken solution: The simplest and obvious attempt around this is:

bash -c 'XXXX ; bash'

This partly works (albeit with an extra sub-shell layer). However, the problem is that while a sub-shell will inherit the exported environment variables, aliases and functions are not inherited. So this might work for some things but isn't a general solution.

Better: The way around this is to dynamically create a startup file and call bash with this new initialization file, making sure that your new init file calls your regular ~/.bashrc if necessary.

# 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

The nice thing is that the temporary init file will delete itself as soon as it is used, reducing the risk that it is not cleaned up correctly.

Note: I'm not sure if /etc/bashrc is usually called as part of a normal non-login shell. If so you might want to source /etc/bashrc as well as your ~/.bashrc.

Argentiferous answered 12/9, 2013 at 6:9 Comment(3)
The rm -f $TMPFILE thing makes it. I don't actually remember the use-case I had for this, and I now use more advanced automation techniques, but this is the way to go !Rampage
No tmp files with process substitution bash --rcfile <(echo ". ~/.bashrc; a=b")Intension
The temp file will only be cleaned if your Bash script runs successfuly. A more robust solution would be to use trap.Apply
M
8
bash -c '<some command> ; exec /bin/bash'

will avoid additional shell sublayer

Mainsail answered 30/9, 2021 at 13:56 Comment(2)
Doesn't work if bash -c 'alias xxx=yyy; exec bash'. Alias is local and will not be passed to child bash.Squinty
As noted elsewhere, in copious amounts of repetitions, don't use aliases. Use a function. shortcut () { yourterminal -e "bash -c '$@'; exec /bin/bash"; } though this will have problems with literal single quotes in the command.Apply
A
7

You can pass --rcfile to Bash to cause it to read a file of your choice. This file will be read instead of your .bashrc. (If that's a problem, source ~/.bashrc from the other script.)

In concrete practice, a function to start a new shell with the stuff from ~/.more.sh would look something like:

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

... and in .more.sh you would have the commands you want to execute when the shell starts. (I suppose it would be elegant to avoid a separate startup file -- you cannot use standard input because then the shell will not be interactive, but you could create a startup file from a here document in a temporary location, then read it.)

Apply answered 19/8, 2011 at 10:54 Comment(0)
H
4

The accepted answer is really helpful! Just to add that process substitution (i.e., <(COMMAND)) is not supported in some shells (e.g., dash).

In my case, I was trying to create a custom action (basically a one-line shell script) in Thunar file manager to start a shell and activate the selected Python virtual environment. My first attempt was:

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

where %f is the path to the virtual environment handled by Thunar. I got an error (by running Thunar from command line):

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

Then I realized that my sh (essentially dash) does not support process substitution.

My solution was to invoke bash at the top level to interpret the process substitution, at the expense of an extra level of shell:

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

Alternatively, I tried to use here-document for dash but with no success. Something like:

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

P.S.: I do not have enough reputation to post comments, moderators please feel free to move it to comments or remove it if not helpful with this question.

Hearttoheart answered 1/7, 2019 at 10:56 Comment(1)
If one does not want bash on top (or doesn't have it), there's various documented ways to implement process substitution directly, mostly with fifos.Rampage
P
3

You can get the functionality you want by sourcing the script instead of running it. eg:

$cat script
cmd1
cmd2
$ . script
$ at this point cmd1 and cmd2 have been run inside this shell
Postconsonantal answered 19/8, 2011 at 10:46 Comment(4)
Hmm. That works, but is there no way of embedding everything in an alias=''?Rampage
Never use aliases; use functions instead. You can put: 'foo() { cmd1; cmd2; }' inside your start up scripts, and then executing foo will run the two commands in the current shell. Note that neither of these solutions give you a new shell, but you can do 'exec bash' and then 'foo'Postconsonantal
Never say never. aliases have their placeDivest
Is there an example of a use case where a function cannot be used instead of an alias?Postconsonantal
V
3

Append to ~/.bashrc a section like this:

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

Then you can start the subshell with sub.

Vegetarianism answered 12/9, 2015 at 13:57 Comment(2)
Points for originality. The alias itself is probably better as env subshell=true bash (as that won't leak $subshell to subsequent commands): Using env vs. using export.Rampage
You are right. But I noticed that it works without env as well. For checking whether is is defined or not, I use echo $subshell (instead of env | grep subshell that you use).Vegetarianism
R
2

With accordance with the answer by daveraja, here is a bash script which will solve the purpose.

Consider a situation if you are using C-shell and you want to execute a command without leaving the C-shell context/window as follows,

Command to be executed: Search exact word 'Testing' in current directory recursively only in *.h, *.c files

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

Solution 1: Enter into bash from C-shell and execute the command

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

Solution 2: Write the intended command into a text file and execute it using bash

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

Solution 3: Run command on the same line using bash

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

Solution 4: Create a sciprt (one-time) and use it for all future commands

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

The script is as follows,

#!/bin/bash
# =========================================================================
# References:
# https://mcmap.net/q/174998/-command-inside-if-statement-of-bash-script-duplicate
# https://mcmap.net/q/174999/-how-to-preserve-double-quotes-in-in-a-shell-script
# https://mcmap.net/q/45406/-how-to-echo-shell-commands-as-they-are-executed
# https://mcmap.net/q/45406/-how-to-echo-shell-commands-as-they-are-executed
# 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://mcmap.net/q/53670/-how-do-i-compare-two-string-variables-in-an-39-if-39-statement-in-bash-duplicate
# =========================================================================

# 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
Rumelia answered 8/7, 2018 at 4:23 Comment(2)
This is a completely different answer for a completely different problem. (It's cool that you figured that out, but it doesn't belong on this thread. If this didn't exist on this site, consider opening a new question with your second sentence, then immediately answering it yourself with the rest... that's how this should be handled ☺) Also see Ciro Santilli's answer for a way to do it without building a temp file at all.Rampage
Flagged not-an-answer, as it's not an attempt to answer this question (though it's a really great attempt at answering a different one!)Jacquerie
B
1
$ bash --init-file <(echo 'some_command')
$ bash --rcfile <(echo 'some_command')

In case you can't or don't want to use process substitution:

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

Another way:

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

sh-only way (dash, busybox):

$ ENV=script sh
Bret answered 20/5, 2020 at 19:33 Comment(2)
Good alternatives, but would benefit from having the top section (before the ENV=script part) removed or reworded to avoid being confused with a blatant copy-paste of the top answer.Rampage
@FélixSaparelli To be frank all the solutions but the ENV + sh one are present in the other answers, but mostly buried in tons of text or surrounded by unneeded/nonessential code. I believe having a brief, clear summary would be of benefit to everybody.Bret
L
0

Here is yet another (working) variant:

This opens a new gnome terminal, then in the new terminal it runs bash. The user's rc file is read first, then a command ls -la is sent for execution to the new shell before it turns interactive. The last echo adds an extra newline that is needed to finish execution.

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

I also find it useful sometimes to decorate the terminal, e.g. with colorfor better orientation.

gnome-terminal --profile green -- bash -c 'bash --rcfile <( cat ~/.bashrc; echo ls -la ; echo)'
Lexicography answered 13/10, 2020 at 17:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.