¿Cómo puedo seleccionar archivos aleatorios de un directorio en bash?

Respuestas:

180

Aquí hay un script que usa la opción aleatoria de GNU sort:

ls |sort -R |tail -$N |while read file; do
    # Something involving $file, or you can leave
    # off the while to just get the filenames
done
Josh Lee
fuente
Genial, no sabía tipo -R; Utilicé bogosort anteriormente :-p
alex
55
ordenar: opción no válida - R Intente "ordenar - ayudar" para obtener más información.
2
No parece funcionar para archivos que tienen espacios en ellos.
Houshalter
Esto debería funcionar para archivos con espacios (la tubería procesa líneas). No funciona para nombres con nueva línea en ellos. Solo el uso de "$file", no mostrado, sería sensible a los espacios.
Yann Vernier
108

Puede usar shuf(del paquete GNU coreutils) para eso. Simplemente alimente una lista de nombres de archivos y pídale que devuelva la primera línea de una permutación aleatoria:

ls dirname | shuf -n 1
# probably faster and more flexible:
find dirname -type f | shuf -n 1
# etc..

Ajuste el -n, --head-count=COUNTvalor para devolver el número de líneas deseadas. Por ejemplo, para devolver 5 nombres de archivo aleatorios que usaría:

find dirname -type f | shuf -n 5
Mainframe nórdico
fuente
44
OP quería seleccionar Narchivos aleatorios, por lo que usar 1es un poco engañoso.
aioobe
44
Si tiene nombres de archivo con nuevas líneas:find dirname -type f -print0 | shuf -zn1
Hitechcomputergeek
55
¿Qué pasa si tengo que copiar estos archivos seleccionados al azar a otra carpeta? ¿Cómo realizar operaciones en estos archivos seleccionados al azar?
Rishabh Agrahari
18

