简体   繁体   English

find命令在提示符下起作用,而不是在bash脚本中起作用-通过变量传递多个参数

[英]find command works on prompt, not in bash script - pass multiple arguments by variable

I've searched around questions with similar issues but haven't found one that quite fits my situation. 我搜索了类似问题的问题,但没有找到一个适合我情况的问题。

Below is a very brief script that demonstrates the problem I'm facing: 下面是一个非常简短的脚本,演示了我所面临的问题:

#!/bin/bash

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

Basically, we need to search inside a folder, but only in certain of its subfolders. 基本上,我们需要在一个文件夹内进行搜索,但只能在其某些子文件夹中进行搜索。 In my longer script, includeString gets built from an array. 在我的较长脚本中,includeString是从数组构建的。 For this demo, I kept things simple. 对于此演示,我保持简单。

Basically, when I run the script, it doesn't find anything. 基本上,当我运行脚本时,它什么也找不到。 No errors, but also no hits. 没有错误,也没有命中。 If I manually run the find command, it works. 如果我手动运行find命令,它将起作用。 If I remove ( $includeString ) it also works, though obviously it doesn't limit itself to the folders I want. 如果我删除了($ includeString),它也可以工作,尽管显然不会将其自身限制在我想要的文件夹中。

So why would the same command work from the command line but not from the bash script? 那么,为什么相同的命令可以从命令行运行而不能从bash脚本运行呢? What is it about passing in $includeString that way that causes it to fail? 以这种方式传递$ includeString导致失败的原因是什么?

You're running into an issue with how the shell handles variable expansion. 您遇到了外壳程序如何处理变量扩展的问题。 In your script: 在脚本中:

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

This results in find looking for files where -wholename matches the literal string './public_html/*' . 这将导致在-wholename匹配文字字符串 './public_html/*'文件中find文件。 That is, a filename that contains single quotes. 即,包含单引号的文件名 Since you don't have any whitespace in your paths, the easiest solution here would be to just drop the single quotes: 由于您的路径中没有空格,因此,最简单的解决方案是删除单引号:

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

Unfortunately, you'll probably get bitten by wildcard expansion here (the shell will attempt to expand the wildcards before find sees them). 不幸的是,您可能会在这里被通配符扩展所咬(外壳会在find通配符之前尝试扩展通配符)。

But as Etan pointed out in his comment, this appears to be needlessly complex; 但是,正如伊坦(Etan)在评论中指出的那样,这似乎是不必要的复杂。 you can simply do: 您可以简单地执行以下操作:

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

If you want to store a list of arguments and expand it later, the correct form to do that with is an array, not a string: 如果要存储参数列表并在以后展开,则使用此参数的正确形式是数组,而不是字符串:

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

This is covered in detail in BashFAQ #50 . BashFAQ#50对此进行了详细介绍。

Note: As Etan points out in a comment, the better solution in this case may be to reformulate the find command, but passing multiple arguments via variable(s) is a technique worth exploring in general. 注意:正如Etan在评论中指出的那样,在这种情况下,更好的解决方案可能是重新编写find命令,但是一般通过变量传递多个参数是一种值得探索的技术。

tl;dr : tl; dr

The problem is not specific to find , but to how the shell parses command lines . 问题不是特定于find ,而是Shell如何解析命令行的问题

  • Quote characters embedded in variable values are treated as literals : They are neither recognized as argument-boundary delimiters nor are they removed after parsing, so you cannot use a string variable with embedded quoting to pass multiple arguments simply by directly using it as part of a command . 嵌入在变量值中的引号字符视为文字 :它们既不被识别为参数边界定界符,也不在解析后将其删除 ,因此, 不能将嵌入引号的字符串变量与 直接用作参数的一部分的传递给多个参数一起 使用命令

  • To robustly pass multiple arguments stored in a variable , 为了可靠地传递存储在变量中的多个参数

    • use array variables in shells that support them ( bash , ksh , zsh ) - see below. 在支持它们的shell中使用数组变量bashkshzsh )-参见下文。
    • otherwise, for POSIX compliance, use xargs - see below. 否则, 为了符合POSIX,请使用xargs参见下文。

Robust solutions : 强大的解决方案

Note: The solutions assume presence of the following script , let's call it echoArgs , which prints the arguments passed to it in diagnostic form: 注意:这些解决方案假定存在以下脚本 ,我们称其为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

Further, assume that the equivalent of the following command is to be executed : 此外,假定将执行以下命令等效项

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

with all arguments but the last passed by variable . 除了最后一个 变量以外的所有参数

Thus, the expected outcome is: 因此,预期结果是:

