我有一个包含很多文件夹的目录,我想移动其中包含超过 100 个文件的文件夹。
我正在考虑这样做:
find . -type d | while read d; do if
现在这对我来说是棘手的部分。
我是否要执行 for 来进入每个目录并检查它是否包含超过 100 个文件?如果是这样,我该怎么做?
for f in *; do cd $f; ll | wc; ?
我有点困惑如何获取目录内的文件总数,然后移动该目录(它包含超过 100 个文件)。
答案1
要计算目录中非隐藏名称的数量dir
,您可以使用
set -- dir/*
这会扩展*
目录中的 glob 并将位置参数设置为结果名称。如果模式匹配任何事物,则计数在 中$#
。
要迭代某个顶级目录中的所有目录top-dir
,请计算每个目录中的名称数量,并对包含超过 100 个名称的目录执行某些操作:
for subdir in top-dir/*/; do
set -- "$subdir"/*
if [ -e "$1" ] && [ "$#" -gt 100 ]; then
# do something to "$subdir"
fi
done
在bash
shell 中,设置nullglob
shell 选项就不再需要检查set
命令是否设法匹配任何名称(因为如果没有匹配,模式将被完全删除,而不是保持未展开状态)。
shopt -s nullglob
for subdir in top-dir/*/; do
set -- "$subdir"/*
if [[ $# -gt 100 ]]; then
# do something to "$subdir"
fi
done
另外,设置dotglob
shell 选项将使代码中的每个模式也匹配隐藏名称。
在上面的任意一段代码中,“do some to "$subdir"
”注释可以替换为您需要对这些子目录执行的操作。例如,要将它们移开,请使用
mv "$subdir" some/other/dir
这会将它们移动到目录中some/other/dir
。
答案2
您可以依次循环每个目录,计算它包含的文件数量,然后将其移动到...某个地方。例如,
for dir in ./*/
do
count=$(find "$dir" -maxdepth 1 -type f -printf "x\n" | wc -l) # Count the number of files in this subdirectory
[ $count -gt 100 ] && echo mv "$dir" # Output a message if we have enough
done
>
您可以在提示符处直接键入此内容(您将在第一行之后直到最后一行看到辅助提示符)或将其保存在脚本文件中并运行。
答案3
和zsh
:
mv -- *(/Fe['()(($# > 100)) $REPLY/*(N^-/)']) /dest/
将移至 /dest/
当前工作目录的非隐藏子目录,其中包含超过 100 个既不隐藏也不属于类型的条目目录(我假设你的意思是文件)。
这利用了zsh
全局限定符((/Fe...)
及(N...)
以上)根据名称以外的其他条件进一步选择匹配文件。
/
:选择文件类型目录仅有的。这里(与 glob 相反*/
)类型是确定的前符号链接解析在这里可能更可取,因为移动符号链接经常会破坏它们)。F
: 选择满的文件作为优化(对于目录,这意味着非空目录)e[code]
:根据包含当前正在考虑的文件的code
位置的解释结果进行选择。$REPLY
这里 code
就是()(($# > 100)) $REPLY/*(N^-/)
.
() <body> <args>
是一个内联函数。这里的 body ( (($# > 100))
) 检查参数的数量是否大于 100。参数是$REPLY/*(N^-/)
glob 的扩展,再次使用全局限定符:
N
: nullglob: 该 glob 将扩展为不当没有匹配的文件时,根本没有参数而不是错误。^
: 否定以下限定符。-/
/
与上面类似,除了-
导致以下限定符(此处/
)适用后符号链接解析。所以在这里我们计算的是文件不是类型的目录符号链接解析后。您可以替换^-/
为.
来计算常规的仅文件(排除所有其他类型的文件,如套接字、fifo、目录、符号链接...),或-.
常规文件和常规文件的符号链接。
要同时考虑隐藏的目录/文件,请添加D
限定符(外部和内部 glob 中的一个或两者)。
要递归地计算子目录中的文件,请将第二个替换*
为**/*
(或***/*
在目录树下降时遍历符号链接)。
您可以通过更改为进一步优化它code
:
()(($#)) $REPLY/*(NoN^-/[101])
这是用来oN
禁用文件排序的,因为我们不关心顺序,并且 glob 扩展为仅第 101 个匹配文件,我们只是测试它是否存在(($#))
(参数数量非零)。
¹ 请注意其中的多个条目可能引用同一个文件,例如当它们硬链接或符号链接在一起时。计算唯一的数量文件将是一个不同的练习
答案4
除了 shell 方法之外,您还可以使用由精心设计的命令组成的管道find
来选择要检查的文件夹/文件,将它们提供给 Awk 脚本,该脚本对最终结果进行过滤,从而在尽可能少的运行中xargs
捆绑实际命令。mv
它也可以是 shell 脚本,但 Awk 通常在处理文本方面更好更快。
下面使用指示处理 nul 分隔 I/O 的 GNU 工具,以支持嵌入换行符的文件名:
find . -maxdepth 2 \( -regex '^./[^/]+' -o -type f \) ! -name '.*' -print0 \
| LC_ALL=C gawk -F/ -v RS='\0' -v ORS='\0' \
'{if (NF==2) {d=1; n=0} else if (d && ++n>100) {d=0; print $2}}' \
| xargs -r0 mv -t dest/
与纯 shell 解决方案相比,该管道对资源的需求应该更少,因为它在处理过程中不进行任何缓冲,因此基本上不会受到任何数量的文件夹和文件的影响。
请注意脚本n>100
中的比较awk
:这是您可以随意调整阈值的地方。
该管道预计从包含要检查的文件夹的目录运行,因为它使用“naked” find .
。但是,您可以轻松地通过在该find .
片段前面添加一个片段来使其通用cd -- "${topdir:-.}" &&
,这样您就可以通过默认为当前目录的自定义$topdir
shell 变量提供起始目录。.
使用 BSD 工具的此类管道的相当等价物(由于 BSD 工具的固有限制而支持文件名中的换行符除外)可能如下:
find -E . -maxdepth 2 \( -regex '^./[^/]+' -o -type f \) ! -name '.*' \
| LC_ALL=C awk -F/ -v q=\' \
'{if (NF==2) {d=1; n=0} else if (d && ++n>100) {d=0; gsub(q, q"\\"q q, $2); print q$2q}}' \
| xargs sh -c '${1:+mv -- "$@" dest/}' --
它本质上与 GNU 工具版本相同,除了各种-print0
,-z
和选项以及脚本的-0
附加操作,以便引用文件名中可能存在的分隔字符(字符),这是 POSIX 需要使用的。gsub()
awk
" ' <space>
xargs
假设没有检查的路径(文件夹和文件)包含换行符,后一个管道应该在任何 BSD 系统上都能正常工作。
说到 POSIX 合规性,BSD 工具管道也应该适用于除find
命令之外的任何 POSIX 系统,因为 POSIX 没有-maxdepth
and-regex
子句。 POSIX 等效项find
可能是这样的:
# replace the find command of the BSD tools version, up to and including the trailing backslash character
find . \( -path '*/*/*' ! -path '*/*/*/*' -type f \) -o \( -path '*/*' ! -path '*/*/*' -type d \) ! -name '.*' \
该find
表达式的设计awk
也是为了简化脚本的工作,并选择目录层次结构第三级(其中级别 1 为.
)的常规文件以及第二级的目录。由于缺乏 BSD 和 GNU 中可用的更强大的子句find
,我通过子句游戏获得了相同的结果-path
。
最后请注意,这些管道通过 to! -name '.*'
子句显式忽略隐藏文件find
,并且它们仅考虑常规文件通过-type f
子句(因此排除例如符号链接),因为根据您的问题,这似乎是最明智的选择,但如果您确实想考虑隐藏文件和/或子文件夹、符号链接,也许还有特殊文件(命名管道、命名套接字)等)可能存在于文件夹内,您可以删除相应的子句,或者使用find
命令的附加子句对它们进行微调。在后一种情况下,请注意 makefind
也始终生成单独文件夹的名称1awk
,因为这些单独名称是脚本用来检测后续名称与先前名称位于不同文件夹中的“信号” 2。
1. 仅限第二级
2.为了获得更好的性能,我使用了真/假测试而不是字符串比较,请参阅脚本d
中的变量awk