从文件内容中使用带引号的命令行参数

6

我有一个包含命令行参数的文件,我想将其传递给另一个脚本。

但是这个文件包含像"param 1" param2 param3这样的元素。

我们将带参数的文件称为test.tmp,脚本文件称为script.sh

如果我执行以下操作:

script.sh -p `cat test.tmp` -other_params 1 2 3
p 后接收到以下内容:
  1. "param
  2. 1"
  3. param2
  4. param3
但我想要的是:
  1. param 1
  2. param2
  3. param3
有什么好主意吗?
小提醒:假设 script.sh 不可修改。解决方案必须在 shell 中执行。

在理想的情况下,test.tmp 应该以 NUL 分隔的形式存储 -- 这是唯一可以存储所有可能值而不需要解析的语法。(安全地解析 shell 引用而不带来安全风险,比如允许扩展,是非常困难的 -- 除非你要将工作交给像 xargs 这样的东西,但即使这样它的行为也不完全与实际的 bash 解析兼容)。如果你有机会鼓励编写 script.sh 的人重新考虑,我建议你这么做。 - Charles Duffy
Tom 建议的 mapfile -t 方法也是一个合理的做法,如果您保证您的参数永远不需要包含字面换行符。 - Charles Duffy
顺便提一下,请查看http://mywiki.wooledge.org/BashFAQ/050及其链接(即WordSplitting和Arguments页面)以了解默认行为发生的原因。 - Charles Duffy
4个回答

6

假设:使用这种方法,test.tmp需要每行包含一个参数。

您可以使用xargs,并使用换行符进行分隔:

cat test.tmp | xargs -d '\n' script.sh -p

这个完美运行。我可以控制test.tmp,但不能控制script.sh。谢谢。 - Marc Simon
1
一个需要注意的地方是:如果你有一个非常长的参数列表, xargs 会多次使用不同子集运行 script.sh 而不是直接失败。在某些情况下,这可能是不可取的。 - Charles Duffy

6
你可以使用 eval 将命令包装起来:
eval "script.sh -p `cat test.tmp` -other_params 1 2 3"

$ cat test.tmp 
"params 1" param2 param3

$ cat script.sh 
#!/bin/bash
echo $1
echo $2
echo $3
echo $4
echo $5
echo $6

$ eval "./script.sh -p `cat test.tmp` other_params 1 2 3"
-p
params 1
param2
param3
other_params
1

2
呃,除非你信任test.tmp的内容,否则请不要这样做;如果它包含类似于$(rm -rf .)的命令,那你就会遭殃了。 - Charles Duffy
是的,一般来说这是一个好观点,但是在这种情况下,OP说他有test.tmp的控制权,所以没有危险。 - Nathan Wilson
控制文件格式和控制文件内容是两回事。如果我拥有编写 foo.txt 的脚本,但该脚本的输出是基于运行时输入编写的,那么除非我非常小心,否则我的输出仍然不能被信任为非恶意的。 - Charles Duffy
我经常讲一个故事,我的前雇主因为在清理脚本的编写上粗心大意而丢失了数TB的备份。毕竟,他们控制着写入该目录的每个程序,所以为什么要担心呢?然后,其中一个程序的新版本被编写成具有缓冲区溢出,可以将随机数据喷射到文件名中。一个文件名被写入其中,其中包含一个带空格的通配符,然后就发生了一些(不是很好笑的)事情。这样的错误可能性很低,但当发生时后果可能是灾难性的;为什么要冒不必要的风险呢? - Charles Duffy

2
请按照以下方式排列您的文件:

param 1
param2
param3

然后像这样将其读入数组:
mapfile -t params < file

然后像这样调用您的脚本:
script.sh -p "${params[@]}" -other_params 1 2 3

这种方法的优点是仅使用内置的bash命令,而不需要eval。为了一行代码实现所有功能,您可以使用:
mapfile -t params < file && script.sh -p "${params[@]}" -other_params 1 2 3

例如,如果第一个命令成功执行,使用&&来执行第二个命令。

1
使用带有Perl正则表达式的grep:
IFS=$'\n'; ./script.sh -p $(grep -woP '((?<=")[^"]*(?="))|([\S]+)' test.tmp)

例子:

script.sh:

#!/bin/bash
echo "$1"
echo "$2"
echo "$3"
echo "$4"
...

输出:

-p
param 1
param2
param3
...

注意:这将改变当前 shell(您运行这些命令的地方)的 IFS

这对我的问题没有用。当展开 $(...) 的值时,第一个参数将是 "param",第二个参数将是 "1"。而我需要第一个参数是 "param 1"。 - Marc Simon
这种方法将(引用的)grep输出作为第二个参数发送到script.sh,而OP希望将param2param3作为单独的参数发送。 - Eugeniu Rosca
@MarcSimon,您能否检查一下这个新编辑是否符合您的要求? - Jahid

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接