it-swarm-es.tech

La mejor manera de elegir un archivo aleatorio de un directorio en un script de Shell

¿Cuál es la mejor manera de elegir un archivo aleatorio de un directorio en un script de Shell?

Aquí está mi solución en Bash, pero estaría muy interesado en una versión más portátil (no GNU) para usar en Unix propiamente dicho.

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"

¿Alguien más tiene otras ideas?

Editar: lhunath hace un buen punto sobre el análisis ls. Supongo que se trata de si quieres ser portátil o no. Si tiene el GNU findutils y coreutils, puede hacer lo siguiente:

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

¡Qué divertido! También coincide mejor con mi pregunta, ya que dije "archivo aleatorio". Sin embargo, en la actualidad, es difícil imaginar un sistema Unix desplegado que tenga GNU instalado pero no Perl 5.

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

Y no analice ls . Leer http://mywiki.wooledge.org/ParsingLs

Editar: Buena suerte al encontrar una solución que no seabash que sea confiable. La mayoría se romperá para ciertos tipos de nombres de archivos, como nombres de archivos con espacios o líneas nuevas o guiones (es prácticamente imposible en puro sh). Para hacerlo correctamente sin bash, necesitaría migrar completamente a awk/Perl/python/... sin canalizar esa salida para un procesamiento posterior o similar.

58
lhunath

¿"Shuf" no es portátil?

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

o encuentre si los archivos son más profundos que un directorio:

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

es parte de coreutils pero necesitará 6.4 o más reciente para obtenerlo ... por lo que RH/CentOS no lo incluye.

27
johnnyB
# ******************************************************************
# ******************************************************************
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
}
3
Pipo

Algo como:

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

$RANDOM en bash es una variable especial que devuelve un número aleatorio, luego uso la división de módulo para obtener un índice válido, luego hago referencia a ese índice en la matriz.

3
fido

Aquí hay un fragmento de Shell que se basa solo en las características POSIX y hace frente a nombres de archivo arbitrarios (pero omite los archivos de puntos de la selección). La selección aleatoria usa awk, porque eso es todo lo que obtienes en POSIX. Es un generador de números aleatorios muy pobre, ya que el RNG de awk se siembra con el tiempo actual en segundos (por lo que es fácilmente predecible y devuelve la misma opción si lo llama varias veces por segundo).

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

Si no desea ignorar los archivos de puntos, el código de generación de nombre de archivo (set -- *) debe reemplazarse por algo más complicado.

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

Si tiene OpenSSL disponible, puede usarlo para generar bytes aleatorios. Si no lo hace pero su sistema tiene /dev/urandom, reemplace la llamada a openssl por dd if=/dev/urandom bs=3 count=1 2>/dev/null. Aquí hay un fragmento que establece n en un valor aleatorio entre 1 y $#, teniendo cuidado de no introducir un sesgo. Este fragmento supone que $# es como máximo 2 ^ 23-1.

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

Las nuevas líneas en los nombres de archivo se pueden evitar haciendo lo siguiente en 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
2
gsbabil

Esto se reduce a: ¿Cómo puedo crear un número aleatorio en un script Unix de forma portátil?

Porque si tiene un número aleatorio entre 1 y N, puede usar head -$N | tail para cortar en algún lugar en el medio. Desafortunadamente, no conozco una forma portátil de hacer esto solo con Shell. Si tiene Python o Perl, puede usar fácilmente su compatibilidad aleatoria pero AFAIK, no hay un comando estándar Rand(1).

2
Aaron Digulla

Creo que Awk es una buena herramienta para obtener un número aleatorio. De acuerdo con Advanced Bash Guide , Awk es un buen reemplazo de números aleatorios para $RANDOM.

Aquí hay una versión de su script que evita los Bash-isms y las herramientas 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"

Hereda los problemas que otras respuestas han mencionado si los archivos contienen nuevas líneas.

2
ashawley

BusyBox (utilizado en dispositivos integrados) generalmente está configurado para admitir $RANDOM pero no tiene matrices de estilo bash o sort --random-sort o shuf. De ahí lo siguiente:

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

Nota final "-" en cut -f2-; Esto es necesario para evitar truncar archivos que contienen espacios (o cualquier separador que desee utilizar).

No manejará los nombres de archivo con nuevas líneas incrustadas correctamente.

1
Robert Calhoun

Coloque cada línea de salida del comando 'ls' en una matriz asociativa llamada línea y luego elija una de esas como ...

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

Mis 2 centavos, con una versión que no debería romperse cuando existen nombres de archivo con caracteres especiales:

#!/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"
0
Jay jargot