【问题标题】:Bash parameter expansion in brackets not working as expected括号中的 Bash 参数扩展未按预期工作
【发布时间】:2015-01-01 11:51:37
【问题描述】:

我正在编写一个包含 find 命令的脚本,以在给定目录下搜索特定的源文件类型。一个示例调用是:

./find_them.sh --java --flex --xml dir1

上面的命令会在 dir1 下搜索 .java、.as 和 .xml 文件。

要手动执行此操作,我想出了以下查找命令:

find dir1 -type f -a \( -name "*.java" -o -name "*.as" -o -name "*.xml" \) 

当我在一个脚本中执行此操作时,我希望能够指定不同的文件集来搜索您,最终得到以下结构:

find_cmd_file_sets=$(decode_file_sets) # Assume this creates a string with the file sets e.g. -name "*.java" -o -name "*.as" etc
dirs=$(get_search_dirs) # assume this gives you the list of dirs to search, defaulting to the current directory

for dir in $dirs 
do
    find $dir -type f -a \( $find_cmd_file_sets \)
done

上述脚本的行为与预期不符,您执行了该脚本,并且 find 命令搅动了一段时间,然后没有返回任何结果。 我确定我创建的 decode_file_setsget_search_dirs 的等价物正在生成正确的结果。

一个更简单的例子,如果直接在 bash shell 中执行以下操作

file_sets=' -name "*.java" -o -name "*.as" '
find dir -type f -a \( $file_sets \) # Returns no result
# Executing result of below command directly in the shell returns correct result
echo find dir -type f -a \\\( $file_sets \\\) 

我不明白为什么 find 命令括号中的变量扩展会改变结果。如果有什么不同,我在 Windows 下使用 git-bash。

这真是令人沮丧。任何帮助将非常感激。最重要的是,我想了解为什么 $file_sets 的变量扩展会表现得如此。

【问题讨论】:

  • 为什么你认为变量中的引号与命令行中的引号相同?
  • 我不知道是什么让你认为我相信这一点。我正在使用单引号,因此当用作 find 的参数时应保留变量中的双引号
  • 我猜for循环有错误——应该是for dir in $dirs
  • @ahjmorton 参数中存储的双引号不用于引用*.java;他们按字面意思对待。你不能像这样嵌套句法引号。 bash 数组的引入正是出于这个原因。

标签: bash shell git-bash


【解决方案1】:

bash 数组被引入以允许这种嵌套引用:

file_sets=( -name "*.java" -o -name "*.as" )
find dir -type f -a \( "${file_sets[@]}" \)

【讨论】:

    【解决方案2】:

    TLDR:不要在 find_cmd_file_sets 变量中使用引号,并在调用 find 之前禁用路径名扩展 (set -f)。

    当您在变量内容中有“特殊”字符,然后您尝试不带引号扩展该变量时,bash 会将每个单词用带单引号的“特殊”字符括起来,例如:

    #!/usr/bin/env bash
    set -x
    VAR='abc "def"'
    echo $VAR
    

    输出是:

    + VAR='abc "def"'
    + echo abc '"def"'
    abc "def"
    

    如您所见,bash 用单引号将"def" 括起来。在您的情况下,对find 命令的调用变为:

    find ... -name '"*.java"' ...
    

    所以它会尝试查找以" 开头并以.java" 结尾的文件

    为了防止这种行为,您唯一能做的(我知道)就是在扩展变量时使用双引号,例如:

    #!/usr/bin/env bash
    set -x
    VAR='abc "def"'
    echo "$VAR"
    

    输出是:

    + VAR='abc "def"'
    + echo 'abc "def"'
    abc "def"
    

    您可能已经注意到,唯一的问题是现在整个变量都在引号中并且被视为单个参数。所以这在你的 find 命令中不起作用。

    剩下的唯一选择是不使用引号,无论是在变量内容中还是在扩展变量时。但是,当然,路径名扩展存在问题:

    #!/usr/bin/env bash
    set -x
    VAR='abc *.java'
    echo $VAR
    

    输出是:

    + VAR='abc *.java'
    + echo abc file1.java file2.java
    abc file1.java file2.java
    

    幸运的是,您可以使用 set -f 禁用路径名扩展:

    #!/usr/bin/env bash
    set -x
    VAR='abc *.java'
    set -f
    echo $VAR
    

    输出是:

    + VAR='abc *.java'
    + set -f
    + echo abc '*.java'
    abc *.java
    

    总而言之,以下应该有效:

    #!/usr/bin/env bash
    
    pattern='-name *.java'
    dir="my_project"
    set -f
    find "$dir" -type f -a \( $pattern \)
    

    【讨论】:

    • 接受这是对解决方案最完整的描述。
    【解决方案3】:

    希望这会奏效,它在 bash 上进行了测试。

    file_sets=' -name "*.java" -o -name "*.as" '
    command=`echo "find $dir -type f -a \( $file_sets \)"`
    
    eval $command
    

    【讨论】:

    • 这种方法确实有效,想知道为什么原始版本没有按预期运行
    • 不知道确切,但在我看来或至少我得出的结论是由于 find 命令功能将所有内容作为其参数,而不是将变量替换为其值。将变量视为 -a 选项的参数。
    • 命令替换中的echo 是多余的; command="find $dir -type -f -a \( $file_sets \)" 也可以。
    • 尽可能避免使用eval——而且几乎总是可以的。
    猜你喜欢
    • 2013-04-20
    • 1970-01-01
    • 2021-05-25
    • 1970-01-01
    • 2018-07-07
    • 1970-01-01
    • 2018-08-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多