多个表达式 - 找到合适的工具?

时间:2018-05-24 01:36:38

标签: shell unix find

我有很多目录包含来自一组程序的输出和数据文件。每个目录都有类似的内容,我的深度更高4级。我的目标是识别在任何子目录中具有两个核心文件的所有目录以及大小大于1k的给定名称模式的日志文件。日志文件不会与核心文件位于同一子目录中。

我可以单独编写查找适用于每个目标的命令,但我所有组合表达式的尝试都无法产生任何结果。

第一个命令:

find \( \( -path "./SESS*" -name "log_snap_*" \) \( -size +1k\) \)

第二个命令:

find \( -path "./SESS" -regex "*core.[0-9]+\(.gz)*" \)

如何编写一个标识符合两个条件的目录的测试?

1 个答案:

答案 0 :(得分:2)

对这个问题的解释不止一个 - 请参阅下面的答案,基于另一个答案。

假设你的意思是“要么”标准......

考虑以下设置:

files_empty=(
  SESS/log_snap_1234  # ignored because not more than 1k in size
  SESS/ignoreme       # ignored because not matching either pattern 
  SESS/core.13.gz     # expected to be in results
  SESS/core.13        # expected to be in results
)
files_full=(
  SESS/log_snap_2345  # expected to be in results
)

{ tempdir=$(mktemp -d /tmp/test.XXXXXX) && cd "$tempdir"; } || exit
mkdir -p SESS bad
touch "${files_empty[@]}"
for f in "${files_full[@]}"; do
  dd if=/dev/zero of="$f" bs=1k count=2
done

如果在使用上述内容创建测试环境后,我们在GNU find中运行以下命令:

find ./SESS \
   '(' '(' -name 'log_snap_*' -size +1k ')' \
    -o '(' -regextype posix-extended -regex ".*core[.][0-9]+([.]gz)?" ')' \
   ')' -print

......我们正确地得到了结果:

./SESS/log_snap_2345
./SESS/core.13
./SESS/core.13.gz

那么,改变了什么?

  • 当您只能修改起始位置时,请勿使用-path来过滤搜索的位置。当您运行find . -path './SESS/*'时,会在.下搜索无处不在,但只要它们与./SESS/*不匹配就会丢弃结果;与仅首先搜索您关心的目录相比,这是非常低效的。
  • 使用-o指定OR条件。
  • 当你想要的是后续测试之间的AND时,没有必要对运算符或显式-a进行分组,因为这是隐式行为。
  • 明确指定最终操作(例如-print)是非常好的形式。在当前情况下并非完全强制要求,但在其他常见情况下(例如使用-prune时) 是强制性的;因此养成习惯会减少出错的余地。
  • 不允许正则表达式以*开头,因为*表示“之前项目为零或更多”。在正则表达式的开头,没有先前的项,所以这个结构没有意义。
  • 在正则表达式中,显式句点应写为[.],因为裸.表示“任何字符之一”。

如果你真的意味着“两个”标准......

对于这部分,我们实际上不需要进入find。一个警告:我故意避免正确处理文件名包含文字换行符的情况。这可能发生。忽略它并不理想。

无论如何,将两个shell函数作为两个不同find命令输出的替身:

find1_cmd() {
  printf '%s\n' \
    SESS/session_one/log_snap_1234 \
    SESS/session_one/log_snap_4567 \
    SESS/session_three/log_snap_8901
}

find2_cmd() {
  printf '%s\n' \
    SESS/session_one/core.1234.gz
    SESS/session_four/core.5678.gz
}

我们只能找到两者中存在的目录,如下所示:

prep() {
  while IFS= read -r line; do
    printf '%s\n' "${line%/*}"  # remove the filename, leaving only the directory
  done | sort -u                # sort and uniq-ify the results
}

comm -12 <(find1_cmd | prep) <(find2_cmd | prep)

当然,您可以使用任何其他方式替换while循环来删除文件名并仅保留目录。重要的是我们(1)生成每个命令找到的唯一目录的排序列表; (2)使用comm排除一个或另一个唯一的目录。

有关使用comm的更多信息,请参阅BashFAQ #36