查找目录,但排除列表,其中目录名称中有空格

时间:2018-07-12 14:00:41

标签: bash find printf

我有一个过程可以在大型文件系统上审核从一天到另一天的文件。我想通过使用要排除的目录列表来排除某些目录。我可以做的很好,但是如果排除目录的名称中有空格,我会遇到麻烦。

为简单起见,我只列出四个子目录,但实际上我想搜索还是排除更多目录。也有可能添加了新目录,并且我想自动包含新目录,因此排除列表与使用包含列表。

base_dir/
├── sub_dir1
├── sub_dir2
├── sub dir3
└── sub_dir4

我有一个shell脚本和一个排除列表

$ cat exclude.txt
sub_dir2
sub dir3

shell脚本使用findprintf以及awk和sort来获取要审核的目录列表。

$ find ./base_dir -maxdepth 1 -type d $(printf "! -iname %s " $(cat exclude.txt)) | awk -F/ '{print $NF}' | sort
sub_dir1
sub dir3
sub_dir4

正如您可能在上面猜到和看到的那样,除了不忽略sub dir3之外,此方法有效。我试过在排除列表中使用双引号的几种组合,并使用%q vs %s vs %a,但似乎找不到正确的组合。

我想要的输出是

sub_dir1
sub_dir4

我意识到我可以做类似的事情:

find ./base_dir -maxdepth 1 -type d \
    ! -iname "sub dir3" $(printf "! -iname %s " $(cat exclude.txt)) \
    | awk -F/ '{print $NF}' | sort

并获得预期的输出,但是我只想使用exclude.txt列表。

EDIT 在阅读了一些答复之后,我尝试使用数组并认为该方法行得通,但现在我更加不清楚为什么该选项不起作用。如果我严格地在命令行中键入它,printf似乎会产生一个字符串,但是当尝试将其作为单行代码运行时,仍然会给我错误。

$cat exclude.txt
base_dir
sub_dir2
"sub dir3"

$ mapfile -t exclude < exclude.txt

$printf "! -iname %s " "${exclude[@]}"
! -iname base_dir ! -iname sub_dir2 ! -iname "sub dir3"

$find ./base_dir -maxdepth 1 -type d $(printf "! -iname %s " "${exclude[@]}")
find: paths must precede expression: dir3"

$ find ./base_dir -maxdepth 1 -type d ! -iname base_dir ! -iname sub_dir2 ! -iname "sub dir3"
./base_dir/sub_dir1
./base_dir/sub_dir4

3 个答案:

答案 0 :(得分:1)

  

已编辑以包含新信息,以防日后使用

不要嵌入printf / cat。解释器解析器正在对您不利。 将带有paste -s的排除过滤器堆叠到一个临时文件中,以动态生成命令,然后执行它。

$: find ./base_dir
./base_dir
./base_dir/sub dir1
./base_dir/sub dir3
./base_dir/sub_dir1
./base_dir/sub_dir3

$: tmpfile=/tmp/xFinder
$: printf "find ./base_dir -maxdepth 1 -type d ! -iname base_dir " > $tmpfile
$: { sed -E 's/^(.*)/! -iname \"\1\"/' exclude.txt; 
     printf " | xargs -I R basename R "; } | paste -s >> $tmpfile
$: cat $tmpfile
find ./base_dir -maxdepth 1 -type d ! -iname base_dir ! -iname "sub_dir1"    ! -iname "sub dir3"     ! -iname "sub_dir4"      | xargs -I R basename R

对basname的xargs调用剥离了路径信息,! -iname base_dir将其保留在查找输出中,作为其自身的目录。

$: . $tmpfile
./base_dir
./base_dir/sub dir1
./base_dir/sub_dir3

对于较早版本不完整的致歉。

答案 1 :(得分:1)

您可以将排除文件读入Bash数组中,然后像这样编写find命令:

mapfile -t exclude < exclude.txt
find ./base_dir \
    -mindepth 1 \          # Exclude the current directory
    -type d \
    -regextype egrep \     # Make sure alternation "|" does not have to be escaped
    ! -iregex ".*/($(IFS='|'; echo "${exclude[*]}"))" \
    -printf '%f\n'         # Print just filename without leading directories

导致

sub_dir1
sub_dir4

对于您的示例输入,-iregex测试扩展如下:

$ IFS='|'
$ echo "${exclude[*]}")
sub_dir2|sub dir3

因此排除路径的正则表达式变为

.*/(sub_dir2|sub dir3)

IFS的更改仅限于命令替换。

对此的限制是,如果要排除的目录包含正则表达式专用的字符,则必须转义这些字符,否则可能会造成混乱。如果您想逃脱,例如管道,则可以使用

echo "${exclude[*]//|/\\|}"

在命令替换中,导致

sub_dir2|sub dir3|has\|pipe

名称为has|pipe的目录|的管道已正确转义。

答案 2 :(得分:0)

由于您只想限制到一个子目录,而无需递归,因此可以将whildcards用于for循环:

$ find base_dir/
base_dir/
base_dir/sub_dir2
base_dir/sub_dir1
base_dir/sub_dir4
base_dir/sub dir3

$ cat exclude.txt 
sub_dir2
sub dir3

$ cat script.sh 
#!/bin/bash
for dir in base_dir/*
do
  ! [ -d "$dir" ] || 
    grep -qFx -- "$(basename -- "$dir")" exclude.txt &&
    continue
  echo "$dir" # or do somthing else
done

$ ./script.sh 
base_dir/sub_dir1
base_dir/sub_dir4