Najlepsza metoda na pobranie losowej próbki ze zbioru plików


23

Załóżmy, że istnieje katalog zawierający 300 plików danych. Chcę losowo wybrać 200 z tych plików i przenieść je do innego katalogu. Czy jest na to sposób w Unix / Linux?


R może to zrobić w mgnieniu oka z list.files()...
sr_

4
Chciałbym niejasno połączyć shufi head(lub po prostu użyć shuf -n, powinienem był przeczytać stronę podręcznika ...)
Ulrich Schwarz

Odpowiedzi:


32

Jeśli twój system ma shuf, możesz użyć tego dość wygodnie (nawet radząc sobie z brzydkimi nazwami plików):

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

Jeśli nie mają shuf, ale mają sortto trwa -R, to powinno działać:

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

7
Ach tak, ponieważ gdzie indziej można by szukać tasowania niż w narzędziu do sortowania. (Przynajmniej shufnie jest wywoływany, trosponieważ działa odwrotnie niż sortowanie).
Ulrich Schwarz

2
Nie ma czegoś takiego jak odwrotność sortowania (w tym samym sensie, że nie ma czegoś takiego jak „brak pogody”). Losowe jest nadal sortowane, jest po prostu losowo sortowane.
Nauczyciel

1
Co to jest „-zen200”? Nie ma tego w żadnej dokumentacji shufa ani nigdzie w Internecie, ale twój przykład bez niego nie działa. Całkiem mistyczne.
SigmaX,

2
@ SigmaX Rzeczywiście, całkiem zen, prawda? Wskazówka: to 3 osobne flagi.
Kevin

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

Umieść wszystkie nazwy plików w tablicy o nazwie „pliki” w bash:

files=( * )

rozmiar tablicy:

echo ${#files[@]}

zdefiniuj 2/3 z nich jako wielkość próby:

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

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

Spowoduje to wybranie duplikatów i tak jest nie testowane z nazwami plików z półfabrykatów i takie.

Najprostszym sposobem uniknięcia duplikatów jest iteracja wszystkich plików i wybranie każdego z szansą 2/3, ale niekoniecznie doprowadzi to do 200 plików.

Spowoduje to usunięcie pliku, jeśli został wybrany z listy i spełni twoje wymagania:

#!/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

Możesz wybrać ten sam plik więcej niż raz.
glenn jackman

Bardzo fajny skrypt powłoki. Aby poradzić sobie z problemem nieotrzymania 200 plików, prawdopodobnie powinieneś użyć Reservoir Sampling: en.wikipedia.org/wiki/Reservoir_sampling Będę słaby i nie będę zawierał tego przykładu.
Bruce Ediger,

@glennjackman: Tak napisałem. Potrzebowałem kilku minut, aby dowiedzieć się, jak usunąć wpisy z tablicy.
użytkownik nieznany

Drobne zastrzeżenie: $RANDOMmoże mieć tylko wartości od 0 do 32767, więc nie będzie działać poprawnie, jeśli masz więcej niż 32768 plików. Ponadto pobieranie jest ukierunkowane na pierwsze pliki.
l0b0

@ l0b0: Wymagania, aby wybrać 200 z 300. Jeśli pliki nie znajdują się w bieżącym katalogu, ale na serwerze plików, to też nie będzie działać. Różne wymagania, inna odpowiedź.
użytkownik nieznany

2

Jeśli to musi być statystycznie losowe, nie powinieneś używać RANDOM % ${#keys[@]}. Rozważać:

  1. $RANDOM ma 32768 unikalnych wartości
  2. Pierwszy wybór to 1 z 300 elementów
  3. 32768 = 109 * 300 + 68

Tak więc, wybierając pierwszy element, istnieje szansa 110/32768 ~ = 0,33569% dla każdego z 68 pierwszych elementów i 109/32768 ~ = 0,33264% szansy na każdy z pozostałych 232 elementów do wyboru. Wybieranie jest powtarzane kilka razy z różnymi szansami, ale zawsze jest ukierunkowane na pierwsze elementy 32768 % ${#keys[@]} -ne 0, więc błąd się pogłębia.

Powinno to być bezstronne i działa z dowolną nazwą pliku:

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

Rozwiązanie Kevina działa świetnie! Coś innego, z czego często korzystałem, ponieważ łatwiej jest zapamiętać to z góry:

cp `ls | shuf -n 200` destination

0

Jedna wkładka w bash:

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

Proszę opracować; U&L to baza wiedzy.
kontr-
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.