¿Cómo puedo seleccionar archivos aleatorios de un directorio en bash?
144
Tengo un directorio con aproximadamente 2000 archivos. ¿Cómo puedo seleccionar una muestra aleatoria de Narchivos mediante el uso de un script bash o una lista de comandos canalizados?
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.
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:
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!
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 terribleintentoprá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.
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.
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.
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
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.
#!/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
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 arraydone
ls | shuf -n 5
Fuente de Unix StackexchangeRespuestas:
Aquí hay un script que usa la opción aleatoria de GNU sort:
fuente
"$file"
, no mostrado, sería sensible a los espacios.ls
?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:Ajuste el
-n, --head-count=COUNT
valor para devolver el número de líneas deseadas. Por ejemplo, para devolver 5 nombres de archivo aleatorios que usaría:fuente
N
archivos aleatorios, por lo que usar1
es un poco engañoso.find dirname -type f -print0 | shuf -zn1
Aquí hay algunas posibilidades que no analizan la salida
ls
y que son 100% seguras con respecto a los archivos con espacios y símbolos divertidos en su nombre. Todos ellos llenarán una matrizrandf
con una lista de archivos aleatorios. Esta matriz se imprime fácilmenteprintf '%s\n' "${randf[@]}"
si es necesario.Este posiblemente generará el mismo archivo varias veces y
N
debe conocerse de antemano. Aquí elegí N = 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 queN
no provenga directamente de la entrada del usuario sin ser revisado a fondo!Personalmente no me gusta
eval
y de ahí esta respuesta!Lo mismo con un método más directo (un bucle):
Si no quieres tener varias veces el mismo archivo:
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 terribleintentoprá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.fuente
"{1..42}"
parte dejara un rastro"1"
. Además,$RANDOM
solo tiene 15 bits y el método no funcionará con más de 32767 archivos para elegir.fuente
ls
. Esto no funcionará si, por ejemplo, un nombre de archivo contiene nuevas líneas.ls
no 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.ls
puede incluir directorios y líneas en blanco. Sugeriría algo así en sufind . -type f | shuf -n10
lugar.Una solución simple para seleccionar
5
archivos aleatorios y evitar analizar ls . También funciona con archivos que contienen espacios, líneas nuevas y otros caracteres especiales:Reemplace
echo
con el comando que desea ejecutar para sus archivos.fuente
read
tiene los mismos problemas que el análisisls
? a saber, lee línea por línea, por lo que no funciona para archivos con nuevas líneas en su nombreSi tiene instalado Python (funciona con Python 2 o Python 3):
Para seleccionar un archivo (o línea de un comando arbitrario), use
Para seleccionar
N
archivos / líneas, use (la nota seN
encuentra al final del comando, reemplácela por un número)fuente
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
eval
y 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,$a
cuyos 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 porls
.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:
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.txt
etc: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:
Esto activa una opción de shell que hace
**
que coincida recursivamente. Ahora su$a
matriz contiene todos los archivos en toda la jerarquía.fuente
Si tiene más archivos en su carpeta, puede usar el siguiente comando entubado que encontré en unix stackexchange .
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
.fuente
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/
fuente
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.
fuente
Yo uso esto: usa un archivo temporal pero va profundamente en un directorio hasta que encuentra un archivo normal y lo devuelve.
fuente
¿Qué tal una solución de Perl ligeramente modificada por el Sr. Kang aquí:
¿Cómo puedo mezclar las líneas de un archivo de texto en la línea de comandos de Unix o en un script de shell?
fuente