如何转义变量中的特殊字符以在 bash 中提供命令行参数
Posted
技术标签:
【中文标题】如何转义变量中的特殊字符以在 bash 中提供命令行参数【英文标题】:How to escape special characters in a variable to provide commandline arguments in bash 【发布时间】:2017-04-18 03:03:42 【问题描述】:我经常使用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 中的 globbing,即 set -f
,然后调用我的“查找”装置,然后再次打开 globbing。
我想出的一个替代方法是定义一组函数(与我的别名 findhg
、findhgbldins
和 findhgbldins
具有相同的名称),它们采用在 @987654335 中使用的简单参数@ 选择我正在寻找的不同文件类型的结构,例如:
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
我的问题是:是否可以将这些类型的参数作为变量传递给命令?
或者是否有不同的方法来实现相同的目标,即结合命令(findhg
、findhgbld
、findhgbldins
)和单个参数来创建大量搜索组合?
【问题讨论】:
你需要egrep
吗? fgrep
会更简单吗?
【参考方案1】:
没有不愉快的心情是不可能做你想做的事的。基本问题是,当您扩展一个没有双引号的变量时(例如findhgbldins $SEARCHALL
),它会对变量的值进行分词和全局扩展,但不会解释引号或转义,因此无法嵌入某些内容在变量的值中以抑制 glob 扩展(好吧,除非您使用无效的 glob 模式,但这会使find
也无法正确匹配它们)。在它周围加上双引号 (findhgbldins "$SEARCHALL"
) 会抑制全局扩展,但它也会抑制分词,您需要让 find
正确解释表达式。您可以完全关闭全局扩展(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[@]" "$@"
...如果您愿意,也可以使用类似的方法来构建要修剪的目录列表。
正如我所说,那将是我的首选。但是,如果您真的想使用变量方法,则有一个技巧(我的意思是技巧)。它被称为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选项,关闭通配符,并将find
命令(包括$SEARCHALL,分词但不扩展通配符)传递给noglob_helper,它使用所有选项运行 find
命令,然后重新打开 glob 扩展(如果在保存的 shell 选项中没有禁用它),这样以后就不会搞砸了。这是一个完整的 hack,但它应该确实有效。
【讨论】:
您的功能有效,谢谢!我唯一需要更改的是在文件类型前面添加-name
。例如。 filetypes+=(-o -name '*.[ch]');;
天啊!当我在发布之前只进行某种测试时,就会发生这种情况。无论如何,我很高兴它有用;我已经把它修好了。以上是关于如何转义变量中的特殊字符以在 bash 中提供命令行参数的主要内容,如果未能解决你的问题,请参考以下文章