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

127

这可能是非常简单或者非常复杂的问题,但我没有找到任何相关内容... 我正在尝试打开一个新的Bash实例,然后在其中运行一些命令,并将控制权交回给用户,在同一实例内

我已经尝试过:

$ bash -lic "some_command"

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

还有一个细节可能会影响答案:如果我能让它工作,我将在我的.bashrc中使用它作为别名,因此对于别名的实现方法会额外加分!


我不理解关于别名的最后一部分。别名在当前Shell的上下文中执行,因此它们没有您要解决的问题(尽管通常情况下,函数比别名更好,原因有很多)。 - tripleee
2
我想在我的 .bashrc 文件中添加类似于 alias shortcut='bash -lic "some_command"' 的内容,然后运行 shortcut 命令,就可以打开一个新的 shell 并自动执行 some_command - Félix Saparelli
请参考 https://dev59.com/A2035IYBdhLWcg3wZ_Qt 来了解不同的方式,根据您使用的 GUI,启动一个新终端的方法也会有所不同。 - tripleee
10个回答

100

我尝试在Windows 10中完成这个任务(尝试编写一个批处理“启动”文件/脚本),但是我收到了“系统找不到指定的文件”的错误提示。 - Doctor Blue
1
@Scott 逐步调试它:1)cat ~/.bashrc 是否有效?2)cat <(echo a) 是否有效?3)bash --rcfile somefile 是否有效? - Ciro Santilli OurBigBook.com
2
@Scott,我们在WSL上遇到了这个问题(从启动批处理文件启动bash)。我们的解决方案是转义一些字符,以便批处理文件能够理解它:bash.exe --rcfile ^<^(echo 'source $HOME/.bashrc; ls; pwd'^)应该可以从Windows命令提示符中运行。 - user2561747
有没有办法使其与用户切换一起工作,例如 sudo bash --init-file <(echo "ls; pwd")sudo -iu username bash --init-file <(echo "ls; pwd") - jeremysprofile
提示3:如果您尝试从服务启动类似tmux的东西,则echo可能会被检测为主进程。拥有一个实际的文件可以避免这个问题bash --rcfile myrcfile。在文件内容中,您有. ~/.bashrc; some_command - eglasius
显示剩余2条评论

58

虽然有些晚了,但我遇到了完全相同的问题,谷歌把我带到这个页面,为了完整起见,这里是我如何解决这个问题的。

据我所知,bash 没有选项可以实现原帖想要做的事情。 -c 选项将在命令执行后立即返回。

错误的解决方案:最简单和显而易见的尝试是:

bash -c 'XXXX ; bash'

这部分有点用(尽管多了一个子Shell层)。 然而,问题在于虽然子Shell会继承导出的环境变量,但别名和函数却不会被继承。 因此,这可能对一些东西有效,但并不是通用解决方案。

更好的方法:绕过这个问题的方法是动态创建一个启动文件,并使用这个新的初始化文件调用bash,确保您的新init文件在必要时调用您的常规~/.bashrc文件。

# 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

好处在于临时的初始化文件会在被使用后自动删除,减少了没有正确清理的风险。

注意:我不确定 /etc/bashrc 是否通常作为普通非登录 shell 的一部分调用。 如果是这样,您可能需要同时源 /etc/bashrc 和您的 ~/.bashrc


1
rm -f $TMPFILE 这个东西很好用。我实际上不记得我当时使用它的用例,现在我使用更先进的自动化技术,但这仍然是正确的方法! - Félix Saparelli
10
使用进程替换符号bash --rcfile <(echo ". ~/.bashrc; a=b")不会生成临时文件。 - Ciro Santilli OurBigBook.com
3
只有当你的Bash脚本成功运行时,临时文件才会被清除。一个更健壮的解决方案是使用“trap”。 - tripleee

6
您可以在Bash中传递--rcfile,使其读取您选择的文件。这个文件将代替您的.bashrc。(如果有问题,请从其他脚本中源代码~/.bashrc)。
编辑:因此,一个使用来自~/.more.sh的内容启动新shell的函数将如下所示:
more() { bash --rcfile ~/.more.sh ; }

.more.sh中,您可以编写希望在shell启动时执行的命令。(我认为避免使用单独的启动文件会更加优雅-- 您不能使用标准输入,因为那样shell就不会是交互式的,但是您可以从临时位置创建一个here文档的启动文件,然后读取它。)


5
bash -c '<some command> ; exec /bin/bash'

避免额外的shell子层


如果使用 bash -c 'alias xxx=yyy; exec bash' 命令,它不会起作用。别名是局部的,不会传递给子 bash。 - anton_rh
1
正如别处所提到的那样,在大量的重复中,不要使用别名(alias),而是使用一个函数。shortcut () { yourterminal -e "bash -c '$@'; exec /bin/bash"; } 尽管这个命令会在具有文字单引号的情况下出现问题。 - tripleee

3

这个被接受的答案非常有帮助! 只是要补充说明,一些shell不支持进程替换(例如dash)。

在我的情况下,我试图在Thunar文件管理器中创建一个自定义操作 (基本上是一行shell脚本),以启动一个shell并激活选定的Python虚拟环境。我的第一次尝试是:

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

