从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/null
replaceopenssl
的调用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-sort
或shuf
。 因此如下:
#!/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))]}'