从shell脚本中的目录中选择随机文件的最佳方法是什么?
这是我在Bash中的解决方案,但我会对在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"
有人还有其他想法吗?
编辑: lhunath对解析ls
提出了一个很好的观点。我想这取决于你是否想要携带。如果您有GNU findutils和coreutils,那么您可以这样做:
find "$dir" -maxdepth 1 -mindepth 1 -type f -print0 \
| sort --zero-terminated --random-sort \
| sed 's/\d000.*//g/'
哇,这很有趣!此外,它更符合我的问题,因为我说“随机文件”。但是很幸运的是,现在很难想象部署了一个安装了GNU而不是Perl 5的Unix系统。
答案 0 :(得分:58)
files=(/my/dir/*)
printf "%s\n" "${files[RANDOM % ${#files[@]}]}"
并且不解析ls 。阅读 http://mywiki.wooledge.org/ParsingLs
编辑:祝你找到一个可靠的非bash
解决方案。对于某些类型的文件名,大多数都会中断,例如带有空格或换行符或破折号的文件名(在纯sh
中几乎不可能)。要在没有bash
的情况下正确执行此操作,您需要完全迁移到awk
/ perl
/ python
/ ...而无需管道输出以进行进一步处理等。
答案 1 :(得分:27)
“shuf”不便携吗?
shuf -n1 -e /path/to/files/*
或查找文件是否比一个目录更深:
find /path/to/files/ -type f | shuf -n1
它是coreutils的一部分,但你需要6.4或更新才能得到它...所以RH / CentOS不包含它。
答案 2 :(得分:3)
类似的东西:
let x="$RANDOM % ${#file}"
echo "The randomly-selected file is ${path[$x]}"
bash中的 $RANDOM
是一个返回随机数的特殊变量,然后我使用模数除法来获得一个有效的索引,然后在数组中引用该索引。
答案 3 :(得分:3)
# ******************************************************************
# ******************************************************************
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
}
答案 4 :(得分:2)
这归结为:如何以便携方式在Unix脚本中创建随机数?
因为如果你有一个介于1和N之间的随机数,你可以使用head -$N | tail
来切换中间的某个位置。不幸的是,我知道没有可移植的方法来单独使用shell。如果你有Python或Perl,你可以轻松使用他们的随机支持,但是AFAIK,没有标准的rand(1)
命令。
答案 5 :(得分:2)
我认为Awk是获取随机数的好工具。根据{{3}},Awk是$RANDOM
的良好随机数替代。
这是一个避免使用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"
它继承了其他答案提到的文件包含换行符的问题。
答案 6 :(得分:2)
通过在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
答案 7 :(得分:2)
这是一个shell片段,它仅依赖于POSIX功能并处理任意文件名(但忽略了选择中的点文件)。随机选择使用awk,因为这是你在POSIX中得到的。这是一个非常糟糕的随机数生成器,因为awk的RNG以秒为单位的当前时间播种(所以它很容易预测,如果你每秒多次调用它会返回相同的选择)。
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
,请将openssl
的呼叫替换为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 % $#))
答案 8 :(得分:1)
BusyBox(在嵌入式设备上使用)通常配置为支持$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-
注意cut -f2-
中的尾随“ - ”;这是为了避免截断包含空格的文件(或您想要使用的任何分隔符)。
它无法正确处理带有嵌入换行符的文件名。
答案 9 :(得分:0)
将命令的每一行输出&#39; ls&#39;到一个名为line的关联数组中,然后选择其中一个...
ls | awk '{ line[NR]=$0 } END { print line[(int(rand()*NR+1))]}'
答案 10 :(得分:0)
我的2美分,当存在带有特殊字符的文件名时,该版本不会中断:
#!/bin/bash --
dir='some/directory'
let number_of_files=$(find "${dir}" -type f -print0 | grep -zc .)
let rand_index=$((1+(RANDOM % number_of_files)))
printf "the randomly-selected file is: "
find "${dir}" -type f -print0 | head -z -n "${rand_index}" | tail -z -n 1
printf "\n"