如何调用 bash,在新的 shell 中运行命令,然后将控制权交还给用户?

这一定非常简单或者非常复杂,但是我找不到任何关于它的东西... ... 我正在尝试打开一个新的 bash 实例,然后在其中运行一些命令,并将控制返回给用户 在同样的情况下

我试过:

$ bash -lic "some_command"

但是这会在新实例中执行 some_command,然后关闭它。我希望它保持打开状态。

还有一个可能会影响答案的细节: 如果我可以让它工作,我将在我的 .bashrc中使用它作为别名(es) ,所以对于 alias实现的奖励点!

157333 次浏览

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

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

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.

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.

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

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.

$ 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

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)'
bash -c '<some command> ; exec /bin/bash'

will avoid additional shell sublayer