Как я могу выбрать случайные файлы из каталога в Bash? - PullRequest
112 голосов
/ 05 января 2009

У меня есть каталог с около 2000 файлов. Как я могу выбрать случайную выборку из N файлов, используя либо скрипт bash, либо список команд по конвейеру?

Ответы [ 12 ]

148 голосов
/ 05 января 2009

Вот скрипт, который использует случайную опцию сортировки GNU:

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
82 голосов
/ 04 сентября 2013

Для этого вы можете использовать shuf (из пакета GNU coreutils). Просто введите ему список имен файлов и попросите вернуть первую строку из случайной перестановки:

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

Настройте значение -n, --head-count=COUNT, чтобы получить количество нужных строк. Например, чтобы вернуть 5 случайных имен файлов, вы бы использовали:

find dirname -type f | shuf -n 5
18 голосов
/ 01 июля 2013

Вот несколько возможностей, которые не анализируют вывод ls и которые на 100% безопасны для файлов с пробелами и забавными символами в их имени. Все они будут заполнять массив randf списком случайных файлов. Этот массив легко печатается с помощью printf '%s\n' "${randf[@]}", если необходимо.

  • Этот файл, возможно, будет выводить один и тот же файл несколько раз, и N необходимо знать заранее. Здесь я выбрал N = 42.

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

    Эта функция не очень хорошо документирована.

  • Если N не известно заранее, но вам действительно понравилась предыдущая возможность, вы можете использовать eval. Но это зло, и вы должны действительно убедиться, что N не приходит напрямую от пользовательского ввода без тщательной проверки!

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

    Мне лично не нравится eval и, следовательно, этот ответ!

  • То же самое с использованием более простого метода (цикл):

    N=42
    a=( * )
    randf=()
    for((i=0;i<N;++i)); do
        randf+=( "${a[RANDOM%${#a[@]}]}" )
    done
    
  • Если вы не хотите иметь один и тот же файл несколько раз:

    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
    

Примечание . Это поздний ответ на старый пост, но принятый ответ ссылается на внешнюю страницу, которая показывает ужасную практику , а другой ответ не намного лучше, так как он также анализирует вывод ls. Комментарий к принятому ответу указывает на превосходный ответ Луната, который, очевидно, показывает хорошую практику, но не совсем отвечает ОП.

7 голосов
/ 15 сентября 2017
ls | shuf -n 10 # ten random files
7 голосов
/ 30 августа 2017

Простое решение для выбора 5 случайных файлов при , избегая анализа ls . Он также работает с файлами, содержащими пробелы, символы новой строки и другие специальные символы:

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

Замените echo на команду, которую вы хотите выполнить для ваших файлов.

4 голосов
/ 02 августа 2016

Это еще более поздний ответ на поздний ответ @ gniourf_gniourf, за который я только что проголосовал, потому что это, безусловно, лучший ответ, дважды. (Один раз для избежания eval и один раз для безопасной обработки имени файла.)

Но мне потребовалось несколько минут, чтобы распутать "не очень хорошо документированные" функции, которые использует этот ответ. Если ваши навыки Bash достаточно сильны, чтобы вы сразу увидели, как это работает, пропустите этот комментарий. Но я этого не сделал, и, распутав это, думаю, это стоит объяснить.

Feature # 1 - это глобальное копирование файла оболочки. a=(*) создает массив $a, членами которого являются файлы в текущем каталоге. Bash понимает все странности имен файлов, поэтому список гарантированно корректен, гарантированно экранирован и т. Д. Не нужно беспокоиться о правильном разборе текстовых имен файлов, возвращаемых ls.

Функция # 2 - это Bash Расширения параметров для массивов , одна вложена в другую. Это начинается с ${#ARRAY[@]}, который увеличивается до длины $ARRAY.

Это расширение затем используется для индексации массива. Стандартный способ найти случайное число от 1 до N состоит в том, чтобы взять значение случайного числа по модулю N. Нам нужно случайное число от 0 до длины нашего массива. Вот подход, разбитый на две строки для ясности:

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

Но это решение делает это в одной строке, удаляя ненужное присвоение переменной.

Feature # 3 is Расширение Bash Brace , хотя я должен признаться, я не совсем понимаю это. Расширение скобок используется, например, для создания списка из 25 файлов с именами filename1.txt, filename2.txt и т. Д .: echo "filename"{1..25}".txt".

Выражение внутри подоболочки выше, "${a[RANDOM%${#a[@]}]"{1..42}"}", использует этот трюк для создания 42 отдельных расширений. Расширение фигурных скобок помещает одну цифру между ] и }, которые сначала я думал, что подписывает массив, но если это так, ему предшествует двоеточие. (Он также возвратил бы 42 последовательных элемента из случайного места в массиве, что совсем не то же самое, что вернуть 42 случайных элемента из массива.) Я думаю, что это просто заставляет оболочку запускать расширение 42 раза, возвращая тем самым 42 случайных предмета из массива. (Но если кто-то сможет объяснить это более полно, я бы хотел услышать это.)

Причина, по которой N должен быть жестко задан (до 42), заключается в том, что расширение фигурной скобки происходит до расширения переменной.

Наконец, вот Feature # 4 , если вы хотите сделать это рекурсивно для иерархии каталогов:

shopt -s globstar
a=( ** )

Включает параметр оболочки , который приводит к рекурсивному совпадению **. Теперь ваш массив $a содержит каждый файл во всей иерархии.

4 голосов
/ 04 ноября 2015

Если у вас установлен Python (работает с Python 2 или Python 3):

Чтобы выбрать один файл (или строку из произвольной команды), используйте

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

Для выбора N файлов / строк используйте (примечание N находится в конце команды, замените его на число)

ls -1 | python -c "import sys; import random; print(''.join(random.sample(sys.stdin.readlines(), int(sys.argv[1]))).rstrip())" N
1 голос
/ 17 декабря 2017

MacOS не имеет команд sort -R и shuf , поэтому мне нужно было решение только для bash, которое рандомизирует все файлы без дубликатов и не нашло что здесь Это решение похоже на решение № 4 от gniourf_gniourf, но, надеюсь, добавляет лучшие комментарии.

Сценарий должен быть легко модифицирован для остановки после N выборок с использованием счетчика с if или цикла gniourf_gniourf for с N. $ RANDOM ограничен ~ 32000 файлами, но это должно быть в большинстве случаев.

#!/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
1 голос
/ 02 октября 2014

Это единственный скрипт, который я могу хорошо сыграть с bash на MacOS. Я соединил и отредактировал фрагменты из следующих двух ссылок:

Команда ls: как получить рекурсивный список полного пути, по одной строке на файл?

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
0 голосов
/ 28 марта 2019

Если в вашей папке больше файлов, вы можете использовать приведенную ниже команду, которую я нашел в unix stackexchange .

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

Здесь я хотел скопировать файлы, но если вы хотите переместить файлы или сделать что-то еще, просто измените последнюю команду, в которой я использовал cp.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...