从shell脚本的目录中select随机文件的最佳方法

从shell脚本的目录中select一个随机文件的最佳方法是什么?

这里是我在Bash中的解决scheme,但是对于在Unix上使用的更便携(非GNU)版本,我会非常感兴趣。

dir='some/directory' file=`/bin/ls -1 "$dir" | sort --random-sort | head -1` path=`readlink --canonicalize "$dir/$file"` # Converts to full path echo "The randomly-selected file is: $path" 

任何人有任何其他的想法?

编辑: lhunathparsingls的好处。 我想这归结于你是否想要移植或不移动。 如果你有GNU findutils和coreutils,那么你可以这样做:

 find "$dir" -maxdepth 1 -mindepth 1 -type f -print0 \ | sort --zero-terminated --random-sort \ | sed 's/\d000.*//g/' 

噢,那很有趣! 从我说的“随机文件”来看,它更符合我的问题。 尽pipe如此,现在很难想象一个安装了GNU但不包含Perl 5的Unix系统。

 files=(/my/dir/*) printf "%s\n" "${files[RANDOM % ${#files[@]}]}" 

不要分析ls 。 阅读http://mywiki.wooledge.org/ParsingLs

编辑:祝你好运find一个非bash解决scheme是可靠的。 大多数会打破某些types的文件名,如文件名与空格或换行符或破折号(这是几乎不可能在纯sh )。 要做到这一点没有bash ,你需要完全迁移到awk / perl / python / …没有pipe道输出进行进一步处理等。

“shuf”不是便携式的吗?

 shuf -n1 -e /path/to/files/* 

或查找文件是否比一个目录更深:

 find /path/to/files/ -type f | shuf -n1 

它是coreutils的一部分,但是你需要6.4或者更新才能得到它……所以RH / CentOS不包括它。

东西lile“

 let x="$RANDOM % ${#file}" echo "The randomly-selected file is ${path[$x]}" 

bash中的$ RANDOM是一个返回一个随机数的特殊variables,然后使用模数除法得到一个有效的索引,然后索引到数组中。

 # ****************************************************************** # ****************************************************************** function randomFile { tmpFile=$(mktemp) files=$(find . -type f > $tmpFile) total=$(cat "$tmpFile"|wc -l) randomNumber=$(($RANDOM%$total)) i=0 while read line; do if [ "$i" -eq "$randomNumber" ];then # Do stuff with file amarok $line break fi i=$[$i+1] done < $tmpFile rm $tmpFile } 

这归结为:我怎样才能以一种便携的方式在Unix脚本中创build一个随机数字?

因为如果你有一个1到N之间的随机数,你可以使用head -$N | tail head -$N | tail巴切在中间的某个地方。 不幸的是,我知道没有可移植的方式来单独使用shell。 如果你有Python或Perl,你可以很容易地使用它们的随机支持,但是AFAIK,没有标准的rand(1)命令。

我认为Awk是一个获得随机数的好工具。 根据高级Bash指南 ,Awk是$RANDOM一个很好的随机数字replace。

这里有一个避免Bash-isms和GNU工具的脚本版本。

 #! /bin/sh dir='some/directory' n_files=`/bin/ls -1 "$dir" | wc -l | cut -f1` rand_num=`awk "BEGIN{srand();print int($n_files * rand()) + 1;}"` file=`/bin/ls -1 "$dir" | sed -ne "${rand_num}p"` path=`cd $dir && echo "$PWD/$file"` # Converts to full path. echo "The randomly-selected file is: $path" 

它inheritance了其他答案提到的问题,如果文件包含换行符。

files=(/my/dir/*) printf "%s\n" "${files[RANDOM % ${#files}]}"

你的想法几乎工作,但我不得不添加一个[@]

files=(/my/dir/*) printf "%s\n" "${files[RANDOM % ${#files[@]}]}"

文件名中的换行符可以通过在Bash中执行以下操作来避免:

 #!/bin/sh OLDIFS=$IFS IFS=$(echo -en "\n\b") DIR="/home/user" for file in $(ls -1 $DIR) do echo $file done IFS=$OLDIFS 

这是一个只依赖于POSIX特性的shell代码片段,可以处理任意文件名(但省略了select中的点文件)。 随机select使用awk,因为这是你在POSIX中得到的。 这是一个非常糟糕的随机数生成器,因为awk的RNG以秒为单位播种当前时间(所以很容易预测,如果每秒多次调用它,返回相同的select)。

 set -- * n=$(echo $# | awk '{srand(); print int(rand()*$0) + 1}') eval "file=\$$n" echo "Processing $file" 

如果你不想忽略点文件,文件名的生成代码( set -- * )需要被更复杂的东西代替。

 set -- *; [ -e "$1" ] || shift set .[!.]* "$@"; [ -e "$1" ] || shift set ..?* "$@"; [ -e "$1" ] || shift if [ $# -eq 0]; then echo 1>&2 "empty directory"; exit 1; fi 

如果您有OpenSSL可用,您可以使用它来生成随机字节。 如果你没有,但你的系统有/dev/urandom ,用dd if=/dev/urandom bs=3 count=1 2>/dev/nullreplaceopenssl的调用dd if=/dev/urandom bs=3 count=1 2>/dev/null 。 这是一个将n设置为1和$#之间的随机值的$# ,注意不要引入偏差。 这段代码假设$#最多是2 ^ 23-1。

 while n=$(($(openssl rand 3 | od -An -t u4) + 1)) [ $n -gt $((16777216 / $# * $#)) ] do :; done n=$((n % $#)) 

BusyBox(在embedded式设备上使用)通常被configuration为支持$RANDOM但是它没有bash风格的数组或sort --random-sortshuf 。 因此如下:

 #!/bin/sh FILES="/usr/bin/*" for f in $FILES; do echo "$RANDOM $f" ; done | sort -n | head -n1 | cut -d' ' -f2- 

注意尾随“ – ”in cut -f2- ; 这是避免截断包含空格的文件(或任何您要使用的分隔符)所必需的。

它不会正确处理embedded换行符的文件名。

把命令'ls'的每行输出放到一个名为line的关联数组中,然后select其中一个类似于…的行。

 ls | awk '{ line[NR]=$0 } END { print line[(int(rand()*NR+1))]}'