bash-从Shell脚本目录中选择随机文件的最佳方法

从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/'

哇,那很有趣! 自从我说“随机文件”以来,它也更符合我的问题。 老实说,如今,很难想象在那里部署了Unix系统并安装了GNU而不是Perl 5。

JasonSmith asked 2020-06-30T04:40:29Z
11个解决方案
59 votes
files=(/my/dir/*)
printf "%s\n" "${files[RANDOM % ${#files[@]}]}"

而且不要解析ls。 阅读[http://mywiki.wooledge.org/ParsingLs]

编辑:祝你好运,找到一个非bash解决方案可靠。 大多数文件会破坏某些类型的文件名,例如带有空格,换行符或破折号的文件名(在纯sh中几乎是不可能的)。 要在没有bash的情况下正确执行此操作,您需要完全迁移到awk/perl/python / ...,而无需通过管道将输出进行进一步处理等。

lhunath answered 2020-06-30T04:40:43Z
30 votes

“ shuf”不是便携式的吗?

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

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

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

它是coreutils的一部分,但您需要6.4或更高版本才能获取它...因此RH / CentOS不包含它。

johnnyB answered 2020-06-30T04:41:12Z
3 votes
# ******************************************************************
# ******************************************************************
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
}
Pipo answered 2020-06-30T04:41:28Z
3 votes

就像是:

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

bash中的$RANDOM是一个特殊变量,它返回一个随机数,然后使用模数除法来获取有效索引,然后在数组中引用该索引。

fido answered 2020-06-30T04:41:52Z
2 votes

归结为:如何以可移植的方式在Unix脚本中创建随机数?

因为如果您有一个介于1到N之间的随机数,则可以使用head -$N | tail在中间切某处。 不幸的是,我不知道有什么可移植的方法可以单独使用shell来实现。 如果您具有Python或Perl,则可以轻松使用它们的随机支持,但AFAIK则没有标准的rand(1)命令。

Aaron Digulla answered 2020-06-30T04:42:17Z
2 votes

我认为Awk是获取随机数的好工具。 根据《高级Bash指南》,Awk是$RANDOM的很好的随机数替代品。

这是避免使用Bash-ism和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"

它继承了文件包含换行符时其他答案提到的问题。

ashawley answered 2020-06-30T04:42:47Z
2 votes

通过在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
gsbabil answered 2020-06-30T04:43:07Z
2 votes

这是一个仅依赖于POSIX功能并能处理任意文件名的shell片段(但从选择中省略了点文件)。 随机选择使用awk,因为这就是您在POSIX中获得的全部。 这是一个非常差的随机数生成器,因为awk的RNG是以当前时间(以秒为单位)播种的(因此很容易预测,如果每秒多次调用,则返回相同的选择)。

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

如果您不想忽略点文件,则需要用更复杂的名称替换文件名生成代码(/dev/urandom)。

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替换对openssl的调用。以下是将n设置为1到$#之间的随机值的摘要,请注意不要引入偏差。 此摘要假定$#最多为2 ^ 23-1。

while
  n=$(($(openssl rand 3 | od -An -t u4) + 1))
  [ $n -gt $((16777216 / $# * $#)) ]
do :; done
n=$((n % $#))
Gilles answered 2020-06-30T04:43:37Z
1 votes

通常将BusyBox(用于嵌入式设备)配置为支持cut -f2-,但它没有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-

注意在cut -f2-中尾随“-”; 为避免截断包含空格(或要使用的分隔符)的文件,这是必需的。

它无法正确处理带有嵌入式换行符的文件名。

Robert Calhoun answered 2020-06-30T04:44:07Z
0 votes

将命令“ ls”的输出的每一行放入一个名为line的关联数组,然后选择类似的其中之一...

ls | awk '{ line[NR]=$0 } END { print line[(int(rand()*NR+1))]}'
kapu answered 2020-06-30T04:44:27Z
0 votes

我的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"
Jay jargot answered 2020-06-30T04:44:47Z
translate from https://stackoverflow.com:/questions/701505/best-way-to-choose-a-random-file-from-a-directory-in-a-shell-script