Aquí hay algunas posibilidades que no analizan la salida lsy que son 100% seguras con respecto a los archivos con espacios y símbolos divertidos en su nombre. Todos ellos llenarán una matriz randfcon una lista de archivos aleatorios. Esta matriz se imprime fácilmente printf '%s\n' "${randf[@]}"si es necesario.

  • Este posiblemente generará el mismo archivo varias veces y Ndebe conocerse de antemano. Aquí elegí N = 42.

    a=( * )
    randf=( "${a[RANDOM%${#a[@]}]"{1..42}"}" )
    

    Esta característica no está muy bien documentada.

  • Si N no se conoce de antemano, pero realmente le gustó la posibilidad anterior, puede usar eval. Pero es malo, ¡y realmente debes asegurarte de que Nno provenga directamente de la entrada del usuario sin ser revisado a fondo!

    N=42
    a=( * )
    eval randf=( \"\${a[RANDOM%\${#a[@]}]\"\{1..$N\}\"}\" )
    

    Personalmente no me gusta evaly de ahí esta respuesta!

  • Lo mismo con un método más directo (un bucle):

    N=42
    a=( * )
    randf=()
    for((i=0;i<N;++i)); do
        randf+=( "${a[RANDOM%${#a[@]}]}" )
    done
    
  • Si no quieres tener varias veces el mismo archivo:

    N=42
    a=( * )
    randf=()
    for((i=0;i<N && ${#a[@]};++i)); do
        ((j=RANDOM%${#a[@]}))
        randf+=( "${a[j]}" )
        a=( "${a[@]:0:j}" "${a[@]:j+1}" )
    done
    

Nota . Esta es una respuesta tardía a una publicación anterior, pero la respuesta aceptada enlaza con una página externa que muestra un terriblepráctica, y la otra respuesta no es mucho mejor ya que también analiza la salida de ls. Un comentario a la respuesta aceptada apunta a una excelente respuesta de Lhunath que obviamente muestra una buena práctica, pero no responde exactamente al OP.

gniourf_gniourf
fuente
Primero y segundo produjeron "mala sustitución"; no le gustaba que la "{1..42}"parte dejara un rastro "1". Además, $RANDOMsolo tiene 15 bits y el método no funcionará con más de 32767 archivos para elegir.
Yann Vernier
13
ls | shuf -n 10 # ten random files
silgon
fuente
1
No debe confiar en la salida de ls. Esto no funcionará si, por ejemplo, un nombre de archivo contiene nuevas líneas.
bfontaine
3
@bfontaine parece perseguido por nuevas líneas en los nombres de archivo :). ¿Son realmente tan comunes? En otras palabras, ¿hay alguna herramienta que cree archivos con nuevas líneas en su nombre? Como usuario es muy difícil crear un nombre de archivo de este tipo. Lo mismo para los archivos que provienen de Internet
Ciprian Tomoiagă
3
@CiprianTomoiaga Ese es un ejemplo de los problemas que puede tener. lsno se garantiza que le dé nombres de archivo "limpios", por lo que no debe confiar en él, punto. El hecho de que estos problemas sean raros o inusuales no cambia el problema; especialmente dado que hay mejores soluciones para esto.
bfontaine
lspuede incluir directorios y líneas en blanco. Sugeriría algo así en su find . -type f | shuf -n10lugar.
cherdt
9

Una solución simple para seleccionar 5archivos aleatorios y evitar analizar ls . También funciona con archivos que contienen espacios, líneas nuevas y otros caracteres especiales:

shuf -ezn 5 * | xargs -0 -n1 echo

Reemplace echocon el comando que desea ejecutar para sus archivos.

scai
fuente
1
bueno, ¿la tubería + no readtiene los mismos problemas que el análisis ls? a saber, lee línea por línea, por lo que no funciona para archivos con nuevas líneas en su nombre
Ciprian Tomoiagă
3
Tienes razón. Mi solución anterior no funcionaba para los nombres de archivo que contenían nuevas líneas y probablemente también se rompe en otros con ciertos caracteres especiales. He actualizado mi respuesta para usar terminación nula en lugar de líneas nuevas.
scai
4

Si tiene instalado Python (funciona con Python 2 o Python 3):

Para seleccionar un archivo (o línea de un comando arbitrario), use

ls -1 | python -c "import sys; import random; print(random.choice(sys.stdin.readlines()).rstrip())"

Para seleccionar Narchivos / líneas, use (la nota se Nencuentra al final del comando, reemplácela por un número)

ls -1 | python -c "import sys; import random; print(''.join(random.sample(sys.stdin.readlines(), int(sys.argv[1]))).rstrip())" N
marca
fuente
Esto no funciona si su nombre de archivo contiene nuevas líneas.
bfontaine
4

Esta es una respuesta aún más tardía a la respuesta tardía de @ gniourf_gniourf, que acabo de votar porque es, con mucho, la mejor respuesta, dos veces. (Una vez para evitar evaly una vez para el manejo seguro de nombre de archivo).

Pero me tomó unos minutos desenredar las características "no muy bien documentadas" que utiliza esta respuesta. Si sus habilidades de Bash son lo suficientemente sólidas como para que haya visto de inmediato cómo funciona, omita este comentario. Pero no lo hice, y habiéndolo desenredado creo que vale la pena explicarlo.

La función n. ° 1 es el bloqueo de archivos del propio shell a=(*)crea una matriz, $acuyos miembros son los archivos en el directorio actual. Bash comprende todas las rarezas de los nombres de archivos, por lo que la lista está garantizada como correcta, garantizada como escape, etc. No es necesario preocuparse por analizar correctamente los nombres de archivo textuales devueltos por ls.

La característica # 2 es expansiones de parámetros Bash para matrices , una anidada dentro de otra. Esto comienza con ${#ARRAY[@]}, que se expande a lo largo de $ARRAY.

Esa expansión se utiliza para subíndice de la matriz. La manera estándar de encontrar un número aleatorio entre 1 y N es tomar el valor del número aleatorio módulo N. Queremos un número aleatorio entre 0 y la longitud de nuestra matriz. Aquí está el enfoque, dividido en dos líneas para mayor claridad:

LENGTH=${#ARRAY[@]}
RANDOM=${a[RANDOM%$LENGTH]}

Pero esta solución lo hace en una sola línea, eliminando la asignación de variables innecesarias.

La característica n. ° 3 es la expansión de la llave Bash , aunque debo confesar que no la entiendo completamente. Se utiliza la expansión de llaves, por ejemplo, para generar una lista de 25 archivos con el nombre filename1.txt, filename2.txtetc: echo "filename"{1..25}".txt".

La expresión dentro del subshell anterior, "${a[RANDOM%${#a[@]}]"{1..42}"}"usa ese truco para producir 42 expansiones separadas. La expansión de la llave coloca un solo dígito entre el ]y el }, que al principio pensé que estaba suscribiendo la matriz, pero de ser así estaría precedido por dos puntos. (También habría devuelto 42 elementos consecutivos desde un punto aleatorio en la matriz, lo que no es lo mismo que devolver 42 elementos aleatorios de la matriz). Creo que solo está haciendo que el shell ejecute la expansión 42 veces, devolviendo así 42 artículos al azar de la matriz. (Pero si alguien puede explicarlo más completamente, me encantaría escucharlo).

La razón por la que N tiene que estar codificado (a 42) es que la expansión de llaves ocurre antes de la expansión variable.

Finalmente, aquí está la Característica # 4 , si desea hacer esto de forma recursiva para una jerarquía de directorios:

shopt -s globstar
a=( ** )

Esto activa una opción de shell que hace **que coincida recursivamente. Ahora su $amatriz contiene todos los archivos en toda la jerarquía.

Conocido
fuente
2

Si tiene más archivos en su carpeta, puede usar el siguiente comando entubado que encontré en unix stackexchange .

find /some/dir/ -type f -print0 | xargs -0 shuf -e -n 8 -z | xargs -0 cp -vt /target/dir/

Aquí quería copiar los archivos, pero si desea mover archivos o hacer otra cosa, simplemente cambie el último comando donde lo he usado cp.

Bhaskar Chakradhar
fuente
1

Este es el único script que puedo jugar bien con bash en MacOS. Combiné y edité fragmentos de los siguientes dos enlaces:

Comando ls: ¿cómo puedo obtener una lista de ruta completa recursiva, una línea por archivo?

http://www.linuxquestions.org/questions/linux-general-1/is-there-a-bash-command-for-picking-a-random-file-678687/

#!/bin/bash

# Reads a given directory and picks a random file.

# The directory you want to use. You could use "$1" instead if you
# wanted to parametrize it.
DIR="/path/to/"
# DIR="$1"

# Internal Field Separator set to newline, so file names with
# spaces do not break our script.
IFS='
'

if [[ -d "${DIR}" ]]
then
  # Runs ls on the given dir, and dumps the output into a matrix,
  # it uses the new lines character as a field delimiter, as explained above.
  #  file_matrix=($(ls -LR "${DIR}"))

  file_matrix=($(ls -R $DIR | awk '; /:$/&&f{s=$0;f=0}; /:$/&&!f{sub(/:$/,"");s=$0;f=1;next}; NF&&f{ print s"/"$0 }'))
  num_files=${#file_matrix[*]}

  # This is the command you want to run on a random file.
  # Change "ls -l" by anything you want, it's just an example.
  ls -l "${file_matrix[$((RANDOM%num_files))]}"
fi

exit 0
benmarbles
fuente
1

MacOS no tiene los comandos sort -R y shuf , por lo que necesitaba una solución bash only que aleatorice todos los archivos sin duplicados y no lo encontré aquí. Esta solución es similar a la solución # 4 de gniourf_gniourf, pero con suerte agrega mejores comentarios.

El script debe ser fácil de modificar para detener después de N muestras usando un contador con if, o gniourf_gniourf's for loop with N. $ RANDOM está limitado a ~ 32000 archivos, pero eso debería ser para la mayoría de los casos.

#!/bin/bash

array=(*)  # this is the array of files to shuffle
# echo ${array[@]}
for dummy in "${array[@]}"; do  # do loop length(array) times; once for each file
    length=${#array[@]}
    randomi=$(( $RANDOM % $length ))  # select a random index

    filename=${array[$randomi]}
    echo "Processing: '$filename'"  # do something with the file

    unset -v "array[$randomi]"  # set the element at index $randomi to NULL
    array=("${array[@]}")  # remove NULL elements introduced by unset; copy array
done
gato
fuente
0

Yo uso esto: usa un archivo temporal pero va profundamente en un directorio hasta que encuentra un archivo normal y lo devuelve.

# find for a quasi-random file in a directory tree:

# directory to start search from:
ROOT="/";  

tmp=/tmp/mytempfile    
TARGET="$ROOT"
FILE=""; 
n=
r=
while [ -e "$TARGET" ]; do 
    TARGET="$(readlink -f "${TARGET}/$FILE")" ; 
    if [ -d "$TARGET" ]; then
      ls -1 "$TARGET" 2> /dev/null > $tmp || break;
      n=$(cat $tmp | wc -l); 
      if [ $n != 0 ]; then
        FILE=$(shuf -n 1 $tmp)
# or if you dont have/want to use shuf:
#       r=$(($RANDOM % $n)) ; 
#       FILE=$(tail -n +$(( $r + 1 ))  $tmp | head -n 1); 
      fi ; 
    else
      if [ -f "$TARGET"  ] ; then
        rm -f $tmp
        echo $TARGET
        break;
      else 
        # is not a regular file, restart:
        TARGET="$ROOT"
        FILE=""
      fi
    fi
done;
bzimage
fuente