我有一个过程可以在大型文件系统上审核从一天到另一天的文件。我想通过使用要排除的目录列表来排除某些目录。我可以做的很好,但是如果排除目录的名称中有空格,我会遇到麻烦。
为简单起见,我只列出四个子目录,但实际上我想搜索还是排除更多目录。也有可能添加了新目录,并且我想自动包含新目录,因此排除列表与使用包含列表。
base_dir/
├── sub_dir1
├── sub_dir2
├── sub dir3
└── sub_dir4
我有一个shell脚本和一个排除列表
$ cat exclude.txt
sub_dir2
sub dir3
shell脚本使用find
和printf
以及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
答案 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