我的输入文件(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 个参数:foo
、bar 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
.