[one]
[two three]
[*]
[last]
  • Using an array variable ( bash , ksh , zsh ): 使用数组变量( 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
  • Using xargs - a POSIX-compliant alternative: 使用xargs 兼容POSIX的替代方法:

POSIX utility xargs , unlike the shell itself, is capable of recognized quoted strings embedded in a string: POSIX实用xargs ,不像外壳本身, 能够嵌入在一个字符串认可引号字符串:

# 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

Note that {} is a freely chosen placeholder that allows you to control where in the resulting command line the arguments provided by xargs go. 请注意, {}是一个自由选择的占位符,它允许您控制xargs提供的参数在结果命令行中的位置。
If all xarg -provided arguments go last , there is no need to use -J at all. 如果所有xarg参数都xarg最后 ,则根本不需要使用-J

For the sake of completeness: eval can also be used to parse quoted strings embedded in another string, but eval is a security risk: arbitrary commands could end up getting executed; 为了完整起见: eval也可以用于解析嵌入在另一个字符串中的带引号的字符串,但是eval存在安全风险:任意命令最终可能会被执行; given the safe solutions discussed above, there is no need to use eval . 鉴于以上讨论的安全解决方案,因此无需使用eval

Finally, Charles Duffy mentions another safe alternative in a comment, which, however, requires more coding: encapsulate the command to invoke in a shell function , pass the variable arguments as separate arguments to the function, then manipulate the all-arguments array $@ inside the function to supplement the fixed arguments (using set ), and invoke the command with "$@" . 最后,查尔斯·达菲(Charles Duffy)在注释中提到了另一种安全的替代方法,但是,它需要更多的编码:将要调用的命令封装在shell函数中 ,将变量参数作为单独的参数传递给该函数,然后操纵所有参数数组$@在函数内部以补充固定参数(使用set ),并使用"$@"调用命令。


Explanation of the shell's string-handling issues involved: 有关shell的字符串处理问题的说明:

  • When you assign a string to a variable , embedded quote characters become part of the string : 将字符串分配给变量时嵌入的引号字符将成为字符串的一部分

     var='one "two three" *' 
  • $var now literally contains one "two three" * , ie, the following 4 - instead of the intended 3 - words, separated by a space each: $var现在从字面上包含one "two three" * ,即以下4个单词(而不是预期的3个单词),每个单词之间用空格分隔:

    • one
    • "two -- " is part of the word itself ! "two - "单词本身的一部分!
    • three" -- " is part of the word itself ! three" - "单词本身的一部分!
    • *
  • When you use $var unquoted as part of an argument list, the above breakdown into 4 words is exactly what the shell does initially - a process called word splitting . 当您使用$var 引号作为参数列表的一部分时, 上述分解成4个单词的过程正是Shell 最初要做的事情 -这个过程称为单词拆分 Note that if you were to double-quote the variable reference ( "$var" ), the entire string would always become a single argument. 请注意,如果要对变量引用( "$var"双引号 ,则整个字符串将始终成为单个参数。

    • Because $var is expanded to its value, one of the so-called parameter expansions , the shell does NOT attempt to recognize embedded quotes inside that value as marking argument boundaries - this only works with quote characters specified literally , as a direct part of the command line (assuming these quote characters aren't themselves quoted). 由于$var扩展为其值,即所谓的参数扩展之一因此外壳程序不会尝试将该值内的嵌入引号识别为标记参数边界 -这仅适用于按字面指定的引号字符,这是该变量直接部分。命令行(假设这些引号字符本身没有被引号)。
    • Similarly, only such directly specified quote characters are removed by the shell before passing the enclosed string to the command being invoked - a process called quote removal . 类似地,在将封闭的字符串传递给被调用的命令之前,shell只会删除这些直接指定的引号字符-这个过程称为quote remove
  • However, the shell additionally applies pathname expansion (globbing) to the resulting 4 words, so any of the words that happen to match filenames will expand to the matching filenames. 但是,shell 还会对结果的4个单词应用路径名扩展 (globbing),因此,碰巧与文件名匹配的任何单词都将扩展为匹配的文件名。

  • In short: the quote characters in $var 's value are neither recognized as argument-boundary delimiters nor are they removed after parsing. 简而言之: $var值中的引号字符既不识别为参数边界定界符,也不在解析后将其删除 Additionally, the words in $var 's value are subject to pathname expansion . 此外, $var值中的单词会受到路径名扩展的影响

  • This means that the only way to pass multiple arguments is to leave them unquoted inside the variable value (and also leave the reference to that variable unquoted ), which: 这意味着传递多个参数的唯一方法是在变量值内不加引号 (并且对变量的引用也不要加引号 ),这是:

    • won't work with values with embedded spaces or shell metacharacters 不适用于带有嵌入式空格或外壳元字符的值
    • invariably subjects the values to pathname expansion 始终使值经受路径名扩展

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM