【问题标题】:Using exec on each file in a bash script对 bash 脚本中的每个文件使用 exec
【发布时间】:2015-04-30 02:16:46
【问题描述】:

我正在尝试为分配编写基本的find 命令(不使用find)。现在我有一组文件我想exec 一些东西。语法如下所示:

-exec /bin/mv {} ~/.TRASH

我有一个名为current 的数组,其中包含所有文件。我的数组只包含/bin/mv{}~/.TRASH(因为我将-exec 移出)并且位于一个名为arguments 的数组中。

我需要它,以便将每个文件都传递到 {} 并在其上调用 exec。

我想我应该像这样使用sed 替换{} 的内容(在for循环中):

for i in "${current[@]}"; do
    sed "s@$i@{}" 
    #exec stuff?
done

我如何执行其他参数?

【问题讨论】:

  • @anubhava 抱歉,我的意思是我正在编写基本的find 命令,但我不允许使用find
  • @anubhava 我需要用我从current 传入的文件替换字符串{},然后我需要exec mv 将该文件放入垃圾箱。所以我用那个文件替换{} 的每个实例,然后在那个文件上执行一个命令。我不知道,如果它应该是这样工作的,或者是否有更简单的方法。
  • 很抱歉,如果不提供完全清晰的信息,您将无法在此处获得答案。你的输入文件中有文字{} 吗?
  • @anubhava 对此感到抱歉:(我没有。大多数文件只是普通文件,即test.txt。所以如果我的current中有test1.txttest2.txt数组,并且用户传入-exec /bin/mv {} ~/.TRASH,那么它看起来像这样:for i in current[0] ... exec mv test1.txt ~/.TRASH ... for i in current[1] ... exec mv test2.txt ~/.TRASH 它遍历每个文件并执行mv 命令

标签: arrays linux bash shell sed


【解决方案1】:

你可以这样:

cmd='-exec /bin/mv {} ~/.TRASH'    
current=(test1.txt test2.txt)

for f in "${current[@]}"; do 
   eval $(sed "s/{}/$f/;s/-exec //" <<< "$cmd")
done

使用eval 命令要非常小心,因为如果输入来自不受信任的来源,它可能会做一些令人讨厌的事情。


这是避免eval 的尝试(感谢@gniourf_gniourf 的cmets):

current=( test1.txt test2.txt )
arguments=( "/bin/mv" "{}" ~/.TRASH )

for f in "${current[@]}"; do
   "${arguments[@]/\{\}/$f}"
done

【讨论】:

  • 不幸的是,这只会输出/bin/mv很多次:(
  • 我希望它将文件移动到垃圾箱。此外,如果您执行 /bin/ls ,它只会输出 /bin/ls/ 多次。这是有原因的吗?
  • 解决问题后实际执行 exec $cmd。谢谢你让我走上正轨:)
  • 这在很多方面都非常危险! 你真的不应该调用eval文件名应该像用户输入一样处理:具有潜在的破坏性。切勿在您无法控制的代码上调用 eval
  • 是的,我完全同意你的看法,并在答案中写到。问题是 OP 写道:用户传入-exec /bin/mv {} ~/.TRASH 现在我们在字符串中有-exec /bin/mv {} ~/.TRASH,然后需要运行命令。 OP 自己通过exec 运行的尝试将带来相同的风险。
【解决方案2】:

你很幸运,你的设计还不错,你的论点在一个数组中。

但你肯定不想使用eval

所以,如果我理解正确的话,你有一个文件数组:

current=( [0]='/path/to/file'1 [1]='/path/to/file2' ... )

还有一个参数数组:

arguments=( [0]='/bin/mv' [1]='{}' [2]='/home/alex/.TRASH' )

注意这里没有波浪号,因为 Bash 已经扩展了它。

执行你想要的:

for i in "${current[@]}"; do
    ( "${arguments[@]//'{}'/"$i"}" )
done

注意引号。

这会将arguments字段中所有出现的{}替换为$i的扩展,即文件名1,并执行此扩展。请注意,数组的每个字段都将扩展为一个参数(感谢引号),因此所有这些对于空格、全局字符等都是非常安全的。这确实是最安全和最正确的方法。使用eval 的每个解决方案都具有潜在的危险和破坏性(除非使用了一些特殊的引用,例如使用printf '%q',但这会使该方法变得毫无用处)。顺便说一句,使用sed也至少有两个方面的问题。

请注意,我将扩展包含在子外壳中,因此用户不可能干扰您的脚本。没有这个,并且取决于你的完整脚本是如何编写的,很容易通过(恶意地)更改一些变量的东西或cd-ing 在其他地方使你的脚本中断。出于明显的安全原因,在子 shell 或单独的进程(例如,单独的 bash 或 sh 实例,但这会增加额外的开销)中运行您的参数确实是强制性的!

请注意,与一些更标准的 find 版本相比,用户可以通过您的脚本直接访问所有 Bash 内置函数(这是一个巨大的专业人士)2


1 注意POSIX clearly specifies that this behavior is implementation-defined:

如果 utility_nameargument 字符串包含两个字符“{}”,而不仅仅是两个字符“{}”,则实现定义是否 find 替换这两个字符或直接使用字符串。

在我们的例子中,我们选择用文件名替换所有出现的{}。这与例如 GNU find 的行为相同。来自man find

字符串{} 被当前文件名替换为正在处理的当前文件名,它出现在命令的参数中,而不仅仅是在单独的参数中,就像在某些版本的 find 中一样。


2POSIX also specifies that calling builtins is not defined:

如果 utility_name 命名了任何特殊的内置实用程序(请参阅Special Built-In Utilities),则结果未定义。

在你的情况下,它的定义很明确!


我认为尝试(在纯 Bash 中)实现 find 命令是一个很棒的练习,应该会教给你很多东西……尤其是如果你得到相关的反馈。我很乐意查看您的代码!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-15
    • 2021-02-28
    • 2011-05-09
    • 2011-04-27
    相关资源
    最近更新 更多