Найкращий спосіб зібрати випадкову вибірку з колекції файлів


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

Покладіть всі назви файлів у масив з назвою "файли" в 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 файлів, ви, мабуть, захочете скористатись вибіркою резервуарів: en.wikipedia.org/wiki/Reservoir_sampling Я буду слабким і не включаю приклад цього сценарію оболонки.
Брюс Едігер

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

Незначний застереження: $RANDOMможе мати значення лише від 0 до 32767, тому це не працюватиме належним чином, якщо у вас є більше 32768 файлів. Крім того, вилучення є упередженим щодо перших файлів.
l0b0

@ 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

Один лайнер в баші:

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

Будь ласка, докладно; U&L - це база знань.
контрмод
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.