将多个包含空格的包含传递给 grep

将多个包含空格的包含传递给 grep

我正在创建 PowerShell 和 Bash 脚本来标准化我们对前者Get-ChildItemSelect-String后者的grep.

作为 Bash 脚本的一部分,我采用命令行参数,解析文件名的逗号分隔值,包括 (复数),并试图将它们传递给 Grep--include=但遇到了各种困难。

最初,我尝试使用大括号扩展,但我放弃了这一点,因为(1)我无法让它工作,(2)我读到 grep 在技术上不支持这一点,正确的解决方案是使用多个包含反正。

现在,我正在尝试使用我已经成功工作的多个包含,但前提是该值不包含空格 - 如果包含空格,则脚本不会执行任何操作,大概是因为这些值没有被引用,但是尽管$grepstring在 shell 中复制并粘贴输出工作正常,但我无法使引用的版本正常工作。

这是该脚本的简化版本:

#!/bin/bash

include="$1"

if [[ $include == *","* ]]; then
    IFS=',' read -r -a includearray <<< "$include"
    
    includemulti=""
    
    firstloop="yes"
    
    for element in "${includearray[@]}"
    do
        # Trim leading and trailing whitespace
        element="${element## }"
        element="${element%% }"
        
        if [[ "$firstloop" == "yes" ]]; then
            firstloop="no"
            includemulti+="--include=$element"
            # includemulti+="--include=\"$element\""
            # includemulti+="--include='"$element"'"
            # includemulti+='--include="'$element'"'
            # includemulti+='--include="'"$element"'"'
            # includemulti+="--include='$element'"
        else
            includemulti+=" --include=$element"
            # includemulti+=" --include=\"$element\""
            # includemulti+=" --include='"$element"'"
            # includemulti+=' --include="'$element'"'
            # includemulti+=' --include="'"$element"'"'
            # includemulti+=" --include='$element'"
        fi
    done
    
    grep -ERins $includemulti "<pattern>" "<path>"
    
    grepstring="grep -ERins $includemulti \"<pattern>\" \"<path>\""
    echo $grepstring
else
    grep -ERins --include="$include" "<pattern>" "<path>"
fi

有效:

bash ~/test.sh 'Hello*.txt, *.sh'

bash ~/test.sh 'Hello W*.txt'

不起作用:

bash ~/test.sh 'Hello W*.txt, *.sh'

我开始想知道多次调用是否更容易,grep每次调用一个包含......

答案1

分析

当您的输入是: 时'Hello W*.txt, *.sh',空格将用作分隔符。所以你includemulti会被分成三个词:

  • --include=Hello
  • W*.txt
  • --include=*.sh

如果您set -x在命令之前添加脚本,grep您将确切地看到它是如何执行的并确认我所说的:

+ grep -ERins --include=Hello 'W*.txt' '--include=*.sh' <pattern> <path>

即使您更改行includemulti+=并在元素周围添加引号:

includemulti+=" --include=\"$element\""

它不会有帮助,因为bash仍然会使用空格作为单词分隔符:

+ grep -ERins '--include="Hello' 'W*.txt"' '--include="*.sh"' <pattern> <path>

解决方案1

一种需要在脚本中进行较少更改的可能解决方案是在元素周围添加引号,eval在命令之前添加内置命令grep。从bash手册页:

评估[精氨酸...]

参数被读取并连接在一起形成一个命令。然后该命令由 shell 读取并执行,其退出状态作为以下值返回评估。如果没有参数,或仅空参数,评估返回 0。

因此,如果您eval在 grep 命令之前添加,实际上就像运行:

bash -c 'grep -ERins --include="Hello W*.txt" --include="*.sh" <pattern> <path>'

set -x命令之前,grep您会看到两行,第二行是实际执行的行:

+ eval grep -ERins '--include="Hello' 'W*.txt"' '--include="*.sh"' <pattern> <path>
++ grep -ERins '--include=Hello W*.txt' '--include=*.sh' <pattern> <path>

解决方案2

这是更优雅的解决方案。您可以修改数组变量,而不是循环includearray

# Remove leading space from every element in the array
includearray=("${includearray[@]## }")
# Remove trailing space from every element in the array
includearray=("${includearray[@]%% }")
# Add --include= as the prefix of every element in the array                                                                                                                
includearray=("${includearray[@]/#/--include=}")

然后你的grep命令将如下所示:

grep -ERins "${includearray[@]}" <pattern> <path>

当您这样做时,不需要用引号将元素括起来,因为数组中的每个元素includearray都将被视为单个单词(无论它有多少空格)。

所以你的最终代码是:

#!/bin/bash

include="$1"

if [[ $include == *","* ]]; then
    IFS=',' read -r -a includearray <<< "$include"

    # Remove leading space from every element in the array
    includearray=("${includearray[@]## }")
    # Remove trailing space from every element in the array
    includearray=("${includearray[@]%% }")
    # Add --include= as the prefix of every element in the array                                                                                                                
    includearray=("${includearray[@]/#/--include=}")
    
    grep -ERins "${includearray[@]}" "<pattern>" "<path>"
else
    grep -ERins --include="$include" "<pattern>" "<path>"
fi

答案2

eval您可以使用数组来构建参数集,而不是构建字符串并应用于它,这很容易因引用、空格和其他问题而出现意外错误grep

#!/bin/bash
#
includesList="$1"
IFS=, read -ra includes <<<"$includesList"
# echo "! includesList=$includesList, includes=(${includes[@]}) !" >&2
[ 0 -eq "${#includes[@]}" ] && { echo "ERROR: Missing includes" >&2; exit 1; }

args=()
for include in "${includes[@]}"
do
    include="${include## }"
    include="${include%% }"
    args+=('--include' "$include")
done
# echo "! args=(${args[@]}) !" >&2

grep -EIins "${args[@]}" "<REpattern>" "<path>"

用法:

chmod a+rx code

./code 'trick*'
./code 'trick*,house,truck*.sh'
./code 'Hello W*.txt, *.sh'

如果取消注释这两个调试echo语句,您最初可能会因缺少引号而感到困惑。这些值本身不包含引号来标记它们,因此在最后一个示例中,您将得到以下结果:

! includesList=Hello W*.txt, *.sh, includes=(Hello W*.txt  *.sh) !
! args=(--include Hello W*.txt --include *.sh) !

因为这是一个简单的调试语句,所以无法直观地看到哪些值args()是哪些值。实际上有四个数组元素,--includex2 以及Hello W*.txt*.sh。更复杂的“打印数组”例程可以使用一种printf '%q'方法来输出适当引用的值,但我觉得这在这里太过分了:

{ printf '! args=('; printf "'%q' " "${args[@]}"; printf ') !\n'; } >&2

相关内容