在Bash脚本中执行多行命令字符串

3

在文件中有一些多行命令,类似于以下内容:

cd /home/user; ls

在一个bash脚本中,我想要执行这些命令,并在最后一个命令中添加一些参数。例如:
cd /home/user; ls   -l *.png

我以为只需要这样做就可以了:

#!/bin/bash
commandLine="$(cat theFileWithCommandInside)   -l *.png"
$commandLine
exit 0

但它说:

/home/user;: No such file or directory

换句话说,";"字符不再表示“命令结束”,shell正在尝试在主目录中查找名为"user;"的文件夹...我试图用"&&"替换";",但结果相同。

不起作用...仍然出现相同的错误。但感谢您提出建议 :) - taalf
1
啊...抱歉,那是打错了,应该是 bash -c ... - Jason Hu
耶!非常感谢!您能否将其发布为答案,以便我可以验证它 :) - taalf
5个回答

2
关键在于:eval
这里是固定的脚本(请看第三行):
#!/bin/bash
commandLine="$(cat theFileWithCommandInside)   -l *.png"
eval $commandLine
exit 0

1
使用 eval也是相当危险的。最好采用其他答案中提到的方法。 - arco444
1
-1 eval 可能适用于您的特定需求,但对于一般问题和访问本网站寻找类似问题答案的用户来说,它是有害的。如果您需要打开脚本更改的大门,则应将其参数化 - 定义所需的接口并限制其支持的更改。 - kojiro
要明确的是,命令文件包含启动程序所需的命令。问题在于可以通过不同的方式进行安装。有时,一个命令就足够了。有时,需要引用一个文件。我不知道如何更好地“参数化”它,除了说“在某个文件中给我启动它的命令”。如前所述,这是为了工作而不是共享。 - taalf
2
@taalf "这是为工作而设计的,而不是为分享而设计的。" 尽管如此,StackOverflow 是一个公共资源,其目的是作为知识库服务。因此,您应该假设您在此发布的任何内容都将被视为已经被分享以供公众参考。因此,正如您所看到的,社区可能会建议或采取行动,反对可能适合您特定情况的东西。 - arco444
1
@arco444:我明白在与陌生人分享代码时安全性很重要,但有时安全性并不是那么重要,因为你知道将使用它的人。因此,“eval” 解决方案不仅适用于我的特定情况,而且适用于任何那些代码不会被分享给随意使用者的情况。这就是我的意思。 - taalf
显示剩余3条评论

2
为什么不在脚本中直接执行命令,而是“导入”它们?
#!/bin/bash
cd /home/user; ls -l *.png
exit 0

由于命令文件的内容将经常被多个用户修改,我希望避免修改我的Bash脚本。 - taalf
顺便说一下,我找到了解决方案...(上面) - taalf
@taalf 这正是为什么你应该避免使用 eval 的一个例子。任何用户都可以在文件中放置任何命令,它将被执行。这是一个潜在的安全风险... - arco444
1
@arco444 我看不出来如果不使用 eval 执行命令,这个安全风险会变小。如果需要运行像 OP 请求的代码,则必须信任其来源。 - Michael Jaros
1
你说得完全正确,但这并不是特定于eval的。不受信任的代码是安全风险,无论是直接执行、使用eval还是使用source。但是我现在看到你可能是在指Dragan的答案,当然这消除了安全风险。不幸的是,这种方法不符合(后来添加的)要求,即在贡献代码更改时不必修改主脚本。 - Michael Jaros
显示剩余2条评论

2
把命令封装到函数中:
function doLS() {
    cd user; ls $@
}
$@ 会展开为传递给函数的所有参数。如果您(或片段作者)添加了期望预定义数量的参数的函数,则可能会发现位置参数 $1$2 等更有用。
作为主脚本的维护者,您需要确保每个提供这种代码片段的人都提供了您的代码使用的“接口”(即他们的代码定义了您的程序调用的函数,他们的函数处理您的程序传递的参数)。
使用 source. 将函数导入到正在运行的 shell 中:
#!/bin/bash
source theFileWithCommandInside
doLS -l *.png
exit 0

我想对分号(;)这个主题添加一些想法:
换句话说,分号(;)字符不再表示“命令结束”:shell 正在尝试在主目录中找到一个名为“user;”的目录... ; 不像 C 语言风格的语言那样用于终止语句。相反,它用于在列表内部分隔应该按顺序执行的命令。例如,在子 shell 中执行两个命令:
( command1 ; command2 )

如果列表是一个组的一部分,它必须在后面跟着一个“;”符号。
{ command1 ; command2 ; }

在您的示例中,标记化和通配符(替换*)将不会被执行(正如您可能期望的那样),因此您的代码将无法成功运行。


你好,谢谢回答。但是如何将“source”命令与需要添加一些参数到行的最后一个命令混合使用呢? - taalf
哦,抱歉,我误解了你问题中的重要部分。我会编辑我的答案。 - Michael Jaros
@Michael_Jaros 谢谢! 最后一个问题:这个解决方案比“eval”更安全吗?还是说,为什么这个解决方案比“eval”更好呢? - taalf
在其他情况下,“eval==evil”仍然可能是正确的,因为它可以在开发人员没有意图的情况下执行代码。 - Michael Jaros
@Michael_Jaros 非常感谢您花费时间并解释 :) - taalf
显示剩余3条评论

2

你的问题的重点是执行存储在字符串中的命令。有成千上万种间接执行该命令的方法,但最终,Bash必须参与其中。

那么为什么不明确地调用Bash来完成这项工作呢?

bash -c "$commandLine"

文档说明:

-c 字符串

如果使用 -c 选项,则从字符串中读取命令。如果字符串后面有参数,它们将被分配给位置参数,从 $0 开始。

http://linux.die.net/man/1/bash


0

使用 <(...) 形式

sh <(sed 's/$/ *.png/' 带有命令的文件名)


bash <(sed 's/$/ *.png/' theFileWithCommandInside) 也可以运行... 这使用了进程替换功能。 sed 命令的输出被放入一个临时文件中 - 文件名作为脚本名称传递给 bash。 - atul

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