Лучший способ собрать случайную выборку из коллекции файлов


23

Предположим, есть каталог, содержащий 300 файлов данных. Я хочу случайным образом выбрать 200 из этих файлов и переместить их в другой каталог. Есть ли способ сделать это под Unix / Linux?


R, вероятно, может сделать это в мгновение ока с list.files()...
sr_

4
Я смутно соединяюсь shufи head(или просто использую shuf -n, должен был прочитать страницу руководства ...)
Ульрих Шварц

Ответы:


32

Если ваша система имеет shuf, вы можете использовать это довольно удобно (даже обрабатывая уродливые имена файлов):

shuf -zen200 source/* | xargs -0 mv -t dest

Если у вас нет, shufно есть, sortчто нужно -R, это должно работать:

find source -type f -print0 | sort -Rz | cut -d $'\0' -f-200 | xargs -0 mv -t dest

7
Ах, да, потому что где еще можно искать перетасовки, чем в инструменте для сортировки. (По крайней мере shuf, не вызывается, trosпотому что это противоположно сортировке.)
Ульрих Шварц

2
Нет такой вещи, как обратная сортировка (в том же смысле, что нет такой вещи, как «нет погоды»). Случайное все еще отсортировано, оно просто отсортировано случайно.
Plutor

1
Что такое "-zen200"? Этого нет ни в одной документации по shuf или где-либо в Интернете, но ваш пример не работает без него. Довольно мистический
SigmaX

2
@ SigmaX Действительно, совсем дзен, не так ли? Подсказка: это 3 отдельных флага.
Кевин

2
files=(*)
for (( i=0; i<200; i++ )); do
    keys=("${!files[@]}")
    rnd=$(( RANDOM % ${#keys[@]} ))
    key=${keys[$rnd]}
    mv "${files[$key]}" "$otherdir"
    unset files[$key]
done

2

Поместите все имена файлов в массив с именем "files" в bash:

files=( * )

размер массива:

echo ${#files[@]}

определите 2/3 из них как размер выборки:

take=$((2*${#files[@]}/3)) 

for i in $(seq 1 $take)
do
    r=$((RANDOM%${#files[@]})) 
    echo ${files[r]}
done

Это будет выбрать дубликаты, и это не протестировано с именами файлов с пробелами и такими.

Самый простой способ избежать дублирования - перебирать все файлы и выбирать каждый с вероятностью 2/3, но это не обязательно приведет к 200 файлам.

Это удалит файл, если он был выбран из списка, и выполнит ваши требования:

#!/bin/bash
files=( * )
# define 2/3 of them as sample size:
take=$((2*${#files[@]}/3)) 

while (( i < $take ))
do
    r=$((RANDOM%${#files[@]})) 
    f=${files[r]}
    if [[ -n $f ]]
    then 
        i=$((i+1))    
        echo ${files[r]}
        unset files[r]    
    fi
done

Вы можете выбрать один и тот же файл более одного раза.
Гленн Джекман

Очень хороший сценарий оболочки. Чтобы обойти вашу проблему с нехваткой 200 файлов, вы, вероятно, захотите использовать Reservoir Sampling: en.wikipedia.org/wiki/Reservoir_sampling Я собираюсь быть слабым и не буду включать пример сценария оболочки.
Брюс Эдигер

@glennjackman: я так и написал, да. Потребовалось несколько минут, чтобы понять, как удалить записи из массива.
пользователь неизвестен

Незначительное предостережение: $RANDOMможет иметь значения только от 0 до 32767, поэтому это не будет работать должным образом, если у вас более 32768 файлов. Кроме того, выборка смещена к первым файлам.
2010 года

@ l0b0: Требования, где выбрать 200 из 300. Если файлы находятся не в текущем каталоге, а на файловом сервере, он также не будет работать. Разные требования, разные ответы.
пользователь неизвестен

2

Если это должно быть статистически случайным, вы не должны использовать RANDOM % ${#keys[@]}. Рассмотреть возможность:

  1. $RANDOM имеет 32768 уникальных значений
  2. Первый выбор - 1 из 300 элементов
  3. 32768 = 109 * 300 + 68

Таким образом, при выборе первого элемента есть вероятность 110/32768 ~ = 0,33569% для каждого из 68 первых элементов, а вероятность 109/32768 ~ = 0,33264% для каждого из остальных 232 элементов, которые будут выбраны. Сбор повторяется несколько раз с разными шансами, но всегда смещен к первым элементам 32768 % ${#keys[@]} -ne 0, поэтому ошибка усугубляется.

Это должно быть беспристрастным , и работает с любым именем файла:

while IFS= read -r -d '' -u 9
do
    mv -- "$REPLY" /target/dir
done 9< <(find /source/dir -mindepth 1 -print0 | shuf -n 200 -z)

2

Решение Кевина прекрасно работает! Что-то еще, что я часто использовал, потому что мне легче вспомнить с головы до головы, это что-то вроде:

cp `ls | shuf -n 200` destination

0

Один лайнер в Bash:

ls original_directory/|sort -R|head -number_of_files_to_move|while read file; do cp "new_directory/"$file test; done

Пожалуйста, дополните; U & L - это база знаний.
countermode
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.