如何转义变量中的特殊字符以在 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' ;

我可以为所有可能的组合制作一大堆别名,但是如果我可以使用变量来选择文件类型,那会容易得多,例如:

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' ;

我尝试了几种转义(*)的变体,但没有一种组合确实有效。 我能让它工作的唯一方法是关闭 Bash 中的通配,即set -f,然后打电话给我的"查找"装置,然后再次打开通货。

我想出的一种替代方法是定义一组函数(与我的别名findhgfindhgbldinsfindhgbldins同名),这些函数采用一个简单的参数,该参数用于选择我正在寻找的不同文件类型的case结构中,如下所示:

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

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

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

真的不可能做你想做的事而不感到不愉快。基本问题是,当您扩展一个没有双引号的变量时(例如findhgbldins $SEARCHALL),它对变量的值进行单词拆分和 glob 扩展,但不解释引号或转义,因此无法在变量的值中嵌入某些内容来抑制 glob 扩展(好吧,除非您使用无效的 glob 模式,但这也会阻止find正确匹配它们)。在它周围加上双引号 (findhgbldins "$SEARCHALL") 会抑制 glob 扩展,但它也会抑制单词拆分,您需要让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 && "$1" != "-"* ]]; then # if we were passed a type list (not just a find primitive starting with "-")
typestr="$1"
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: $0 [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 $1, so it doesn't get passed to `find` later!
fi
# Run `find`
find . ( -name .hg -o -name bld -o -name install ) -prune -o "${filetypes[@]}" "$@"
}

。如果需要,您还可以使用类似的方法来构建要修剪的目录列表。

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

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 选项,关闭通配,并将find命令(包括 $SEARCHALL、单词拆分但不 glob 扩展)传递给 noglob_helper,后者使用所有选项运行find命令,然后重新打开 glob 扩展(如果未在保存的 shell 选项中禁用),这样以后就不会搞砸您。这是一个完整的黑客,但它实际上应该有效。

最新更新