使用初始命令运行交互式 bash 子外壳,而无需立即返回(“超级”)外壳

SAB*_*uca 134 bash

我想运行一个 bash 子 shell,(1) 运行一些命令,(2) 然后留在那个子 shell 中按照我的意愿去做。我可以单独做这些:

  1. 使用-c标志运行命令:

    $> bash -c "ls; pwd; <other commands...>"
    
    Run Code Online (Sandbox Code Playgroud)

    但是,它会在命令执行后立即返回到“超级”shell。我也可以只运行一个交互式子shell:

  2. 启动新bash进程:

    $> bash
    
    Run Code Online (Sandbox Code Playgroud)

    它不会退出子shell,直到我明确地说出来......但我无法运行任何初始命令。我找到的最接近的解决方案是:

    $> bash -c "ls; pwd; <other commands>; exec bash"
    
    Run Code Online (Sandbox Code Playgroud)

    这是可行的,但不是我想要的方式,因为它在一个子 shell 中运行给定的命令,然后打开一个单独的命令进行交互。

我想在一行上做到这一点。一旦我退出子外壳,我应该返回到常规的“超级”外壳,而不会发生任何事故。一定有办法~~

注意:我不是在问什么......

  1. 不问从哪里获得 bash 手册页
  2. 不问如何从文件中读取初始化命令......我知道如何做到这一点,这不是我正在寻找的解决方案
  3. 对使用 tmux 或 gnu 屏幕不感兴趣
  4. 对此不感兴趣。即,该问题是一般性的,而不是出于任何特定目的
  5. 如果可能的话,我想避免使用那种可以完成我想要的东西的变通方法,但是以一种“肮脏”的方式。我只想在一行上执行此操作。特别是,我不想做类似的事情xterm -e 'ls'

Jon*_*ter 110

这可以通过临时命名管道轻松完成:

bash --init-file <(echo "ls; pwd")
Run Code Online (Sandbox Code Playgroud)

这个答案归功于Lie Ryan的评论。我发现这真的很有用,而且在评论中不太明显,所以我认为它应该是它自己的答案。

  • 这大概意味着`$HOME/.bashrc` 没有被执行。它必须包含在临时命名管道中。 (15认同)
  • 澄清一下,像这样:`bash --init-file &lt;(echo ".\"$HOME/.bashrc\"; ls; pwd")` (13认同)
  • 这太恶心了,但确实有效。我不敢相信 bash 不直接支持这一点。 (8认同)
  • @Gus,`.` 是 `source` 命令的同义词:https://ss64.com/bash/source.html。 (3认同)
  • 有没有办法让它与用户切换一起工作,例如`sudo bash --init-file &lt;(echo "ls; pwd")` 或`sudo -iu username bash --init-file &lt;(echo "ls;密码")`? (3认同)

Edu*_*nec 17

您可以使用临时文件以迂回的方式执行此操作,尽管它需要两行:

echo "ls; pwd" > initfile
bash --init-file initfile
Run Code Online (Sandbox Code Playgroud)

  • 这是一个老问题,但 Bash 可以使用以下语法创建临时命名管道:bash --init-file &lt;(echo "ls; pwd")。 (13认同)
  • 为了获得良好的效果,您可以通过在其中包含“rm $BASH_SOURCE”来使临时文件自行删除。 (2认同)
  • @SABBATINILuca:我不是说那样的话。这只是一种方法,正如 @cjc 指出的那样,`mktemp` 确实解决了临时文件问题。Bash *可以*支持从标准输入读取 init 命令,但据我所知它不支持。将 `-` 指定为初始化文件并通过管道传输它们一半工作,但 Bash 然后退出(可能是因为它检测到管道)。恕我直言,优雅的解决方案是使用 exec。 (2认同)
  • 这是否也会覆盖您正常的 bash 初始化?@SABBATINILuca你想用这个实现什么目的,为什么你需要生成一个shell自动运行一些命令,然后保持该shell打开? (2认同)

小智 12

试试这个:

$> bash -c "ls;pwd;other commands;$SHELL"
Run Code Online (Sandbox Code Playgroud)

$SHELL 它使外壳以交互模式打开,等待关闭exit

  • 仅供参考,这之后会打开一个新的 shell,因此如果任何命令影响当前 shell 的状态(例如,获取文件),它可能无法按预期工作 (14认同)

tor*_*gen 6

为什么不使用本机子 shell?

$ ( ls; pwd; exec $BASH; )
bar     foo     howdy
/tmp/hello/
bash-4.4$ 
bash-4.4$ exit
$
Run Code Online (Sandbox Code Playgroud)

用括号括住命令会使 bash 生成一个子进程来运行这些命令,因此您可以在不影响父 shell 的情况下更改环境。这基本上相当于bash -c "ls; pwd; exec $BASH".

如果这看起来仍然很冗长,有两种选择。一种是将此代码片段作为函数:

$ run() { ( eval "$@"; exec $BASH; ) }
$ run 'ls; pwd;'
bar     foo     howdy
/tmp/hello/
bash-4.4$ exit
$ run 'ls;' 'pwd;'
bar     foo     howdy
/tmp/hello/
bash-4.4$ exit
$
Run Code Online (Sandbox Code Playgroud)

另一种方法是exec $BASH缩短:

$ R() { exec $BASH; }
$ ( ls; pwd; R )
bar     foo     howdy
/tmp/hello/
bash-4.4$ exit
$
Run Code Online (Sandbox Code Playgroud)

我个人R更喜欢这种方法,因为不需要使用转义字符串。


gle*_*man 5

我指的“Expect 解决方案”是使用Expect 编程语言编写 bash shell :

#!/usr/bin/env expect
set init_commands [lindex $argv 0]
set bash_prompt {\$ $}              ;# adjust to suit your own prompt
spawn bash
expect -re $bash_prompt {send -- "$init_commands\r"}
interact
puts "exiting subshell"
Run Code Online (Sandbox Code Playgroud)

你可以这样运行:./subshell.exp "ls; pwd"


小智 5

您需要执行启动脚本,然后继续进行交互式会话。

默认启动脚本是~/.bashrc,可以通过选项指定另一个脚本--init-file。如果您只是传递一个--init-file选项,您的脚本将替换默认值,而不是增强它。

解决方案是使用<(...)语法传递一个临时脚本,该脚本来源默认值,~/.bashrc后跟任何其他命令:

bash --init-file <(echo ". ~/.bashrc; ls; pwd; ### other commands... ###")
Run Code Online (Sandbox Code Playgroud)