find命令适用于提示,而不是bash脚本 - 按变量传递多个参数

时间:2015-05-06 18:09:35

标签: linux bash shell scripting quoting

我已经搜索过类似问题的问题但未找到一个非常符合我情况的问题。

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

#!/bin/bash

includeString="-wholename './public_html/*' -o -wholename './config/*'"
find . \( $includeString \) -type f -mtime -7 -print

基本上,我们需要在文件夹内搜索,但只能在其某些子文件夹中搜索。在我的较长脚本中,includeString是从数组构建的。对于这个演示,我保持简单。

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

那么为什么同一命令可以从命令行运行而不是从bash脚本运行?传递$ includeString会导致它失败的原因是什么?

3 个答案:

答案 0 :(得分:2)

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

includeString="-wholename './public_html/*' -o -wholename './config/*'"
find . \( $includeString \) -type f -mtime -7 -print

这会导致find查找-wholename文字字符串 './public_html/*'匹配的文件。也就是说, filename 包含单引号。由于您的路径中没有任何空格,因此最简单的解决方案是删除单引号:

includeString="-wholename ./public_html/* -o -wholename ./config/*"
find . \( $includeString \) -type f -mtime -7 -print

不幸的是,你可能会被这里的通配符扩展所困扰(shell会在find看到它们之前尝试扩展通配符。)

但正如Etan在他的评论中指出的那样,这似乎是不必要的复杂;你可以这样做:

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

答案 1 :(得分:1)

如果你想存储一个参数列表并在以后扩展它,那么使用的正确形式是一个数组,而不是一个字符串:

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

BashFAQ #50中详细介绍了这一点。

答案 2 :(得分:1)

注意:正如Etan在评论中指出的那样,这个案例中更好的解决方案可能是重新构造find命令,但是通过变量传递多个参数这是一项值得探索的技术。

<强> TL;博士

问题并非特定于find ,而是 shell 如何解析命令行

  • 嵌入在变量值中的引号字符 视为文字 :它们既不被识别为参数边界分隔符解析后 多个参数只需直接使用它作为命令的一部分

  • 强力传递存储在变量中的多个参数

    • 在支持它们的shell中使用 数组变量bashkshzsh) - 请参阅下文。
    • 否则,对于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

此外,假设要执行以下命令的等效项

echoArgs one 'two three' '*' last  # note the *literal* '*' - no globbing

所有参数变量 传递的最后一次

因此,预期结果是:

[one]
[two three]
[*]
[last]
  • 使用数组变量(bashkshzsh):
# Assign the arguments to *individual elements* of *array* args.
# The resulting array looks like this: [0]="one" [1]="two three" [2]="*"
args=( one 'two three' '*' )

# Safely pass these arguments - note the need to *double-quote* the array reference:
echoArgs "${args[@]}" last
  • 使用xargs - 符合 POSIX标准的替代方案:

POSIX实用程序xargs,与shell本身不同, 能够识别嵌入字符串中的引用字符串:

# Store the arguments as *single string* with *embedded quoting*.
args="one 'two three' '*'"

# Let *xargs* parse the embedded quoted strings correctly.
# Note the need to double-quote $args.
echo "$args" | xargs -J {} echoArgs {} last

请注意,{}是一个自由选择的占位符,允许您控制xargs提供的参数在结果命令行中的位置。 如果所有xarg - 提供的参数都是 last ,则根本不需要使用-J

为了完整起见:eval也可用于解析嵌入在另一个字符串中的引用字符串,但eval存在安全风险:任意命令最终可能会被执行;鉴于上面讨论的安全解决方案,无需使用eval

最后,Charles Duffy在评论中提到了另一个安全的替代方案,然而,这需要更多编码:封装命令以在 shell函数中调用,将变量参数作为单独的参数传递到函数,然后操作函数内的所有参数数组$@以补充固定参数(使用set),并使用"$@"调用命令。

解释shell所涉及的字符串处理问题:

  • 当您将字符串分配给变量 时,嵌入式引号字符将成为字符串的一部分

    var='one "two three" *' 
    
  • $var现在字面包含one "two three" *,即以下 4 - 而不是预期的3字,由...分隔每个空间:

    • one
    • "two - "字本身的一部分
    • three" - "字本身的一部分
    • *
  • 当您使用$var 不加引号作为参数列表的一部分时,上面分解为4个单词正是shell最初所做的 - 一个名为 分词 的过程。 请注意,如果您要双引号变量引用("$var"),则整个字符串将始终成为单个参数。

    • 由于$var 已扩展到其值,因此其中一个所谓的parameter expansions shell不会尝试将该值中的嵌入引号识别为标记参数边界 - 这仅适用于指定字面的引号字符,作为命令行的直接部分(假设这些引号字符不是自己的引述)。
    • 同样,在将附带的字符串传递给被调用的命令之前,shell仅删除了这些直接指定的引号字符 - 一个名为 引用删除 的进程。
  • 但是,shell 还会将路径名扩展(globbing)应用于生成的4个单词,因此恰好匹配文件名的任何单词都将扩展为匹配的文件名。

  • 简而言之: $var&#39}中的引号字符既不会被识别为参数 - 边界分隔符也不会被删除< / em>解析后。此外,$var的值中的字词受路径名扩展的约束。

  • 这意味着传递多个参数的唯一方法是将它们不带引号留在变量值中(并将引用保留到该变量未加引号),其中:

    • 不使用嵌入空格或shell元字符的值
    • 总是对值进行路径名扩展