方法一

方法一

在 X 会话中,我可以执行以下步骤:

  1. 打开终端模拟器 (Xterm)。
    • Bash 读取.bashrc并变得交互式。命令提示符正在等待命令。
  2. 进入vim 'my|file*' '!another file&'
    • Vim 启动,并显示my|file*!another file&进行编辑。
  3. CTRL-Z
    • Vim 变为暂停工作,Bash 提示符再次出现。

我不明白一个脚本执行步骤 1 和 2,而不放弃步骤 3(作业控制)。它将接收文件作为参数:

script 'my|file*' '!another file&'

你能帮我么?

该脚本将由文件管理器执行,选定的文本文件作为参数提供。

别担心,我很理智,通常不会那样命名我的文件。另一方面,如果此类特殊字符 ( *!&|$<newline>...) 碰巧出现在文件名中,则脚本不应中断。

我使用 Vim 只是为了一个具体的例子。在终端中交互运行并接收参数的其他程序将从解决方案中受益。


我的尝试/研究

xterm -e vim "$@"

显然失败了。 Xterm 没有外壳。

使用初始命令运行交互式 bash 子 shell,而不立即返回超级 shell 看起来很有希望。那里的答案解释了如何.bashrc为 Bash 指定不同的文件(而不是 )作为源。所以我创建了这个~/.vimbashrc

. ~/.bashrc
set -m
vim

现在,打电话

xterm -e bash --init-file ~/.vimbashrc

结果是一个带有 Bash 和可挂起的 Vim 的新终端。但这样我就看不到如何指定 Vim 应打开的文件。

答案1

我可以想到几种方法,我认为第一种方法在 Bash 上不那么老套,主要是因为(对我来说)它似乎有更容易处理的怪癖。然而,由于这也可能是一个品味问题,所以我将两者都涵盖。

方法一

“预先引用”方式

它包括使你的脚本扩展它的$@数组,从而代表内部交互执行此操作bash,并且您可以使用/dev/fd/X文件系统上的文件描述符工具(如果您的系统上可用)作为参数的参数--init-file。此类文件描述符可能引用此处字符串,您将在其中处理文件名,如下所示:

xterm -e bash --init-file /dev/fd/3 3<<<". ~/.bashrc; set -m; vim $@"

这种文件系统上的文件描述符技巧的一个优点是您拥有一个独立的解决方案,因为它不依赖于外部帮助程序文件,例如您的.vimbashrc.这在这里特别方便,因为--init-file由于扩展,内容是动态的$@

另一方面,它可能需要注意文件描述符从脚本一直到内壳的实际持久性。只要没有中间进程关闭它从其父进程接收的文件描述符,这个技巧就可以正常工作。这是许多标准工具中的常见行为,但如果 asudo处于关闭所有收到的文件描述符的默认行为中间,那么这个技巧将不起作用,我们需要诉诸临时文件或原始文件.vimbashrc

无论如何,$@在文件名包含空格或换行符的情况下,简单地使用上面的方法是行不通的,因为在内部bash使用命令序列时,这些文件名没有被引用,因此文件名中的空格根据标准行为被解释为单词分隔符。

@Q为了解决这个问题,我们可以注入一级引用,在 Bash 4.4 及更高版本上,只需在数组上使用参数转换语法即可$@,如下所示:

xterm -e bash --init-file /dev/fd/3 3<<<". ~/.bashrc; set -m; vim ${@@Q}"

在 Bash 4.4 以下的版本中,我们可以通过使用它来获得相同的内容printf %q,如下所示(作为 Here Document 以获得更好的可读性,但会与上面的 Here String 执行相同的操作):

printf -v files ' %q' "$@"
xterm -e bash --init-file /dev/fd/3 3<<EOF
. ~/.bashrc
set -m
vim $files
EOF

顺便说一句,根据您的设置,您/etc/bash.bashrc也可能会考虑在用户的 之前进行采购.bashrc,因为这是 Bash 对于交互式 shell 的标准行为。

另请注意,我保留该set -m命令是为了熟悉您的原始脚本,并且因为它是无害的,但它实际上在这里是多余的,因为--init-file暗示了一个交互bash,这意味着-m.相反,如果从字面上理解问题标题,您希望有一个作业控制 shell,但又不是一个完全交互式的 shell,则需要它。有行为差异

方法二

选项-s

Bash(和 POSIX)-s选项允许您为交互式 shell 指定参数,就像对非交互式 shell 所做的那样1。因此,通过使用此-s选项,并且仍然作为一个独立的解决方案,它会像:

xterm -e bash --init-file /dev/fd/3 -s "$@" 3<<'EOF'
. ~/.bashrc
set -m  # superfluous as bash is run with `--init-file`; you would instead need it for a job-controlling yet "non-interactive" bash (ie no `--init-file` nor `-i` option)
exec <<<'exec < /dev/tty; vim "$@"'
EOF

