Linux find命令在提示符下工作,而不是在bash脚本中-按变量传递多个参数

Linux find命令在提示符下工作,而不是在bash脚本中-按变量传递多个参数,linux,bash,shell,scripting,quoting,Linux,Bash,Shell,Scripting,Quoting,我搜索了很多类似的问题,但没有找到一个非常适合我的情况 下面是一个非常简短的脚本,演示了我面临的问题: #!/bin/bash includeString="-wholename './public_html/*' -o -wholename './config/*'" find . \( $includeString \) -type f -mtime -7 -print 基本上,我们需要在文件夹内搜索,但只在其某些子文件夹中搜索。在我较长的脚本中,includeString是从数组中构建

我搜索了很多类似的问题,但没有找到一个非常适合我的情况

下面是一个非常简短的脚本,演示了我面临的问题:

#!/bin/bash

includeString="-wholename './public_html/*' -o -wholename './config/*'"
find . \( $includeString \) -type f -mtime -7 -print
基本上,我们需要在文件夹内搜索,但只在其某些子文件夹中搜索。在我较长的脚本中,includeString是从数组中构建的。对于这个演示,我保持了简单

基本上,当我运行脚本时,它找不到任何东西。没有错误,但也没有命中。如果我手动运行find命令,它就会工作。如果我删除$includeString,它也可以工作,尽管它显然不局限于我想要的文件夹


那么,为什么相同的命令可以从命令行工作,而不能从bash脚本工作呢?以这种方式传入$includeString导致它失败的原因是什么?

您遇到了shell如何处理变量扩展的问题。在脚本中:

includeString="-wholename './public_html/*' -o -wholename './config/*'"
find . \( $includeString \) -type f -mtime -7 -print
这将导致查找-wholename与文本字符串“./public\u html/*”匹配的文件。即,包含单引号的文件名。由于路径中没有任何空格,因此这里最简单的解决方案是删除单引号:

includeString="-wholename ./public_html/* -o -wholename ./config/*"
find . \( $includeString \) -type f -mtime -7 -print
不幸的是,您可能会被通配符扩展所咬,在find看到通配符之前,shell将尝试扩展通配符

但正如埃坦在评论中指出的,这似乎是不必要的复杂;您可以简单地执行以下操作:

find ./public_html ./config -type f -mtime -7 -print

您遇到了shell如何处理变量扩展的问题。在脚本中:

includeString="-wholename './public_html/*' -o -wholename './config/*'"
find . \( $includeString \) -type f -mtime -7 -print
这将导致查找-wholename与文本字符串“./public\u html/*”匹配的文件。即,包含单引号的文件名。由于路径中没有任何空格,因此这里最简单的解决方案是删除单引号:

includeString="-wholename ./public_html/* -o -wholename ./config/*"
find . \( $includeString \) -type f -mtime -7 -print
不幸的是,您可能会被通配符扩展所咬,在find看到通配符之前,shell将尝试扩展通配符

但正如埃坦在评论中指出的,这似乎是不必要的复杂;您可以简单地执行以下操作:

find ./public_html ./config -type f -mtime -7 -print

如果要存储参数列表并在以后展开,正确的格式是数组,而不是字符串:

includeArgs=( -wholename './public_html/*' -o -wholename './config/*' )
find . '(' "${includeArgs[@]}" ')' -type f -mtime -7 -print

这将在中详细介绍。

如果要存储参数列表并在以后展开,正确的格式是数组,而不是字符串:

includeArgs=( -wholename './public_html/*' -o -wholename './config/*' )
find . '(' "${includeArgs[@]}" ')' -type f -mtime -7 -print
注:正如Etan在评论中指出的,在这种情况下更好的解决方案可能是重新格式化find命令,但是通过变量传递多个参数是一种值得探索的技术

tl;博士:

问题不在于查找,而在于shell如何解析命令行

嵌入在变量值中的引号字符被视为文字:它们既不会被识别为参数边界分隔符,也不会在解析后被删除,因此不能将带有嵌入引号的字符串变量作为命令的一部分直接使用来传递多个参数

要可靠地传递存储在变量中的多个参数

在支持bash、ksh、zsh的shell中使用数组变量-请参见下文。 否则,为了符合POSIX,请使用xargs-请参阅下文。 稳健的解决方案:

注意:解决方案假定存在以下脚本,我们称之为echoArgs,它以诊断形式打印传递给它的参数:

#!/usr/bin/env bash
for arg; do     # loop over all arguments
  echo "[$arg]" # print each argument enclosed in [] so as to see its boundaries
done
此外,假设将执行与以下命令等效的命令:

具有除最后一个由变量传递的参数以外的所有参数

因此,预期结果是:

[one]
[two three]
[*]
[last]
使用数组变量bash、ksh、zsh: 将参数分配给*数组*参数的*单个元素*。 结果数组如下所示:[0]=1[1]=2-3[2]=* args=一个“两个三个”*' 安全地传递这些参数-注意需要*双引号*数组引用: echoArgs${args[@]}last 使用xargs-兼容POSIX的替代方案: POSIX实用程序xargs与shell本身不同,它能够识别嵌入字符串中的带引号的字符串:

includeArgs=( -wholename './public_html/*' -o -wholename './config/*' )
find . '(' "${includeArgs[@]}" ')' -type f -mtime -7 -print
将参数存储为带有*嵌入引号*的*单个字符串*。 args=一个“两个三个”*' 让*xargs*正确解析嵌入的带引号的字符串。 注意需要双引号$args。 echo$args | xargs-J{}echoArgs{}last 请注意,{}是一个自由选择的占位符,它允许您控制xargs提供的参数在生成的命令行中的位置。 如果所有xarg提供的参数都放在最后,则根本不需要使用-J

为了完整性:eval还可以用于解析嵌入另一个字符串中的带引号的字符串,但eval存在安全风险:任意命令可能最终被执行;鉴于上述安全解决方案,无需使用eval

最后,Charles Duffy在评论中提到了另一个安全的替代方案,但是这需要更多的编码:将要调用的命令封装在shell函数pas中 将变量参数设置为函数的独立参数,然后使用set操作函数中的all arguments数组$@以补充固定参数,并使用$@调用命令

解释壳牌公司涉及的字符串处理问题:

将字符串指定给变量时,嵌入的引号字符将成为字符串的一部分:

$var现在字面上包含1-2-3*,即以下4个字,而不是预期的3个字,每个字之间用空格分隔:

一 二是单词本身的一部分! 三是单词本身的一部分! * 当您使用$var unquoted作为参数列表的一部分时,上面的分解为4个单词正是shell最初所做的——一个称为单词拆分的过程。请注意,如果要双引号引用变量引用$var,则整个字符串将始终成为单个参数

由于$var被扩展到它的值,即所谓的值之一,shell不会尝试将该值内的嵌入引号识别为标记参数边界-这只适用于按字面指定的引号字符,作为命令行的直接部分,假设这些引号字符本身没有被引用。 类似地,在将包含的字符串传递给被调用的命令之前,shell只删除这些直接指定的引号字符,这个过程称为引号删除。 但是,shell还将路径名扩展globbing应用于生成的4个字,因此任何与文件名匹配的字都将扩展为匹配的文件名

简而言之,$var值中的引号字符既不能识别为参数边界分隔符,也不能在解析后删除。此外,$var值中的单词受路径名扩展的影响

这意味着传递多个参数的唯一方法是在变量值中不加引号,同时也不加引号地保留对该变量的引用,即:

无法使用带有嵌入空格或shell元字符的值 始终使值服从路径名扩展 注意:正如Etan在一篇评论中指出的,在这种情况下更好的解决方案可能是重新格式化find命令,但是通过变量传递多个参数是一种值得探索的技术

tl;博士:

问题不在于查找,而在于shell如何解析命令行

嵌入在变量值中的引号字符被视为文字:它们既不会被识别为参数边界分隔符,也不会在解析后被删除,因此不能将带有嵌入引号的字符串变量作为命令的一部分直接使用来传递多个参数

要可靠地传递存储在变量中的多个参数

在支持bash、ksh、zsh的shell中使用数组变量-请参见下文。 否则,为了符合POSIX,请使用xargs-请参阅下文。 稳健的解决方案:

注意:解决方案假定存在以下脚本,我们称之为echoArgs,它以诊断形式打印传递给它的参数:

#!/usr/bin/env bash
for arg; do     # loop over all arguments
  echo "[$arg]" # print each argument enclosed in [] so as to see its boundaries
done
此外,假设将执行与以下命令等效的命令:

具有除最后一个由变量传递的参数以外的所有参数

因此,预期结果是:

[one]
[two three]
[*]
[last]
使用数组变量bash、ksh、zsh: 将参数分配给*数组*参数的*单个元素*。 结果数组如下所示:[0]=1[1]=2-3[2]=* args=一个“两个三个”*' 安全地传递这些参数-注意需要*双引号*数组引用: echoArgs${args[@]}last 使用xargs-兼容POSIX的替代方案: POSIX实用程序xargs与shell本身不同,它能够识别嵌入字符串中的带引号的字符串:

includeArgs=( -wholename './public_html/*' -o -wholename './config/*' )
find . '(' "${includeArgs[@]}" ')' -type f -mtime -7 -print
将参数存储为带有*嵌入引号*的*单个字符串*。 args=一个“两个三个”*' 让*xargs*正确解析嵌入的带引号的字符串。 注意需要双引号$args。 echo$args | xargs-J{}echoArgs{}last 请注意,{}是一个自由选择的占位符,它允许您控制xargs提供的参数在生成的命令行中的位置。 如果所有xarg提供的参数都放在最后,则根本不需要使用-J

为了完整性:eval还可以用于解析嵌入另一个字符串中的带引号的字符串,但eval存在安全风险:任意命令可能最终被执行;鉴于上述安全解决方案,无需使用eval

最后,Charles Duffy在一条注释中提到了另一个安全的替代方案,但这需要更多的编码:封装要在shell函数中调用的命令,将变量参数作为单独的参数传递给函数,然后操纵函数中的all arguments数组$@以使用set补充固定参数,并使用$@调用该命令

解释壳牌公司涉及的字符串处理问题:

将字符串指定给变量时,嵌入的引号字符将成为字符串的一部分: $var现在字面上包含1-2-3*,即以下4个字,而不是预期的3个字,每个字之间用空格分隔:

一 二是单词本身的一部分! 三是单词本身的一部分! * 当您使用$var unquoted作为参数列表的一部分时,上面的分解为4个单词正是shell最初所做的——一个称为单词拆分的过程。请注意,如果要双引号引用变量引用$var,则整个字符串将始终成为单个参数

由于$var被扩展到它的值,即所谓的值之一,shell不会尝试将该值内的嵌入引号识别为标记参数边界-这只适用于按字面指定的引号字符,作为命令行的直接部分,假设这些引号字符本身没有被引用。 类似地,在将包含的字符串传递给被调用的命令之前,shell只删除这些直接指定的引号字符,这个过程称为引号删除。 但是,shell还将路径名扩展globbing应用于生成的4个字,因此任何与文件名匹配的字都将扩展为匹配的文件名

简而言之,$var值中的引号字符既不能识别为参数边界分隔符,也不能在解析后删除。此外,$var值中的单词受路径名扩展的影响

这意味着传递多个参数的唯一方法是在变量值中不加引号,同时也不加引号地保留对该变量的引用,即:

无法使用带有嵌入空格或shell元字符的值 始终使值服从路径名扩展

您的示例命令扩展为:find-wholename'/public_html/*'-o-wholename'/config/*'-type f-mtime-7-print。问:这就是你想要的吗?当您将逐字复制/粘贴到命令行时,它是否起作用?为什么要使用的目录是。然后使用-wholename进行过滤,而不仅仅是使用find./public\u html./config-键入f-mtime 7-print?顺便说一句,这里的基本问题在BashFAQ 50中介绍:您的示例命令扩展为:find-wholename'/public_html/*'-o-wholename'/config/*'-type f-mtime-7-print。问:这就是你想要的吗?当您将逐字复制/粘贴到命令行时,它是否起作用?为什么要使用的目录是。然后使用-wholename进行过滤,而不仅仅是使用find./public\u html./config-键入f-mtime 7-print?顺便说一句,这里的基本问题在BashFAQ 50中讨论过:当我删除单引号时,我会出现以下错误:查找:路径必须在表达式之前:./public\u html/上载,但当我采用其他方法时(仅包括路径),它会起作用。我以为我已经尝试过这种方法,但它不起作用,但显然我错了。谢谢很好的提示是单引号将成为文件名的一部分,但@ChrisRoberts刚刚指出的问题是,$includeString的非引号使用使其中的单词受到shell的预先路径名扩展的影响,如果至少有两个匹配项,则会中断find命令。P.S.:换句话说:通常不可能通过单个字符串变量可靠地传递多个参数。对,这就是为什么尝试处理多个引用级别很棘手的原因。您可能会使用eval解决这个问题,但我认为放弃引号和通配符的解决方案更好;考虑到后者,eval总是可以避免的,以避免它的安全问题。当我删除单引号时,我得到了这样一个错误:find:path必须在expression:./public\u html/uploads之前,但当我采用其他方法时(仅包括路径),它就可以工作。我以为我已经尝试过这种方法,但它不起作用,但显然我错了。谢谢很好的提示是单引号将成为文件名的一部分,但@ChrisRoberts刚刚指出的问题是,$includeString的非引号使用使其中的单词受到shell的预先路径名扩展的影响,如果至少有两个匹配项,则会中断find命令。P.S.:换句话说:通常不可能通过单个字符串变量可靠地传递多个参数。对,这就是为什么尝试处理多个引用级别很棘手的原因。您可能会使用eval解决这个问题,但我认为放弃引号和通配符的解决方案更好;考虑到后者,eval总是可以避免的,以避免其安全问题。++我个人很谨慎地推荐没有GNU扩展的xargs-0或-d,很明显,这两种扩展都可以防止在这里故意使用它的行为,因为它很容易被错误地不安全地使用,但是在这个答案中有很多很好的建议和解释。我很好奇-你在这方面工作了多久
是“当我更简洁的回答被采纳时,它似乎还未被公开。我感谢你的赞美,”CharlesDuffy说。我认为xargs比eval更可取,在安全性方面,我只建议在阵列不可用时使用它。我没有记录我在这方面工作了多长时间,但我想说,这包括我自己的实验和加深我自己的理解。顺便说一下,纯POSIX shell的另一个选择是将代码封装在函数中,并重写或操作其中的$。诚然,每个函数的作用域中只有一个数组,但这已经足够了。@CharlesDuffy:在函数中操纵$@可能超出了这个答案的范围,但我已经添加了它作为脚注;谢谢。我个人对推荐没有GNU扩展名的xargs很谨慎-0或-d,很明显,这两个扩展名都可以防止故意使用它的行为,因为它很容易被错误地不安全地使用,但是在这个答案中有很多很好的建议和解释。我很好奇-你在这方面工作了多久?“当我更简洁的回答被采纳时,它似乎还未被公开。我感谢你的赞美,”CharlesDuffy说。我认为xargs比eval更可取,在安全性方面,我只建议在阵列不可用时使用它。我没有记录我在这方面工作了多长时间,但我想说,这包括我自己的实验和加深我自己的理解。顺便说一下,纯POSIX shell的另一个选择是将代码封装在函数中,并重写或操作其中的$。诚然,每个函数的作用域中只有一个数组,但这已经足够了。@CharlesDuffy:在函数中操纵$@可能超出了这个答案的范围,但我已经添加了它作为脚注;谢谢