如何转义变量中的特殊字符以在 bash 中提供命令行参数

How to escape special characters in a variable to provide commandline arguments in bash

我经常使用 find 在巨大的源代码树中搜索文件和符号。如果我不限制目录和文件类型,在文件中搜索一个符号需要几分钟。 (我已经将源代码树安装在 SSD 上,这将搜索时间减半。)

我有几个别名来限制我想搜索的目录,例如:

alias findhg='find . -name .hg -prune -o' 
alias findhgbld='find . \( -name .hg -o -name bld \) -prune -o' 
alias findhgbldins='find . \( -name .hg -o -name bld -o -name install \) -prune -o'

然后我也限制了文件类型,例如:

findhgbldins \( -name '*.cmake' -o -name '*.txt' -o -name '*.[hc]' -o -name '*.py' -o -name '*.cpp' \) 

但有时我只想检查 cmake 文件中的符号:

findhgbldins \( -name '*.cmake' -o -name '*.txt' \) -exec egrep -H 'pattern' \;

我可以为所有可能的组合制作一大堆别名,但如果我可以使用变量 select 文件类型,例如:

export SEARCHALL="\( -name '*.cmake' -o -name '*.txt' -o -name '*.[hc]' -o -name '*.py' -o -name '*.cpp' \)"
export SEARCHSRC="\( -name '*.[hc]' -o -name '*.cpp' \)"

然后调用:

findhgbldins $SEARCHALL -exec egrep -H 'pattern' \;

我尝试了几种转义变体 \(*),但没有一种组合有效。 我可以让它工作的唯一方法是在调用我的 'find'-装置之前关闭 Bash 中的 globbing,即 set -f,然后再次打开 globbing。

我想到的一个替代方法是定义一组函数(与我的别名 findhgfindhgbldinsfindhgbldins 同名),它采用简单的在 case 结构中使用的参数 select 是我要查找的不同文件类型,例如:

findhg {
    case  in
        '1' )
            find <many file arguments> ;;
        '2' )
            find <other file arguments> ;;
        ...
    esac
}

findhgbld {
    case  in
        '1' )
            find <many file arguments> ;;
        '2' )
            find <other file arguments> ;;
        ...
    esac
}

etcetera

我的问题是:是否可以将这些类型的参数作为变量传递给命令?

或者是否有不同的方法来实现相同的目的,即结合命令 (findhgfindhgbldfindhgbldins) 和单个参数来创建一个大的搜索的组合数 ?

想做自己想做的事而不感到不愉快是不可能的。基本问题是当你扩展一个没有双引号的变量时(例如 findhgbldins $SEARCHALL),它会对变量的值进行分词和全局扩展,但不解释引号或转义符,所以没有办法在变量的值中嵌入一些东西以抑制 glob 扩展(好吧,除非你使用无效的 glob 模式,但这也会使 find 无法正确匹配它们)。在它周围加上双引号 (findhgbldins "$SEARCHALL") 可以抑制全局扩展,但它也可以抑制单词拆分,您需要让 find 正确解释表达式。您可以完全关闭 glob 扩展(set -f,正如您提到的),但这会为所有内容关闭它,而不仅仅是这个变量。

一个可行的方法(但使用起来会很烦人)是将搜索选项放在数组中而不是普通变量中,例如:

SEARCHALL=( \( -name '*.cmake' -o -name '*.txt' -o -name '*.[hc]' -o -name '*.py' -o -name '*.cpp' \) )
findhgbldins "${SEARCHALL[@]}" -exec egrep -H 'pattern' \;

但是要使用它需要输入很多内容(而且您确实需要每个引号、方括号、大括号等才能使数组正确展开)。帮助不大。

我的首选是构建一个函数,将其第一个参数解释为要匹配的文件类型列表(例如 findhgbldins mct -exec egrep -H 'pattern' \; 可能会找到 make/cmake、c/h 和文本文件)。像这样:

findhgbldins() {
filetypes=()
if [[ $# -ge 1 && "" != "-"* ]]; then # if we were passed a type list (not just a find primitive starting with "-")
    typestr=""
    while [[ "${#typestr}" -gt 0 ]]; do
        case "${typestr:0:1}" in # this looks at the first char of typestr
            c) filetypes+=(-o -name '*.[ch]');;
            C) filetypes+=(-o -name '*.cpp');;
            m) filetypes+=(-o -name '*.make' -o '*.cmake');;
            p) filetypes+=(-o -name '*.py');;
            t) filetypes+=(-o -name '*.txt');;
            ?) echo "Usage: [=11=] [cCmpt] [find options]" >2
               exit ;;
        esac
        typestr="${typestr:1}" # remove first character, so we can process the remainder
    done
    # Note: at this point filetypes will be something like '-o' -name '*.txt' -o -name '*.[ch]'
    # To use it with find, we need to remove the first element (`-o`), and add parens
    filetypes=( \( "${filetypes[@]:1}" \) )
    shift # and get rid of , so it doesn't get passed to `find` later!
fi

# Run `find`
find . \( -name .hg -o -name bld -o -name install \) -prune -o "${filetypes[@]}" "$@"
}

...如果您愿意,您也可以使用类似的方法来构建 p运行e 的目录列表。

正如我所说,这是我的首选。但是如果你真的想使用变量方法,有一个技巧(我的意思是技巧)。它被称为 magic alias,它利用了别名在通配符之前扩展,但函数在之后处理的事实,并且对组合做了一些完全不自然的事情。像这样:

alias findhgbldins='shopts="$SHELLOPTS"; set -f; noglob_helper find . \( -name .hg -o -name bld -o -name install \) -prune -o'
noglob_helper() {
    "$@"
    case "$shopts" in
        *noglob*) ;;
        *) set +f ;;
    esac
    unset shopts
}
export SEARCHALL="( -name *.cmake -o -name *.txt -o -name *.[hc] -o -name *.py -o -name *.cpp )"

然后如果你 运行 findhgbldins $SEARCHALL -exec egrep -H 'pattern' \;,它扩展别名,记录当前 shell 选项,关闭 globbing,并传递 find 命令(包括 $ SEARCHALL, word-split but not glob-expanded) to noglob_helper, 运行s find 命令的所有选项,然后重新打开 glob 扩展(如果它没有在保存的 shell 选项),这样以后就不会把你搞得一团糟。这是一个完整的 hack,但它应该确实有效。