如何转义变量中的特殊字符以在 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。
我想到的一个替代方法是定义一组函数(与我的别名 findhg
、findhgbldins
和 findhgbldins
同名),它采用简单的在 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
我的问题是:是否可以将这些类型的参数作为变量传递给命令?
或者是否有不同的方法来实现相同的目的,即结合命令 (findhg
、findhgbld
、findhgbldins
) 和单个参数来创建一个大的搜索的组合数 ?
想做自己想做的事而不感到不愉快是不可能的。基本问题是当你扩展一个没有双引号的变量时(例如 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,但它应该确实有效。
我经常使用 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。
我想到的一个替代方法是定义一组函数(与我的别名 findhg
、findhgbldins
和 findhgbldins
同名),它采用简单的在 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
我的问题是:是否可以将这些类型的参数作为变量传递给命令?
或者是否有不同的方法来实现相同的目的,即结合命令 (findhg
、findhgbld
、findhgbldins
) 和单个参数来创建一个大的搜索的组合数 ?
想做自己想做的事而不感到不愉快是不可能的。基本问题是当你扩展一个没有双引号的变量时(例如 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,但它应该确实有效。