多行 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,然后只需键入包含在此页眉页脚中的命令,就好像我不必担心缩进一样。
我知道我可以通过分配给一个变量然后稍后使用结果来解决问题,但我不想那样。我希望能够在一处输入换行缩进命令 。
基本上。具有这些特征:
- 实际 EOL 保留前 space 秒行尾
- 删除行尾
- 新行前导 space 已删除
这可能吗?
编辑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)。
我正在运行宁这段代码:
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,然后只需键入包含在此页眉页脚中的命令,就好像我不必担心缩进一样。 我知道我可以通过分配给一个变量然后稍后使用结果来解决问题,但我不想那样。我希望能够在一处输入换行缩进命令 。 基本上。具有这些特征:
- 实际 EOL 保留前 space 秒行尾
- 删除行尾
- 新行前导 space 已删除
这可能吗?
编辑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)。