其中%f是由Thunar处理的虚拟环境路径。 我运行Thunar时遇到了错误(通过命令行):

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

然后我意识到我的sh(基本上是dash)不支持进程替换。

我的解决方案是在顶层调用bash来解释进程替换,代价是增加了一个额外的shell层:

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

另外,我尝试使用 here-document 用于 dash,但没有成功。大致如下:

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

附言:我没有足够的声望来发布评论,请管理员将其移动到评论区或者如果对这个问题不帮助请删除。


如果不想在上面使用bash(或者没有它),那么有各种记录的方法可以直接实现进程替换,大多数都是使用FIFO。 - Félix Saparelli

3

您可以通过源代码来获取想要的功能而不是运行它。例如:

$cat script
cmd1
cmd2
$ . script
$此时,cmd1和cmd2已经在这个shell内运行了。

嗯,这个可以用,但是难道没有一种将所有内容嵌入到“alias =''”中的方法吗? - Félix Saparelli
1
不要使用别名,而应该使用函数。您可以将“foo() { cmd1; cmd2; }”放在启动脚本中,然后执行foo将在当前shell中运行这两个命令。请注意,这两种解决方案都不会给您一个新的shell,但是您可以执行“exec bash”,然后执行“foo”。 - William Pursell
1
永远不要说永远不。别名有它们的用处。 - glenn jackman
有没有一个使用案例的例子,其中别名无法替代函数? - William Pursell

3

~/.bashrc中添加类似以下内容的部分:

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

然后你可以使用sub启动子shell。


原创分很重要。别名本身可能更好,如 env subshell=true bash(因为这不会将 $subshell 泄漏给后续命令):使用 env vs. 使用 export - Félix Saparelli
你是对的。但我注意到它也可以不用 env 来工作。为了检查它是否被定义,我使用 echo $subshell(而不是你使用的 env | grep subshell)。 - dashohoxha

2
根据 daveraja 的回答,这里有一个可以解决问题的 bash 脚本。
考虑一种情况,如果你正在使用 C-shell 并且想要在不离开 C-shell 上下文/窗口的情况下执行命令,如下所示: 要执行的命令:在当前目录中递归搜索只包含 *.h、*.c 文件中的单词“Testing”。
grep -nrs --color -w --include="*.{h,c}" Testing ./

解决方案1:从C-shell进入bash并执行该命令。
bash
grep -nrs --color -w --include="*.{h,c}" Testing ./
exit

解决方案2:将预期的命令写入文本文件中,然后使用bash执行它。
echo 'grep -nrs --color -w --include="*.{h,c}" Testing ./' > tmp_file.txt
bash tmp_file.txt

解决方案3:在同一行上使用bash运行命令
bash -c 'grep -nrs --color -w --include="*.{h,c}" Testing ./'

解决方案4:创建一个脚本(一次性),并将其用于所有未来的命令。
alias ebash './execute_command_on_bash.sh'
ebash grep -nrs --color -w --include="*.{h,c}" Testing ./

剧本如下,
#!/bin/bash
# =========================================================================
# References:
# https://dev59.com/QG435IYBdhLWcg3wrSLN#13343457
# https://dev59.com/LG865IYBdhLWcg3wkPWD#26733366
# https://dev59.com/yHE85IYBdhLWcg3wShaf#2853811
# https://dev59.com/yHE85IYBdhLWcg3wShaf#2853811
# 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://dev59.com/sG855IYBdhLWcg3wjlCL#4277753
# =========================================================================

# 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

2
这是一个完全不同的问题的完全不同的答案。(你能够想出来很酷,但它不适合在这个帖子中。如果这个网站上没有这个问题的解决方案,请考虑打开一个新的问题,并立即用你的第二句话回答它...这就是应该处理的方式☺)此外,还可以参考Ciro Santilli的答案,以一种不需要构建临时文件的方式来完成它。 - Félix Saparelli
因为它并没有尝试回答这个问题(尽管它非常出色地回答了另一个问题!),所以被标记为“不是答案”。 - Charles Duffy

1
$ bash --init-file <(echo 'some_command')
$ bash --rcfile <(echo 'some_command')

如果你不能或不想使用进程替换:

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

另一种方式:

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

sh -only 方式(dashbusybox):

$ ENV=script sh

好的替代方案,但最好将顶部部分(ENV=script之前)删除或重新措辞,以避免被误认为是明显的复制粘贴顶部答案。 - Félix Saparelli
@FélixSaparelli 坦白说,除了使用 ENV + sh 的解决方案以外,其他答案中都有这些解决方案,但是它们通常被大量的文本埋没或者被不必要/非必要的代码包围。我认为提供一个简明清晰的摘要对每个人都有好处。 - x-yuri

0

这里是另一种(可行的)变体:

它打开一个新的 GNOME 终端,然后在新终端中运行 bash。首先读取用户的 rc 文件,然后将命令 ls -la 发送到新 shell 中执行,然后再转为交互模式。 最后的 echo 添加了一个额外的换行符,以完成执行。

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

有时我也觉得在终端上添加一些装饰很有用,比如使用颜色来更好地定位。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接