具有多次修剪的复杂查找命令

具有多次修剪的复杂查找命令

我需要遍历具有多个起点的目录树,例如,find ./User1 ./User2 ./User3但仅处理任何这些起点中存在的特定目录名称,如下所示:./User1/Documents ./User1/Pictures /.User2/Documents等等。我想跳过 prune不在所有起点的给定列表中的任何其他子文件夹。

-not我对andprune\(&的多种组合的所有尝试\)都失败了。

起点将在运行时在脚本中确定。作为最后的手段,我可​​以通过循环列表User?并生成一个更大的起点列表(已附加所需的目录名称)来构建起点列表。

./User?/NotInList应排除但./User?/InList/NotInList可以。

[总体情况:我正在进行数据恢复,并希望枚举 Microsoft WindowsUsers文件夹中的“重要”文件。我需要跳过PublicDefault文件夹(因此我构建了一串剩余文件夹作为命令的起点find),并且仅选择/下降到Pictures Documents每个用户的等文件夹,而不是选择/下降到每个用户的AppDataContacts文件夹。很少重要。]

答案1

zsh

users=(User1 User2)
# or users=($(<userlist.txt))
# if userlist.txt contains a list of users as separate words

dirs=(Documents Pictures ...)

find ./$^users/$^dirs

fish与:相同

set users User1 User2 ...
set dirs Documents Pictures
find ./$users/$dirs

fishor中zsh -o rcexpandparam,数组以大括号扩展方式扩展。在 中zsh,该语法针对单个数组扩展$^array启用。rcexpandparam

这里的引用rc有点误导。虽然$array^stringin rc/ es(其中array是 (1 2)) 会像这样展开{1,2}string(这就是为什么在 中zsh选择^类似 rc 类型的展开的原因$^array),但这不适用于将数组连接在一起。在 中rc$array1^$arrat2(与 相同$array1$array2)仅适用于相同大小的数组,并将元素一一连接((1 2)^(a b)成为1a 2b,不是1a 1b 2a 2b)。

请注意,这些不是通配,无论该文件是否存在User1/Documents都会被传递到。find传递实际存在的文件或目录的列表存在, 在zsh, 你可以做

find ./$^users/$^dirs(N)

which pass 将(N)glob 限定符添加到该数组乘法的所有元素结果中,这有两个效果:

  • 使它们成为全局变量,这意味着它们将扩展到匹配的文件
  • 如果 glob 不匹配任何文件(它们只能匹配 0 或 1,因为其中没有通配符),则生成的 glob 将扩展为空。

或者,你可以去全局一路:

find (User1|User2)/(Documents|Pictures)

或者根据你的例子:

set -o extendedglob # best in ~/.zshrc
find ./^(#i)(Default|Public)/(Documents|Pictures)

或者根据数组生成该 glob

find (${(j:|:)~${(b)users}})/${(j:|:)~${(b)dirs}}

在哪里:

  • (b)如果数组元素中有通配符,则用引号引起来
  • (j:|:)加入他们|
  • ~在扩展时打开通配符

它们可以使用任意文件名(除了以 开头的文件名-作为限制find)。

单独使用它find会非常棘手,特别是如果您想允许任意文件名。但对于像那些相对驯服的人DefaultPublic...),你可以尝试:

LC_ALL=C find . -name . -o -path './*/*' \( ! -path './*/*/*' \
  ! -name Documents ! -name Pictures -prune -o -print \) -o   \
  ! -name Default ! -name Public -o -prune

(这种事情让我头疼)。

这是使用标准find语法,如果你想要不区分大小写的匹配,你可以使用-name '[pP][uU][bB][lL][iI][cC]',或者如果使用 GNU find(你似乎)或兼容的,使用-iname Public

答案2

从您自己发布的答案来看,您想要选择目录,其中是当前目录中的任何目录,除了或,并且是、、 或中的任何一个。./x/yxPublicDefaultyDocumentsPicturesMy Documents

extglob在 Bash 中,当启用时,您可以使用 glob 来做到这一点。 (在 ksh 中,当模式直接位于命令行上时,这是默认设置;在 Zsh 中,您需要使用setopt kshglob。)

鉴于:

$ mkdir -p {Someuser,"other user",Public,Default}/{Pictures,"My Documents",Uselessdir}

这里的 glob 应该做你想做的事:

$ shopt -s extglob
$ printf "%s\n" ./!(Public|Default)/@(Documents|My Documents|Pictures)
./other user/My Documents
./other user/Pictures
./Someuser/My Documents
./Someuser/Pictures

所以,你应该能够运行

shopt -s extglob
find ./!(Public|Default)/@(Documents|My Documents|Pictures) ...

glob 不会扩展到不存在的目录,因此find如果 say./Someuser/Pictures不存在,您不会收到错误。

shopt -s nocaseglob如果您想要不区分大小写的匹配,请添加。

答案3

基于使用大括号扩展的 @StephaneChazelas 评论,这是我使用的解决方案,bash它可以处理文件名中的空格并处理像只有一个用户这样的边缘情况。

用于大括号扩展的字符串将存储在变量中,因此我们需要使用eval它来实际扩展它。

# escape spaces with backslash
directories=Documents,Pictures,My\ Documents

# get usernames to use with expansion
# and escape any spaces using sed
user_folders=$(find . -maxdepth 1 -mindepth 1 -not -iname Public -a -not -iname Default -type d -printf "%f," | \
sed 's| |\\ |g' )   

# remove trailing comma from last user's name
user_folders=${user_folders::-1}    

# only use brace expansion if there is more than one user; if there's only
# one value it won't expand and we'll be left with braces
[[ $user_folders = *,* ]] && user_folders="{$user_folders}"

find_string="find $user_folders/{$directories}"
eval "$find_string"

相关内容