假设有以下命令
search /home/user proc .h .c .txt ...
我正在使用find
命令构建一个脚本,以获取以给定名称开头并以给定扩展名之一结尾的所有文件。
我已经成功地使用循环构建了它:
directory=$1
fileName=$2
fileExtensions=""
for arg in "$@"
do
#skipping first and second argument
if [ $arg = $1 -o $arg = $2 ]; then
continue;
fi
fileExtensions+="${arg//.}\|"
done
#removing the last '|', otherwise regex parser error occurs
fileExtensions=${fileExtensions::-1}
find $directory -name "$fileName*" -regex ".*\.\($fileExtensions)"
有没有更优雅的方法使用正则表达式来实现这一点?
感谢您的帮助!
答案1
该脚本可以简化为:
directory=$1
fileName=$2
shift 2
a="$*" b="${*#.}"
(( ${#a} - ${#b} - $# )) && echo "some extension(s) is(are) missing a leading dot." >&2
fileExtensions="$(IFS=\|; echo "${*#.}")"
find "$directory" -name "$fileName*" -regextype posix-egrep -regex ".*\.($fileExtensions)$"
默认情况下,find 接受 emacs 类型的正则表达式,要使用该类型需要几个反斜杠,例如\|
.可以通过使用不同类型的正则表达式来避免这种情况(如上所述)。
其中${*#.}
删除前导点(如果存在)并将所有剩余的“位置参数”与 IFS 的第一个字符的值连接起来,该值被设置|
为用于子 shell 的执行。
只需要一个变量赋值和一个“参数扩展”,就足够了。
编辑
用于(( ${#a} - ${#b} - $# ))
检查参数列表中提供的所有扩展名是否以点开头。
${#a}
连接的所有参数的字符数 ( ) ( a=$*
)
应等于连接的所有参数的
字符数 ( ${#b}
)(删除一个前导点) ( b=${*#.}
)
加上
参数的数量 ( $#
)。
${#a} == ${#b} + $#
当且仅当所有参数都有一个前导点。
作为算术测试:
(( ${#a} - (${#b} + $#) ))
或者也可以:
(( ${#a} - ${#b} - $# )) && echo "missing leading dot(s)."
编辑二
命令行参数中给出的扩展列表在此处处理:
fileExtensions="$(IFS=\|; echo "${*#.}")"
从内到外,它的工作原理如下:
$* # This generates a string of all positional arguments using IFS.
从LESS=+'/Special Parameters' man bash
:
也就是说,“$*”相当于“$1c$2c...”,其中 c 是 IFS 变量值的第一个字符。
然后我们使用“参数扩展”来切割每个位置参数的前面:
${parameter#word} # used as ${*# } above.
从LESS=+/'parameter#word' man bash
:
如果参数是@或*,则依次对每个位置参数应用模式删除操作,并且扩展是结果列表。
word
之前扩展中的 被设置为一个点,.
从而删除了所有位置参数前面的点。
由于此时的 IFS 以一个|
字符开头,因此该字符用于构建一个字符串,并|
作为参数列表的分隔符,该参数列表在前面被去掉了一个点。
该字符串被提供给命令 echo 以使其打印。
但在执行 echo 命令之前,该变量$IFS
被设置为|
.
它被包装在命令执行中$(…)
(创建一个子 shell,该子 shell 在结束时会忘记对 IFS 的更改)。
然后我们将字符串分配给一个变量:
fileExtensions="$(IFS=\|; echo "${*#.}")"
简而言之:转化.c .h .txt
为c|h|txt
.
答案2
这似乎正则表达式应该可以工作,而我能想到的唯一其他选择是类似的东西,-name "proc*" \( -name "*.c" -o -name "*.h" ... \)
可能并没有那么简单。
一些可以做的小事:
1)您可以使用andshift
来代替循环内的条件。$1
$2
2)你可以结合-name
和-regex
,这样最后你就得到了-regex ".*/proc.*\.(c|h|txt)"
3)如果想让代码更短,可以用IFS
and连接位置参数$*
:
$ set .c .h
$ IFS='|'
$ echo "(${*/./})"
(c|h)
(我并不是说任何关于可读性或优雅的事情。)