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

我想出的一个替代方法是定义一组函数(与我的别名 findhgfindhgbldinsfindhgbldins 具有相同的名称),它们采用在 @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

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

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

【问题讨论】:

你需要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 中提供命令行参数的主要内容,如果未能解决你的问题,请参考以下文章

Bash Shell中的通配符及转义字符的使用

如何转义任意字符串以用作 Bash 中的命令行参数?

Linux中的特殊字符

如何转义mysql jdbc连接字符串中的特殊字符

如何正确设置 PHP 环境变量以在 Git Bash 中运行命令

转义字符串以在 XML 中使用