如何在 Bash 中方便地使用单引号或转义整个命令行?

Kam*_*ski 6 bash command-line escaping

介绍

考虑这样的工具watch可以接受另一个命令(例如ls -l),如下所示:

watch ls -l
# or equivalently
watch 'ls -l'
Run Code Online (Sandbox Code Playgroud)

如果命令更复杂,那么如果当前 shell 或 get 解释诸如$variable*|;之类的内容,那么一切都与引用/转义有关。这两个命令是不同的:&&watch

watch echo "$$"
watch 'echo "$$"'
Run Code Online (Sandbox Code Playgroud)

这些也是:

watch date ; echo done
watch date \; echo done
Run Code Online (Sandbox Code Playgroud)

ssh类似,它可以接受一个或多个参数并构建一个要在服务器上运行的命令。如果本地 shell 解释等,或者远程 shell 则$variable取决于引用/转义。|

有些命令sh -c需要包含代码的单个参数,但引用/转义的需求是相似的。事实上watch(或ssh) 为sh -c或类似的命令构建了这个单一参数。


问题

我已经有了想要提供给或或类似工具的确切命令。命令是从历史记录中获取的,或者粘贴到命令行中或者键入,就像要直接执行一样;它逐字在我的命令行中。在使用类似工具之前,不应扩展或解释命令中的任何内容。如果是这样,则意味着所有扩展和解释都应该稍后定期进行。如果是这样,则意味着它应该发生在服务器上。watchsshwatchwatchssh

现在我需要做两件事:

  • watch在开头添加(或其他)。这不是问题。我可以最后做这件事。
  • 引用和/或转义原始命令。一般来说命令中可以包含单引号,所以一味地拥抱单引号并不是解决问题的办法。我想我知道正确的通用解决方案:

    • 将每个替换''"'"''\'',
    • 用单引号包含整个结果字符串;

    但手动完成此操作既乏味又容易出错。


问题

我可以让 Bash 根据需要向整个命令行正确添加一级单引号/转义吗?

(注:这个问题是我回答这个问题时出现的。)

Kam*_*ski 7

解决方案

\n

是的。我的想法是调用一个 shell 函数(通过按键)来READLINE_LINE使用该${variable@Q}功能进行操作。

\n

文档的相关部分:

\n
\n

${parameter@operator}

\n

扩展要么是 的值的转换parameter,要么是关于parameter其自身的信息的转换,具体取决于 的值operator。每个operator都是一个字母:

\n

Q
parameter\n扩展是一个字符串,它是以可重复用作输入的格式引用的值。

\n
\n

来源

\n
\n

READLINE_LINE
\nReadline 行缓冲区的内容,与bind -x[\xe2\x80\xa6] 一起使用。

\n
\n

来源

\n

Bash 4.4.20 中的工作原理如下:

\n
_quote_all() { READLINE_LINE="${READLINE_LINE@Q}"; }\nbind -x \'"\\C-x\\C-o":_quote_all\'\n
Run Code Online (Sandbox Code Playgroud)\n

要测试解决方案,请在命令行中准备一条命令(不要执行),例如

\n
d="$(LC_ALL=C date)"; printf \'It\'\\\'\'s now %s\\n\' "$d"\n
Run Code Online (Sandbox Code Playgroud)\n

(引用和整个命令可以被简化。它是故意这样的。您可以执行它以确保它是一个有效的命令,但在继续之前将其放回命令行中。)

\n

点击Ctrl+ xCtrl+ o,它将被正确引用/转义以达到我们的目的。它看起来像这样:

\n
\'d="$(LC_ALL=C date)"; printf \'\\\'\'It\'\\\'\'\\\'\\\'\'\'\\\'\'s now %s\\n\'\\\'\' "$d"\'\n
Run Code Online (Sandbox Code Playgroud)\n

现在您需要做的就是在前面添加watch(或ssh \xe2\x80\xa6,或其他任何内容)并执行。如果是的话watch请注意标题就像

\n
Every 2.0s: d="$(LC_ALL=C date)"; printf \'It\'\\\'\'s now %s\\n\' "$d"\n
Run Code Online (Sandbox Code Playgroud)\n

它包含原始命令。命令正确到达watch,没有任何部分被过早解释。

\n
\n

改进

\n

为了方便起见,考虑这个变体:

\n
_quote_all() { READLINE_LINE=" ${READLINE_LINE@Q}"; READLINE_POINT=0; }\n
Run Code Online (Sandbox Code Playgroud)\n

它将准备好行并将光标放在开头,以便您可以watch立即输入。或者甚至可能是这个变体(它故意使用不同的名称,我们正在为其创建一个单独的绑定):

\n
_prepend_watch() { READLINE_LINE="watch  ${READLINE_LINE@Q}"; READLINE_POINT=6; }\nbind -x \'"\\C-x\\C-w":_prepend_watch\'\n
Run Code Online (Sandbox Code Playgroud)\n

现在Ctrl+ x, Ctrl+w处理引用、watch自动插入并将光标置于正确的位置以便您键入选项。

\n

使用另一个函数READLINE_POINT可以处理以下情况:键入watch(或ssh \xe2\x80\xa6)后跟一个命令,其中引用/转义就像要直接执行该命令一样。将光标放在命令开始的位置,按下按键,然后让函数修改从光标到行尾的所有内容。我在这里不提供这样的功能;如果需要的话自己写一下。

\n
\n

Yo Dawg,我听说你喜欢引用

\n

您可以堆叠解决方案。我的意思是你可以从这里开始

\n
df -h | grep -v tmpfs\n
Run Code Online (Sandbox Code Playgroud)\n

对此

\n
watch \'df -h | grep -v tmpfs\'\n
Run Code Online (Sandbox Code Playgroud)\n

对此

\n
ssh hostB -t \'watch \'\\\'\'df -h | grep -v tmpfs\'\\\'\'\'\n
Run Code Online (Sandbox Code Playgroud)\n

对此

\n
ssh -t hostA \'ssh -t hostB \'\\\'\'watch \'\\\'\'\\\'\\\'\'\'\\\'\'df -h | grep -v tmpfs\'\\\'\'\\\'\\\'\'\'\\\'\'\'\\\'\'\'\n
Run Code Online (Sandbox Code Playgroud)\n

(是的,我知道ssh -J

\n

只需点击Ctrl+ xCtrl+o并在每一步中在前面添加一个或几个单词即可。

\n