需要注意的奇怪事项:

  1. Here文档的分隔符规范必须位于单引号内,否则$@此处文档内的部分将由您的脚本(没有正确的引用等)扩展,而不是bash由其所属的内部扩展。这与“预先引用”方法相反,其中故意不引用此处文档的分隔符
  2. Here String(exec <<<...标准输入重定向部分)必须也是单引号类型2,或者当其数组尚未填充时,其中"$@"的部分将由内部扩展bash$@
  3. 具体来说,我们需要这样的标准输入重定向(通过此处字符串进行的重定向exec <<<)作为帮助器,只是为了使内部“推迟”需要完全填充数组bash 的命令的执行$@
  4. 在这样的帮助程序标准输入重定向(Here String 部分)中,我们需要bash再次使内部重定向其自己的标准输入,这次返回到其控制终端(因此该exec < /dev/tty行)以使其恢复其交互功能
  5. 我们需要全部命令意思是在指定的exec < /dev/tty(即此处)之后执行vim "$@"在同一条线上3因为exec < /dev/tty在这样的重定向之后,将不再读取此处的字符串4。事实上,如果这个特定的片段像本例中那样足够短,那么作为 Here String 看起来会更好

此方法可能更适合与像您这样的外部帮助程序文件一起使用.vimbashrc(尽管放弃了独立的便利性),因为就文件名参数问题而言,此类文件的内容可以完全静态。这样,文件管理器调用的脚本将变得简单如下:

xterm -e bash --init-file .vimbashrc -s "$@"

它的同伴.vimbashrc会像:

. ~/.bashrc
#  (let's do away with the superfluous `set -m`)
exec <<<'exec < /dev/tty && vim "$@"'  # let's also run vim only if the redirection to the controlling tty succeeded

伴随文件仍然有一些怪癖,但是,除了任何“清洁”考虑之外,后一个版本(非独立)的一个可能的优点是它的整个xterm -e ...命令(除了部分"$@")可以直接由文件管理器使用代替你的“脚本”,如果它非常友善地允许您指定一个命令,它会尽职尽责地在空格上分割以生成规范的“argv”数组以及文件名参数。

另请注意,整个-s方法(在其所有版本中)默认情况下使用.bash_history帮手stdin 重定向,这就是为什么我一直尽可能保留该特定部分的原因。当然,您可以使用您喜欢的方式来阻止此类更新,例如通过unset HISTFILE添加--init-file.

--

作为比较,使用此方法dash会更方便,因为dash会填充环境变量$@指定的脚本的数组ENV,因此独立的解决方案将非常简单:

xterm -e env ENV=/dev/fd/3 dash -s "$@" 3<<'EOF'  # `dash` run through `env` just to be positive that `ENV` is present when dash starts
vim "$@"
EOF

华泰


1例外情况是无法指定,与使用选项$0调用 shell 时相反-c

2如果您使用了额外的此处文档,则还需要引用其分隔符

3实际上在相同的“complete_command”上由 POSIX 定义,这意味着您仍然可以跨越多行,只要它们是同一“complete_command”的一部分,例如,当这些行具有续行反斜杠或位于复合块内时

4这应该是标准行为,大概源自第一个简短的段落本小节

答案2

作为一个简单的概念证明,我尝试了:

. ~/.bashrc
set -m
vim $arg

和:

arg=file-to-edit xterm -e bash --init-file vimbashrc

它的行为或多或少与您指定的一样,并允许您将参数传递给交互式命令。

因此,使用临时文件可能会导致类似的结果

#!/bin/bash

if [ "$1" = "" ] ; then
        . ~/.bashrc
        set -m
        (IFS=$'\n'; vi $(cat "$tmpfile"))
else
    tmpfile=$(mktemp /tmp/vimstart.XXXXXX)
    for arg in "$@" ; do
        echo "$arg" >> $tmpfile
    done
    tmpfile=$tmpfile xterm -e bash --init-file ./vimstart
    rm "$tmpfile"
fi

由于文件名可能包含\n,因此您还可以使用 NULL 字符作为分隔符:

#!/bin/bash

if [ "$1" = "" ] ; then
    . ~/.bashrc
    set -m
    (IFS=$'\00'; vi $(cat "$tmpfile"))
else
    tmpfile=$(mktemp /tmp/vimstart.XXXXXX)
    for arg in "$@" ; do
        echo -n "$arg" >> $tmpfile
                echo -n $'\x00' >> $tmpfile
    done
    tmpfile=$tmpfile xterm -e bash --init-file ./vimstart
    rm "$tmpfile"
fi

相关内容