多行 bash 使用 sed 删除前导空格:引号错误地保留在命令参数中

Multiline bash using sed to remove leading whitespace: quotes erroneously preserved in command argument

我正在运行宁这段代码:

annotate-output $((sed -E 's/^[ ]+//;'  <<____COMMAND

sshfs 
foo_user@fooserver.com:/sftp_folder 
  /var/sshfs.sandbox/server.com 
  -o 
    user=foo_user
    ,reconnect
    ,ServerAliveInterval=15
    ,ServerAliveCountMax=3

____COMMAND
) | sed -E -e ':a;N;$!ba;s/\n//g')

这是我(也许是笨拙的)尝试在 bash 中实现多行的通用方法。请注意,只要我试图分成多行的命令需要它,就会有一个尾随 space:

sshfs 
foo_user@fooserver.com:/sftp_folder 
  /var/sshfs.sandbox/server.com 
  -o 

对于必须在中间没有空格的情况下连接的选项,没有尾随 spaces space:

user=foo_user
,reconnect
,ServerAliveInterval=15
,ServerAliveCountMax=3

如果我在任何地方都没有引号或双引号,它就可以正常工作。如果我 运行 这个:

annotate-output $((sed -E 's/^[ ]+//;'  <<____COMMAND

sshfs 
foo_user@fooserver.com:/sftp_folder 
  "/var/sshfs.sandbox/server.com"
  -o 
    user=foo_user
    ,reconnect
    ,ServerAliveInterval=15
    ,ServerAliveCountMax=3

____COMMAND
) | sed -E -e ':a;N;$!ba;s/\n//g')

我得到:

fuse: bad mount point `"/var/sshfs.sandbox/server.com"': No such file or directory

引号似乎作为 "argument" 的一部分传递给了 sshfs。为什么?

编辑 1 (20170418 0739) [注意现在是 separate question]:

我现在明白为什么要保留引号了 (ty Charles Duffy - see )

我在 this answer 之后建模了我的解决方案。我需要应用 sed 两次,一次用于前导 space ,另一次用于 EOL,这就是我最终使用这些理解的原因。 PS:我有前导 space,而不是前导制表符,因此删除前导制表符的 <<- 标记没有用

我最初的问题是我想要一个可以粘贴到任何脚本中的样板文件 header/footer,然后只需键入包含在此页眉页脚中的命令,就好像我不必担心缩进一样。 我知道我可以通过分配给一个变量然后稍后使用结果来解决问题,但我不想那样。我希望能够在一处输入换行缩进命令 。 基本上。具有这些特征:

这可能吗?

编辑2(20170421 0818):原问题已得到解答。请关注我正在尝试解决的相关问题的演变here

实际有效的方法

IFS=,  # cause "${array[*]}" to combine all items with commas

options=(
  user=foo_user           # can put a comment here if you like
  reconnect               # or here
  ServerAliveInterval=15  # or so forth
  ServerAliveCountMax=3
)

sshfs_cmd=(
  sshfs 
  foo_user@fooserver.com:/sftp_folder 
    /var/sshfs.sandbox/server.com
      -o "${options[*]}"
)

"${sshfs_cmd[@]}"

解释为什么另一种方法没有

不带引号的扩展会经过以下解析步骤,会经过以下解析步骤:

  • 字符串拆分(根据 IFS 中的字符拆分为多个不同的单词)
  • Glob 扩展(将任何看起来像 glob 表达式的单词替换为与该表达式匹配的名称列表)。

就是这样。没有引号,没有引号删除,没有参数替换等。如果不将引号解析为语法,它们将导致被解析为单个单词的变量不会以这种方式解析;在不删除引号的情况下,这些引号在被调用的命令中作为文字数据出现。

有关详细讨论,请参阅 BashFAQ #50

使用eval

下面是您想要的 -- 删除前导但不删除尾随的白色space,并将 heredoc 内容合并到一个命令中。 (无需竭尽全力在命令末尾保留尾随换行符:即使没有 true 以换行符文字结尾,eval "true" 也能完美运行。

eval "$(
  { sed -E -e 's/^[ ]+//;' -e ':a;N;$!ba;s/\n//g' | tr -d '\n'; } <<'____COMMAND'
sshfs 
foo_user@fooserver.com:/sftp_folder 
  /var/sshfs.sandbox/server.com 
  -o 
    user=foo_user
    ,reconnect
    ,ServerAliveInterval=15
    ,ServerAliveCountMax=3
____COMMAND
)"

一些注意事项:

  • heredoc (____COMMAND) 中的印记 被引用。这确保扩展只发生在稍后,在 eval 被调用之后,而不是更早(当 heredoc 的内容本身被扩展时)。
  • 可以将多个命令传递给单个 sed 调用,每个调用使用 -e

但是不要。说真的。

为尾随的白色赋予语义意义space 是一种让阅读您的代码的其他人感到困惑的简单方法。正如 tabs-vs-space 问题涉及使用 Makefile 或 Python 的几代开发人员(在 PEP-8 仅在 spaces 上标准化之前),尾随 -space vs no-trailing-space 是一个语义上有意义的区别会严重混淆任何阅读你的代码的人,并会导致与自动格式验证的冲突(例如 git 当前版本中包含的功能当任何行在未来包含尾随白色时发出警告space)。