[更新:我想回到这个问题,试图让我的回答在平台(OS X 是 Unix!)和 $SHELL 之间更有用和可移植,即使最初的问题指定了 bash 和 zsh。其他回复假设“随机”文件名的临时文件列表,因为问题没有显示列表是如何构建的或如何进行选择的。我展示了一种使用临时文件在我的响应中构建列表的方法。我不确定如何随机化find
操作“内联”,并希望其他人可以展示如何做到这一点(便携)。我也希望这能引起一些评论和批评:你永远不会知道太多的 $SHELL 技巧。我删除了 perl 引用,但我在此挑战自己在 perl 中再次执行此操作 - 因为 perl 非常便携 - 使其在 Windows 上运行。我会等待一段时间的评论,然后缩短和清理这个答案。谢谢。]
创建文件列表
你可以用 GNU find(1) 做很多事情。以下将创建一个文件,其中包含文件名和三个制表符分隔的数据列(文件名、位置、大小(以千字节为单位))。
find / -type f -fprintf tmp.txt '%f\t%h/%f\t%k \n'
我假设您希望在所有文件名中随机(即没有链接),因此您将从整个文件系统中获取条目。我的工作站上有 800000 个文件,但内存很大,所以这不需要太长时间。我的笔记本电脑有大约 30 万个文件,内存不多,但创建完整列表仍然只需要几分钟左右。您需要通过从搜索中排除或修剪某些目录来进行调整。
该-fprintf
标志的一个好处是它似乎可以处理文件名中的空格。通过使用vim
and检查文件sed
(即查找带空格的行)并比较 and 的输出,wc -l
您uniq
可以了解您的输出以及结果列表是否合理。然后,您可以通过cut
、grep
或sed
、awk
和朋友进行管道传输,以便以您想要的方式创建文件。例如,从 shell 提示符:
~/# touch `cat tmp.txt |cut -f1`
~/# for i in `cat tmp.txt|cut -f1`; do cat tmp.txt | grep $i > $i.dat ; done
我在.dat
这里为我们创建的文件提供了一个扩展名,以将它们与它们所引用的文件区分开来,并使它们更容易移动或删除它们,您不必这样做:只需去掉扩展名$i > $i
.
该标志的坏处在于-fprintf
它仅适用于 GNU find 并且不是 POSIX 标准标志,因此它在 OS X 或 BSD 上不可用find(1)
(尽管 GNU find 可能以 或 的形式安装在您的 Unix 上gfind
)gnufind
。一种更便携的方法是创建一个直接向上的文件列表find / -type f > tmp.txt
(这在我的系统上大约需要 15 秒,有 800k 文件和 ZFS 池中的许多慢速驱动器。想出更高效的东西应该很容易让人们在评论中做!)。从那里,您可以使用标准实用程序创建您想要的数据值,以处理上面的 Florin Stingaciu 所示的文件列表。
#!/bin/sh
# portably get a random number (OS X, BSD, Linux and $SHELLs w/o $RANDOM)
randnum=`od -An -N 4 -D < /dev/urandom` ; echo $randnum
for file in `cat tmp.txt`
do
name=`basename $file`
size=`wc -c $file |awk '{print $1}'`
# Uncomment the next line to see the values on STDOUT
# printf "Location: $name \nSize: $size \n"
# Uncomment the next line to put data into the respective .dat files
# printf "Location: $file \nSize: $size \n" > $name.dat
done
# vim: ft=sh
如果您一直关注这一步,您会意识到这会创建很多文件——在我的工作站上,这会创建 800k的.dat
文件,这不是我们想要的!那么,如何从我们的 800k 列表中随机选择 1000 个文件进行处理呢?有几种方法可以解决它。
从文件列表中随机选择
我们有一个系统上所有文件的列表(!)。现在为了选择 1000 个文件,我们只需要从列表文件 ( tmp.txt
) 中随机选择 1000 行。我们可以通过使用上面看到的很酷的技术生成一个随机数来设置要选择的行号的上限od
- 它非常酷且跨平台,我在我的 shell 中有这个别名;-) - 然后执行模除法( %
)使用文件中的行数作为除数。然后我们只取那个数字并选择文件中与 awk 或 sed 对应的行(例如 sed -n <$RANDOMNUMBER>p filelist
),迭代 1000 次,然后就可以了!我们有一个包含 1000 个随机文件的新列表。或者不......它真的很慢!在寻找加快速度的方法时awk
,sed
我遇到了一个使用 Alex Lines的绝妙技巧,它按字节(而不是行)搜索文件,并使用ordd
将结果转换为一行。有关详细信息,请参阅Alex 的博客。我对他技术的唯一问题是将开关设置为足够高的数字。出于神秘的原因(我希望有人会解释)——也许是因为我的原因 ——会吐出不完整的行 ,除非我设置的数字比实际的最大行长高得多。我想我可能混淆了字符和字节。有什么解释吗?sed
awk
count=
locale
LC_ALL=en_US.UTF-8
dd
randlist.txt
count=
因此,在上述警告之后并希望它可以在两个以上的平台上运行,这是我解决问题的尝试:
#!/bin/sh
IFS='
'
# We create tmp.txt with
# find / -type f > tmp.txt # tweak as needed.
#
files="tmp.txt"
# Get the number of lines and maximum line length for later
bytesize=`wc -c < $files`
# wc -L is not POSIX and we need to multiply so:
linelenx10=`awk '{if(length > x) {x=length; y = $0} }END{print x*10}' $files`
# A function to generate a random number modulo the
# number of bytes in the file. We'll use this to find a
# random location in our file where we can grab a line
# using dd and sed.
genrand () {
echo `od -An -N 4 -D < /dev/urandom` ' % ' $bytesize | bc
}
rm -f randlist.txt
i=1
while [ $i -le 1000 ]
do
# This probably works but is way too slow: sed -n `genrand`p $files
# Instead, use Alex Lines' dd seek method:
dd if=$files skip=`genrand` ibs=1 count=$linelenx10 2>/dev/null |awk 'NR==2 {print;exit}'>> randlist.txt
true $((i=i+1)) # Bourne shell equivalent of $i++ iteration
done
for file in `cat randlist.txt`
do
name=`basename $file`
size=`wc -c <"$file"`
echo -e "Location: $file \n\n Size: $size" > $name.dat
done
# vim: ft=sh