如何从bash中的目录中select随机文件?

我有一个约2000个文件的目录。 如何通过使用bash脚本或pipe道命令列表来selectN文件的随机样本?

这是一个使用GNUsorting的随机选项的脚本:

 ls |sort -R |tail -$N |while read file; do # Something involving $file, or you can leave # off the while to just get the filenames done 

你可以使用shuf (来自GNU coreutils包)。 给它提供一个文件名列表,并要求它从随机排列中返回第一行:

 ls dirname | shuf -n 1 # probably faster and more flexible: find dirname -type f | shuf -n 1 # etc.. 

调整-n, --head-count=COUNT值以返回想要的行数。 例如,要返回5个随机文件名,您可以使用:

 find dirname -type f | shuf -n 5 

这里有一些不parsingls的输出的可能性,并且对于名称中带有空格和有趣符号的文件是100%安全的。 所有这些将随机文件列表填充数组randf 。 如果需要,可以使用printf '%s\n' "${randf[@]}"轻松打印此数组。

  • 这个可能会多次输出相同的文件,而且N需要事先知道。 这里我select了N = 42。

     a=( * ) randf=( "${a[RANDOM%${#a[@]}]"{1..42}"}" ) 

    这个function没有很好的logging。

  • 如果事先不知道N,但是您确实喜欢以前的可能性,则可以使用eval 。 但它是邪恶的,你必须真正确保N不会直接从用户input没有彻底检查!

     N=42 a=( * ) eval randf=( \"\${a[RANDOM%\${#a[@]}]\"\{1..$N\}\"}\" ) 

    我个人不喜欢eval ,因此这个答案!

  • 使用更直接的方法(一个循环)相同:

     N=42 a=( * ) randf=() for((i=0;i<N;++i)); do randf+=( "${a[RANDOM%${#a[@]}]}" ) done 
  • 如果你不想有可能有几次相同的文件:

     N=42 a=( * ) randf=() for((i=0;i<N && ${#a[@]};++i)); do ((j=RANDOM%${#a[@]})) randf+=( "${a[j]}" ) a=( "${a[@]:0:j}" "${a[@]:j+1}" ) done 

注意 。 这是一个旧的post较晚的答案,但接受的答案链接到一个外部页面,显示可怕的bash实践,而另一个答案并不好多了,因为它也parsing了ls的输出。 对接受的答案的一个评论指出,Lhunath的一个很好的答案显然表明了良好的做法,但是并没有完全回答OP。

如果您安装了Python(与Python 2或Python 3一起使用):

要select一个文件(或从任意命令行),请使用

 ls -1 | python -c "import sys; import random; print(random.choice(sys.stdin.readlines()).rstrip())" 

要selectN文件/行,请使用(注意N位于命令末尾,将其replace为一个数字)

 ls -1 | python -c "import sys; import random; print(''.join(random.sample(sys.stdin.readlines(), int(sys.argv[1]))).rstrip())" N 

这是对@ gniourf_gniourf迟到的答案的一个更晚的回应,我刚刚提出,因为这是迄今为止最好的答案,两次。 (一次用于避免eval ,一次用于安全的文件名处理。)

但是花了我几分钟的时间来解开这个答案使用的“没有很好logging”的function。 如果你的Bash技能足够稳定,你立即看到它的工作原理,那么跳过这个评论。 但我没有,并解开了它,我认为这是值得解释的。

特性#1是shell自己的文件通配符。 a=(*)创build一个数组$a ,其成员是当前目录中的文件。 Bash理解所有文件名的奇怪,以便列表保证正确,保证转义等。不需要担心正确parsing由ls返回的文本文件名。

特性#2是数组的 Bash 参数扩展 ,一个嵌套在另一个中。 这从${#ARRAY[@]} ,扩展到$ARRAY的长度。

然后,该扩展用于下标数组。 find1到N之间的随机数的标准方法是取模随机数N的值。我们需要一个介于0和数组长度之间的随机数。 这是为了清晰起见,将其分成两行:

 LENGTH=${#ARRAY[@]} RANDOM=${a[RANDOM%$LENGTH]} 

但是这个解决scheme只需要一行代码,就可以去除不必要的variables赋值。

function#3是Bash大括号扩展 ,虽然我不得不承认我不完全理解它。 例如,使用Brace扩展来生成名为filename1.txtfilename2.txt等的25个文件的列表: echo "filename"{1..25}".txt"

"${a[RANDOM%${#a[@]}]"{1..42}"}"的expression式"${a[RANDOM%${#a[@]}]"{1..42}"}"使用这个技巧来产生42个单独的扩展。 大括号扩展在[ ]}之间放置了一个数字,起初我以为是对数组进行下标,但如果是这样的话,它会以冒号开头。 (它也会从数组中的一个随机点中返回42个连续的项目,这与从数组中返回42个随机项目完全不一样)。我认为这只是让shell运行扩展42次,从而返回来自arrays的42个随机项目。 (但是如果有人能够更全面地解释,我很乐意听到。)

N必须被硬编码(至42)的原因是在variables扩展之前支架扩展发生。

最后,这里是function#4 ,如果你想recursion的做一个目录层次结构:

 shopt -s globstar a=( ** ) 

这打开了一个shell选项 ,导致**recursion匹配。 现在,您的$a数组包含整个层次结构中的每个文件。

这是我可以在MacOS上与bash很好玩的唯一脚本。 我从以下两个链接合并和编辑片段:

ls命令:我怎样才能得到一个recursion的全path列表,每个文件一行?

http://www.linuxquestions.org/questions/linux-general-1/is-there-a-bash-command-for-picking-a-random-file-678687/

 #!/bin/bash # Reads a given directory and picks a random file. # The directory you want to use. You could use "$1" instead if you # wanted to parametrize it. DIR="/path/to/" # DIR="$1" # Internal Field Separator set to newline, so file names with # spaces do not break our script. IFS=' ' if [[ -d "${DIR}" ]] then # Runs ls on the given dir, and dumps the output into a matrix, # it uses the new lines character as a field delimiter, as explained above. # file_matrix=($(ls -LR "${DIR}")) file_matrix=($(ls -R $DIR | awk '; /:$/&&f{s=$0;f=0}; /:$/&&!f{sub(/:$/,"");s=$0;f=1;next}; NF&&f{ print s"/"$0 }')) num_files=${#file_matrix[*]} # This is the command you want to run on a random file. # Change "ls -l" by anything you want, it's just an example. ls -l "${file_matrix[$((RANDOM%num_files))]}" fi exit 0 
 ls | shuf -n 10 # ten random files 

一个简单的解决scheme, 避免ls的parsing和空间:

 shuf -en 5 dir/* | while read file; do echo $file done 

我使用这个:它使用临时文件,但深入目录,直到它find一个普通的文件,并返回它。

 # find for a quasi-random file in a directory tree: # directory to start search from: ROOT="/"; tmp=/tmp/mytempfile TARGET="$ROOT" FILE=""; n= r= while [ -e "$TARGET" ]; do TARGET="$(readlink -f "${TARGET}/$FILE")" ; if [ -d "$TARGET" ]; then ls -1 "$TARGET" 2> /dev/null > $tmp || break; n=$(cat $tmp | wc -l); if [ $n != 0 ]; then FILE=$(shuf -n 1 $tmp) # or if you dont have/want to use shuf: # r=$(($RANDOM % $n)) ; # FILE=$(tail -n +$(( $r + 1 )) $tmp | head -n 1); fi ; else if [ -f "$TARGET" ] ; then rm -f $tmp echo $TARGET break; else # is not a regular file, restart: TARGET="$ROOT" FILE="" fi fi done; 

康先生在这里稍微修改了一下Perl的解决scheme:
如何在Unix命令行或shell脚本中打乱文本文件的行?

$ ls | perl -MList :: Util = shuffle -e'@lines = shuffle(<>); print @lines [0..4]'