如何防止循环中的特殊字符作为另一个命令的参数转义?

如何防止循环中的特殊字符作为另一个命令的参数转义?

我的输入文件(objects.lst)如下所示:

host.fqdn.com:/ 'host.fqdn.com [/]'
host.fqdn.com:/boot 'host.fqdn.com [/boot]'
host.fqdn.com:/tmp 'host.fqdn.com [/tmp]'
host.fqdn.com:/tmp '/tmp'
host.fqdn.com:/ '/usr/share'
host.fqdn.com:/var 'host.fqdn.com [/var]'

我想逐行循环这个文件,并需要每一行作为第三方软件的参数。

所以我尝试了类似的方法:

#!/bin/bash
set -x
while read -r line; do
/opt/report -filesystem "$line" -detail
done < objects.lst

这将被解释为

 /opt/report -filesystem 'host.fqdn.com:/ '\''host.fqdn.com [/]'\''' -detail

但应该是

/opt/report -filesystem host.fqdn.com:/ 'host.fqdn.com [/]'

我尝试了很多不同的事情,比如将参数保存在数组中、echo -E保存到变量中、各种不同的引用以及通过 for 循环,但没有任何东西能按照我需要的方式工作。

目前我的解决方法如下所示:

#!/bin/bash
set -x
while read -r line; do
echo "/opt/report -filesystem "${line}" -detail" | bash -
done < objects.lst

有没有另一种方法可以在不回显/管道到 bash - 的情况下使其工作?

我看到了一些 ${parameter@Q} 的例子(bash 4.4 可用),但我使用的是 bash 4.2。

我显然混淆了 set -x 输出,但我的问题仍然是一样的。我循环遍历objects.lst并需要与objects.lst中完全相同的整行作为参数。

所以如果我正确理解下面的评论:

while read -r line; do
/opt/report -filesystem "$line" -detail
done < objects.lst

应该与以下内容完全相同:

/opt/report -filesystem host.fqdn.com:/ 'host.fqdn.com [/]'

但事实并非如此。也许第 3 方软件是这里的问题(microfocus 数据保护器/omnidb cli 命令),因为在循环中,我从omnidb/report 得到“未找到对象”。

但如果我做一个

while read -r line; do
echo "/opt/report -filesystem "$line" -detail"
done < objects.lst

并从输出命令进行复制/粘贴,它可以正常工作并从omnidb/report命令打印正确的对象信息。

答案1

对于/opt/report -filesystem "$line" -detail,host.fqdn.com:/ 'host.fqdn.com [/]'逐字传递为命令的参数和set -x输出只是通过显示单引号中的值(shell 中的逐字引号)来告诉您这一点。

/opt/report -filesystem host.fqdn.com:/ 'host.fqdn.com [/]'

您将两个参数传递给命令:host.fqdn.com:/host.fqdn.com [/]。这是因为空格字符和单引号字符在 shell 语法中都是特殊的。

对于外壳,如上'...'所示逐字引号用于按字面意思传递文本,内部没有任何字符被特殊处理,shell 语法中的 SPC(本身没有被引号括起来时)用于分隔参数(或者更一般地说,shell 语法中的标记)

如果您想host.fqdn.com:/ 'host.fqdn.com [/]'按字面意思作为一个参数传递,如输出所示set -x,您需要类似 的东西'host.fqdn.com:/ '\''host.fqdn.com [/]'\''',尽管使用其他类型的引号"host.fqdn.com:/ 'host.fqdn.com [/]'"也可以。

$ printf '<%s>\n' 'host.fqdn.com:/ '\''host.fqdn.com [/]'\'''
<host.fqdn.com:/ 'host.fqdn.com [/]'>
$ printf '<%s>\n' "host.fqdn.com:/ 'host.fqdn.com [/]'"
<host.fqdn.com:/ 'host.fqdn.com [/]'>

如果您确实需要host.fqdn.com:/ 'host.fqdn.com [/]'将其解释为 shell 代码,而不是作为传递给命令的一个文字参数,那么您将需要对$line类似于管道的内容调用 shell 解释器bash(尽管您对双引号的使用是不正确)或eval

#!/bin/bash -
set -x
while IFS= read -r line; do
  eval "/opt/report -filesystem $line -detail"
done < objects.lst

然而,这意味着如果一行包含;reboot;$(reboot),则将调用该reboot命令。

如果您只想执行 shell 标记化和引用删除,而不想产生其他副作用(例如运行reboot上面的命令),则可以使用zsh具有运算符的命令:

#!/bin/zsh -
set -x
while IFS= read -r line; do
  /opt/report -filesystem "${(Q@)${(z)line}}" -detail
done < objects.lst

其中z导致标记化为zsh代码,并Q删除一层引号。例如,在这样的行上:

foo 'bar baz' $(echo x y)

它将传递 3 个参数:foobar baz$(echo x y)

答案2

根据您所写的内容,您似乎不需要将行作为单个参数传递给程序,而是作为两个参数传递:行中未加引号的部分作为一个,以下带引号的字符串作为另一个。

如果所有这些行都遵循该格式(并且没有任何行包含更多带引号的字符串),您可以在 Bash 中执行类似的操作:

#/bin/bash
while IFS=" " read -r a b; do
    b=${b%\'};
    b=${b#\'};
    /opt/report -filesystem "$a" "$b" -detail
done < objects.lst

在第一个空格上将行read分成两部分,并将各部分存储在a和中b。然后b=${b%\'}; b=${b#\'};删除单引号,然后两个字符串都作为不同的参数传递给/opt/report.

相关内容