123

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!

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
Félix Saparelli
  • 8,424
  • 6
  • 52
  • 67
  • 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). – tripleee Aug 19 '11 at 10:55
  • 2
    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. – Félix Saparelli Aug 19 '11 at 10:58
  • See also https://stackoverflow.com/questions/5436899/how-do-i-start-commands-in-new-terminals-in-bash-script for various ways to run a new terminal depending on which GUI you are using. – tripleee Feb 03 '23 at 08:31

10 Answers10

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

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

Community
  • 1
  • 1
Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
  • 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.` – Doctor Blue Dec 08 '17 at 13:22
  • 1
    @Scott debug it step by step: 1) Does `cat ~/.bashrc` work? 2) Does `cat <(echo a)` work? 3) Does `bash --rcfile somefile` work? – Ciro Santilli OurBigBook.com Dec 08 '17 at 13:32
  • 2
    @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. – user2561747 Dec 08 '17 at 20:11
  • 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")`? – jeremysprofile Aug 21 '18 at 18:53
  • Hint: [Difference between rcfile and init-file](https://askubuntu.com/questions/760749/what-is-the-difference-between-init-file-and-rcfile) – lony Mar 10 '20 at 13:07
  • Hint2: Not working for docker, but [here](https://stackoverflow.com/questions/59814742/docker-run-bash-init-file) is the solution `bash -c "cd /usr/; pwd; exec \"\$0\""` – lony Mar 10 '20 at 13:17
  • 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_command` – eglasius Sep 13 '21 at 08:20
58

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.

agotsis
  • 179
  • 4
  • 11
daveraja
  • 839
  • 6
  • 9
6

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.)

Edit: So 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.)

tripleee
  • 175,061
  • 34
  • 275
  • 318
5
bash -c '<some command> ; exec /bin/bash'

will avoid additional shell sublayer

joanis
  • 10,635
  • 14
  • 30
  • 40
soltiz
  • 154
  • 2
  • 4
  • Doesn't work if `bash -c 'alias xxx=yyy; exec bash'`. Alias is local and will not be passed to child bash. – anton_rh Dec 16 '22 at 12:51
  • 1
    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. – tripleee Feb 03 '23 at 08:31
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.

dashohoxha
  • 221
  • 1
  • 5
  • 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](http://i.imgur.com/XSORn6h.png) vs. [using export](http://i.imgur.com/SqwdkH8.png). – Félix Saparelli Sep 13 '15 at 11:33
  • 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). – dashohoxha Sep 13 '15 at 16:26
3

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.

Haimo
  • 31
  • 2
  • 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. – Félix Saparelli Jul 02 '19 at 11:30
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
William Pursell
  • 204,365
  • 48
  • 270
  • 300
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://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
  • 479
  • 5
  • 11
  • 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. – Félix Saparelli Jul 09 '18 at 06:41
  • 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!) – Charles Duffy Mar 04 '20 at 00:21
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
x-yuri
  • 16,722
  • 15
  • 114
  • 161
  • 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. – Félix Saparelli May 23 '20 at 02:38
  • @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. – x-yuri May 23 '20 at 07:14
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)'
QT-1
  • 900
  • 14
  • 21