我需要遍历具有多个起点的目录树,例如,find ./User1 ./User2 ./User3
但仅处理任何这些起点中存在的特定目录名称,如下所示:./User1/Documents
./User1/Pictures
/.User2/Documents
等等。我想跳过和 prune
不在所有起点的给定列表中的任何其他子文件夹。
-not
我对andprune
与\(
&的多种组合的所有尝试\)
都失败了。
起点将在运行时在脚本中确定。作为最后的手段,我可以通过循环列表User?
并生成一个更大的起点列表(已附加所需的目录名称)来构建起点列表。
仅./User?/NotInList
应排除但./User?/InList/NotInList
可以。
[总体情况:我正在进行数据恢复,并希望枚举 Microsoft WindowsUsers
文件夹中的“重要”文件。我需要跳过Public
和Default
文件夹(因此我构建了一串剩余文件夹作为命令的起点find
),并且仅选择/下降到Pictures
Documents
每个用户的等文件夹,而不是选择/下降到每个用户的AppData
等Contacts
文件夹。很少重要。]
答案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
在fish
or中zsh -o rcexpandparam
,数组以大括号扩展方式扩展。在 中zsh
,该语法针对单个数组扩展$^array
启用。rcexpandparam
这里的引用rc
有点误导。虽然$array^string
in 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
会非常棘手,特别是如果您想允许任意文件名。但对于像那些相对驯服的人Default
,Public
...),你可以尝试:
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/y
x
Public
Default
y
Documents
Pictures
My 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"