3

我需要在 FreeBSD 中使用 bash 脚本完成以下事情:

  • 创建一个目录。
  • 生成 1000 个唯一文件,其名称取自系统中的其他随机文件。
  • 每个文件必须包含有关其名称的原始文件的信息 - 名称和大小,不包括文件的原始内容。
  • 该脚本必须以毫秒为单位显示有关其执行速度的信息。

我可以完成的是使用命令获取 1000 个唯一文件的名称和路径,find并将grep它们放在一个列表中。然后我无法想象如何删除路径部分并在另一个目录中创建文件,其名称取自随机文件列表。我尝试了一个带有basename命令的for循环,但不知何故我无法让它工作,我也不知道如何完成其​​他任务......

4

2 回答 2

3

[更新:我想回到这个问题,试图让我的回答在平台(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标志的一个好处是它似乎可以处理文件名中的空格。通过使用vimand检查文件sed查找带空格的行)并比较 and 的输出,wc -luniq可以了解您的输出以及结果列表是否合理。然后,您可以通过cutgrepsedawk和朋友进行管道传输,以便以您想要的方式创建文件。例如,从 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 上gfindgnufind。一种更便携的方法是创建一个直接向上的文件列表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 个随机文件的新列表。或者不......它真的很慢!在寻找加快速度的方法时awksed我遇到了一个使用 Alex Lines的绝妙技巧,它按字节(而不是行)搜索文件,并使用ordd将结果转换为一行。有关详细信息,请参阅Alex 的博客。我对他技术的唯一问题是将开关设置为足够高的数字。出于神秘的原因(我希望有人会解释)——也许是因为我的原因 ——会吐出不完整的行 ,除非我设置的数字比实际的最大行长高得多。我想我可能混淆了字符和字节。有什么解释吗?sedawkcount=localeLC_ALL=en_US.UTF-8ddrandlist.txtcount=

因此,在上述警告之后并希望它可以在两个以上的平台上运行,这是我解决问题的尝试:

#!/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 
于 2013-05-25T16:48:44.210 回答
1

我可以完成的是使用命令“find”和“grep”获取 1000 个唯一文件的名称和路径,并将它们放在一个列表中

我将假设有一个文件在每一行都包含每个文件的完整路径(FULL_PATH_TO_LIST_FILE)。考虑到与此过程相关的统计数据不多,我省略了这一点。但是,您可以添加自己的。

cd WHEREVER_YOU_WANT_TO_CREATE_NEW_FILES
for file_path in `cat FULL_PATH_TO_LIST_FILE`
do
     ## This extracts only the file name from the path
     file_name=`basename $file_path`

     ## This grabs the files size in bytes
     file_size=`wc -c < $file_path`

     ## Create the file and place info regarding original file within new file
     echo -e "$file_name \nThis file is $file_size bytes "> $file_name

done
于 2013-05-23T19:34:48.383 回答