我在这方面遇到了很大困难。我正在尝试测试目录中是否存在与给定模式不匹配的文件,返回 true 或 false。
在这种情况下,$dir
其中的文件不以下划线开头,_
.我想我会尝试if [ -f $dir/!(_*) ]
,if [ $dir -f -name ! _* ]
或者
if ls $dir/!(_*) 1> /dev/null 2>&1
但它总是会说Too many arguments
或者syntax error near unexpected token "("
答案1
names=( "$dir"/[!_]* )
if [ -e "${names[0]}" ]; then
echo 'there are filenames that do not start with underscore'
printf '%d of them\n' "${#names[@]}"
fi
或者,对于/bin/sh
(bash
就此而言):
set -- "$dir"/[!_]*
if [ -e "$1" ]; then
echo 'there are filenames that do not start with underscore'
printf '%d of them\n' "$#"
fi
简而言之,扩展适当的通配模式并测试它是否与存在的内容匹配。
该[!_]
模式将匹配任何非下划线的字符。它类似于正则表达式 [^_]
,但文件名通配模式使用!
而不是^
否定字符类。
当模式不匹配,默认情况下,它将保持未展开状态,因此这就是为什么我们使用测试-e
来确保匹配列表中的第一项存在。我们无法真正测试返回列表的长度,因为如果长度为 1,它仍然可能没有匹配任何内容(除非您nullglob
在 中设置了 shell 选项bash
)。
如果你想测试的话,这会变得有点棘手常规文件具体来说,因为通配符模式匹配任何名称(目录、常规文件和所有其他类型的文件)。但这会做到这一点:
names=( "$dir"/[!_]* )
while [ "${#names[@]}" -gt 0 ] && [ ! -f "${names[0]}" ]; do
names=( "${names[@]:1}" )
done
if [ -f "${names[0]}" ]; then
echo 'there are at least one regular file here (or a symlink to one)'
echo 'whose filename does not start with underscore'
fi
或者,对于/bin/sh
:
set -- "$dir"/[!_]*
while [ "$#" -gt 0 ] && [ ! -f "$1" ]; do
shift
done
if [ -f "$1" ]; then
echo 'there are at least one regular file here (or a symlink to one)'
echo 'whose filename does not start with underscore'
fi
这种方法还可以检测到符号链接到名称不以下划线开头的常规文件。
循环对于移走我们可能匹配的非常规文件的任何文件名(例如目录名)是必要的。
在shell 中,您可以使用保证仅匹配常规文件的zsh
模式(如果它匹配任何内容)。"$dir"/[^_]*(.)
对于更复杂的模式,您可以天真地将匹配的项目数与 匹配的项目数进行比较*
。如果它们不同,则说明存在与复杂模式不匹配的名称。
在 中,您可以在使用 启用shell 选项后bash
使用扩展的通配模式。一般形式为.您仍然需要像上面那样检查扩展的结果,看看它是否扩展为任何内容。!(PATTERN)
extglob
shopt -s extglob
!(pattern1|pattern2|pattern3|etc)
名称不以下划线开头的示例可能会使用!(_*)
扩展的通配模式,但请注意,这!(_)*
不起作用,因为它会匹配每个可见名称,就像*
会一样。