我有两个变量,作为命令行参数传递给 bash 函数,包含文件类型扩展名的排除和包含信息,使用逗号作为分隔符。
excl="el"
excl="el,htm"
incl="texi,org"
我想使用excl
并incl
解析 的排除和包含选项grep
。
excl="el,htm"
incl="texi,org"
grep -hir --exclude=\*.{el,htm} --include=\*.{org,texi} "$@"
excl="el"
incl="texi,org"
grep -hir --exclude=\*.el --include=\*.{org,texi} "$@"
答案1
您确实需要使用数组来跟踪单独的字符串。使用单个字符串保存多个值使得无法使用带有嵌入分隔符的后缀的代码(例如,v
CVS 和 RCS 文件具有的后缀)。
exclude=( .el .htm )
include=( .texi .org )
opts=( -h -i -r )
for ext in "${exclude[@]}"; do
opts+=( --exclude="*$ext" )
done
for ext in "${include[@]}"; do
opts+=( --include="*$ext" )
done
grep "${opts[@]}" "$@"
这会将您的文件名后缀存储在两个数组中,exclude
并且include
.然后它迭代两个数组的元素,将适当的选项添加到名为 的第三个数组中opts
。然后,在对 的调用中使用第三个数组grep
。
扩展数组时使用的双引号(例如,例如)"${opts[@]}"
可确保每个单独的数组元素都被双引号引用,并且不会被 shell 进一步拆分或通配。
作为一个函数,它将包含和排除的文件名后缀列表作为两个单独的参数:
call_grep () {
local -n include="$1"
local -n exclude="$2"
shift 2
local opts=( -h -i -r )
local ext
for ext in "${exclude[@]}"; do
opts+=( --exclude="*$ext" )
done
for ext in "${include[@]}"; do
opts+=( --include="*$ext" )
done
grep "${opts[@]}" "$@"
}
脚本的主要部分:
excl=( .el .htm )
incl=( .texi .org )
call_grep incl excl more arguments here
这将函数设置call_grep
为获取两个数组的名称。第一个数组是要包含的文件名后缀数组,第二个数组是要排除的后缀数组。该函数接收这些数组的名称并使用它们来设置两个本地名称引用变量。从第三个参数开始grep
按原样传递。
再次,但使用真正的命令行解析call_grep
:
call_grep () {
OPTIND=1
local ext opt
local opts=( -h -i -r )
while getopts 'i:e:' opt; do
case $opt in
i)
local -n include="$OPTARG"
for ext in "${include[@]}"; do
opts+=( --include="*$ext" )
done
;;
e)
local -n exclude="$OPTARG"
for ext in "${exclude[@]}"; do
opts+=( --exclude="*$ext" )
done
;;
*)
echo 'Error in option parsing' >&2
exit 1
esac
done
shift "$(( OPTIND - 1 ))"
grep "${opts[@]}" "$@"
}
该函数现在接受一个-i
和一个-e
参数(两者都是可选的)。每个选项的参数应该是包含要包含或排除的文件名后缀的数组的名称。
你可以用它作为
excl=( .el .htm )
incl=( .texi .org )
call_grep -i incl -e excl -- more arguments here
您需要使用--
将函数的参数与应直接传递给 的参数分隔开grep
。
如果您想要的只是一种简化的调用方式grep
,其中不提及 shell 模式或长选项:
call_grep () {
OPTIND=1
while getopts 'i:e:' opt; do
case $opt in
i) opts+=( --include="*$OPTARG" ) ;;
e) opts+=( --exclude="*$OPTARG" ) ;;
*) echo 'error' >&2; exit 1
esac
done
shift "$(( OPTIND - 1 ))"
grep "${opts[@]}" "$@"
}
您可以-i suffix
重复使用来包含多个后缀,并且类似地用于排除后缀。例如,
call_grep -i .texi -e .el -e .htm -i .org -- other arguments for grep here
或者
call_grep -i{.texi,.org} -e{.htm,.el} -- more here for grep
答案2
如果您想获取表单中的扩展名列表el,htm
,您可能会受到诱惑执行类似的操作以使用大括号扩展:
eval 'echo grep -hir --exclude=*.{'"$excl"'} "$@"'
但除了通常的警告之外eval
,例如不带引号的分号 in$excl
会通过终止命令来扰乱语法,如果仅包含一个扩展名,或者根本不包含扩展名,grep
它也将不起作用,因为和是$excl
{foo}
{}
不是作为大括号扩展进行处理。所以,让我们忘记大括号扩展。
我们实际上最终会在数组中构建参数列表,就像上面 Kusalananda 的答案一样,以及有条件地将参数传递给脚本
保持逗号作为分隔符,以逗号分隔的简单方法是使用read -a
.它生成一个数组,我们需要另一个数组来构建选项列表。
excl="el,htm"
IFS=, read -r -a exts <<< "$excl"
opts=()
for ext in "${exts[@]}"; do
opts+=(--exclude="*.$ext")
done
grep -hir "${opts[@]}" "$@"
正如您在评论中注意到的那样,像 RCS 这样的扩展,v
在这里会成为问题,其他任何不以点开头的扩展也会成为问题。如果您仍然想将扩展名作为一个字符串给出,您可以转而接受冒号、分号或空格作为分隔符,并要求用户在需要时明确输入点,因此例如 with 作为:
分隔符:
excl=".html:,v"
IFS=: read -r -a exts <<< "$excl"
opts=()
for ext in "${exts[@]}"; do
opts+=(--exclude="*$ext")
done
grep -hir "${opts[@]}" "$@"
当然,无论您选择使用什么字符作为分隔符,都不能成为扩展名的一部分,但分号和冒号可能比逗号更罕见。