Jak mogę wybrać losowe pliki z katalogu w bash?


Odpowiedzi:


180

Oto skrypt, który używa opcji losowej GNU sort:

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

Fajnie, nie wiedziałem sort -R; Używałem Bogosort wcześniej :-p
alex

5
sort: nieprawidłowa opcja - R Spróbuj użyć `sort --help ', aby uzyskać więcej informacji.

2
Wydaje się, że nie działa w przypadku plików zawierających spacje.
Houshalter

Powinno to działać w przypadku plików ze spacjami (potok przetwarza linie). Nie działa w przypadku nazw zawierających znak nowej linii. Tylko użycie "$file", nie pokazane, byłoby wrażliwe na spacje.
Yann Vernier


108

Możesz do tego użyć shuf(z pakietu GNU coreutils). Po prostu podaj listę nazw plików i poproś o zwrócenie pierwszej linii z losowej permutacji:

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

Dostosuj -n, --head-count=COUNTwartość, aby zwrócić liczbę żądanych wierszy. Na przykład, aby zwrócić 5 losowych nazw plików, których użyłbyś:

find dirname -type f | shuf -n 5

4
OP chciał wybierać Nlosowe pliki, więc użycie 1jest nieco mylące.
aioobe

4
Jeśli masz nazwy plików z nowymi wierszami:find dirname -type f -print0 | shuf -zn1
Hitechcomputergeek

5
co się stanie, jeśli będę musiał skopiować te losowo wybrane pliki do innego folderu? jak wykonać operacje na tych losowo wybranych plikach?
Rishabh Agrahari

18

Oto kilka możliwości, które nie analizują wyniku programu lsi są w 100% bezpieczne w przypadku plików ze spacjami i zabawnymi symbolami w nazwie. Wszystkie z nich wypełnią tablicę randflistą losowych plików. W printf '%s\n' "${randf[@]}"razie potrzeby tablicę tę można łatwo wydrukować .

  • Ten prawdopodobnie wyprowadzi ten sam plik kilka razy i Nmusi być znany z wyprzedzeniem. Tutaj wybrałem N = 42.

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

    Ta funkcja nie jest dobrze udokumentowana.

  • Jeśli N nie jest znane z góry, ale bardzo podobała Ci się poprzednia możliwość, możesz użyć eval. Ale to jest złe i naprawdę musisz się upewnić, że Nnie pochodzi bezpośrednio z danych wejściowych użytkownika bez dokładnego sprawdzenia!

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

    Osobiście nie lubię evali stąd ta odpowiedź!

  • To samo przy użyciu prostszej metody (pętla):

    N=42
    a=( * )
    randf=()
    for((i=0;i<N;++i)); do
        randf+=( "${a[RANDOM%${#a[@]}]}" )
    done
  • Jeśli nie chcesz mieć kilkukrotnie tego samego pliku:

    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

Uwaga . To jest późna odpowiedź na stary post, ale zaakceptowana odpowiedź prowadzi do zewnętrznej strony, która pokazuje okropnepraktyki, a druga odpowiedź nie jest dużo lepsza, ponieważ analizuje również wynik ls. Komentarz do zaakceptowanej odpowiedzi wskazuje na doskonałą odpowiedź Lhunatha, która oczywiście pokazuje dobrą praktykę, ale nie odpowiada dokładnie PO.


Pierwsza i druga spowodowały „złe zastąpienie”; nie podobało mu się, że "{1..42}"część pozostawia ślad "1". Ponadto $RANDOMjest tylko 15-bitowy, a metoda nie będzie działać z ponad 32767 plikami do wyboru.
Yann Vernier

13
ls | shuf -n 10 # ten random files

1
Nie powinieneś polegać na wynikach ls. To nie zadziała, jeśli np. Nazwa pliku zawiera znaki nowej linii.
bfontaine,

3
@bfontaine wydajesz się nawiedzać przez znaki nowej linii w nazwach plików :). Czy naprawdę są tak powszechne? Innymi słowy, czy jest jakieś narzędzie, które tworzy pliki ze znakami nowej linii w nazwie? Ponieważ jako użytkownik bardzo trudno jest utworzyć taką nazwę pliku. To samo dotyczy plików pochodzących z internetu
Ciprian Tomoiagă

3
@CiprianTomoiaga To przykład problemów, które możesz napotkać. lsnie gwarantuje, że podasz "czyste" nazwy plików, więc nie powinieneś na tym polegać. Fakt, że te problemy są rzadkie lub niezwykłe, nie zmienia problemu; zwłaszcza biorąc pod uwagę, że istnieją lepsze rozwiązania tego problemu.
bfontaine

lsmoże zawierać katalogi i puste wiersze. find . -type f | shuf -n10Zamiast tego sugerowałbym coś takiego .
cherdt

9

Proste rozwiązanie do wybierania 5losowych plików bez analizowania ls . Działa również z plikami zawierającymi spacje, znaki nowej linii i inne znaki specjalne:

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

Zastąp echopolecenie, które chcesz wykonać dla swoich plików.


1
no cóż, czy potok + nie readma takich samych problemów jak parsowanie ls? mianowicie, czyta wiersz po wierszu, więc nie działa w przypadku plików z
znakami

3
Masz rację. Moje poprzednie rozwiązanie nie działało w przypadku nazw plików zawierających nowe linie i prawdopodobnie przerwy w innych z określonymi znakami specjalnymi. Zaktualizowałem moją odpowiedź, aby używała zakończenia zerowego zamiast nowej linii.
scai

4

Jeśli masz zainstalowany Python (działa z Pythonem 2 lub Pythonem 3):

Aby wybrać jeden plik (lub wiersz z dowolnego polecenia), użyj

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

Aby wybrać Npliki / linie, użyj (uwaga Njest na końcu polecenia, zastąp to liczbą)

ls -1 | python -c "import sys; import random; print(''.join(random.sample(sys.stdin.readlines(), int(sys.argv[1]))).rstrip())" N

To nie działa, jeśli nazwa pliku zawiera znaki nowej linii.
bfontaine

4

To jest jeszcze późniejsza odpowiedź na późną odpowiedź @ gniourf_gniourf, na którą właśnie głosowałem, ponieważ jest to zdecydowanie najlepsza odpowiedź, dwukrotnie. (Raz, aby uniknąć evali raz, aby bezpiecznie obsługiwać nazwy plików).

Ale zajęło mi kilka minut, aby rozplątać „niezbyt dobrze udokumentowane” funkcje, których używa ta odpowiedź. Jeśli twoje umiejętności Bash są na tyle solidne, że od razu zobaczyłeś, jak to działa, pomiń ten komentarz. Ale tego nie zrobiłem i po rozplątaniu tego uważam, że warto to wyjaśnić.

Cechą # 1 jest globalizacja plików własnej powłoki. a=(*)tworzy tablicę, $aktórej elementami są pliki w bieżącym katalogu. Bash rozumie wszystkie dziwactwa związane z nazwami plików, dzięki czemu lista jest poprawna, gwarantowana ucieczka itp. Nie musisz się martwić o poprawną analizę nazw plików tekstowych zwracanych przez ls.

Cechą nr 2 jest rozszerzenie parametrów Bash dla tablic , jedna zagnieżdżona w drugiej. Zaczyna się od ${#ARRAY[@]}, który rozwija się do długości $ARRAY.

To rozwinięcie jest następnie używane do indeksowania tablicy. Standardowym sposobem znalezienia liczby losowej z przedziału od 1 do N jest przyjęcie wartości liczby losowej modulo N. Chcemy uzyskać liczbę losową z przedziału od 0 do długości naszej tablicy. Oto podejście, dla jasności podzielone na dwie linie:

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

Ale to rozwiązanie robi to w jednym wierszu, eliminując niepotrzebne przypisywanie zmiennych.

Cechą # 3 jest rozszerzenie klamry Bash , chociaż muszę przyznać, że nie do końca to rozumiem. Interpretacja nawiasów jest używany, na przykład, aby wygenerować listę 25 plików nazwanych filename1.txt, filename2.txtitp: echo "filename"{1..25}".txt".

Wyrażenie wewnątrz podpowłoki powyżej, "${a[RANDOM%${#a[@]}]"{1..42}"}"używa tej sztuczki do stworzenia 42 oddzielnych ekspansji. Rozwinięcie nawiasów klamrowych umieszcza pojedynczą cyfrę między znakami ]i }, co na początku myślałem, że indeksuje tablicę, ale jeśli tak, byłoby poprzedzone dwukropkiem. (Zwróciłoby to również 42 kolejne elementy z losowego miejsca w tablicy, co wcale nie jest tym samym, co zwrócenie 42 losowych elementów z tablicy.) Myślę, że powoduje to po prostu wykonanie przez powłokę rozszerzenia 42 razy, zwracając w ten sposób 42 losowe pozycje z tablicy. (Ale jeśli ktoś może to wyjaśnić dokładniej, z przyjemnością to usłyszę.)

Powodem, dla którego N musi być zakodowane na stałe (do 42) jest to, że rozwijanie nawiasów klamrowych następuje przed rozwinięciem zmiennych.

Na koniec funkcja nr 4 , jeśli chcesz to zrobić rekurencyjnie dla hierarchii katalogów:

shopt -s globstar
a=( ** )

Włącza to opcję powłoki, która powoduje **rekursywne dopasowywanie. Teraz twoja $atablica zawiera każdy plik w całej hierarchii.


2

Jeśli masz więcej plików w swoim folderze, możesz użyć poniższego polecenia potokowego, które znalazłem w unix stackexchange .

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

Tutaj chciałem skopiować pliki, ale jeśli chcesz przenieść pliki lub zrobić coś innego, po prostu zmień ostatnie polecenie, w którym użyłem cp.


1

To jedyny skrypt, który mogę dobrze zagrać w bash na MacOS. Połączyłem i zredagowałem fragmenty z następujących dwóch linków:

Polecenie ls: jak mogę uzyskać rekurencyjną listę pełnej ścieżki, po jednej linii na plik?

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

1

MacOS nie ma poleceń sort -R i shuf , więc potrzebowałem rozwiązania tylko dla basha, które losuje wszystkie pliki bez duplikatów i nie znalazłem tego tutaj. To rozwiązanie jest podobne do rozwiązania # 4 gniourf_gniourf, ale miejmy nadzieję, że dodaje lepsze komentarze.

Skrypt powinien być łatwy do zmodyfikowania i zatrzymania po N próbkach przy użyciu licznika z pętlą if lub gniourf_gniourf z N. $ RANDOM jest ograniczone do ~ 32000 plików, ale powinno to wystarczyć w większości przypadków.

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

0

Używam tego: używa pliku tymczasowego, ale wnika głęboko w katalog, dopóki nie znajdzie zwykłego pliku i nie zwróci go.

# find for a quasi-random file in a directory tree:

# directory to start search from:
ROOT="/";  

tmp=/tmp/mytempfile    
TARGET="$ROOT"
FILE=""; 
n=
r=
while [ -e "$TARGET" ]; do 
    TARGET="$(readlink -f "${TARGET}/$FILE")" ; 
    if [ -d "$TARGET" ]; then
      ls -1 "$TARGET" 2> /dev/null > $tmp || break;
      n=$(cat $tmp | wc -l); 
      if [ $n != 0 ]; then
        FILE=$(shuf -n 1 $tmp)
# or if you dont have/want to use shuf:
#       r=$(($RANDOM % $n)) ; 
#       FILE=$(tail -n +$(( $r + 1 ))  $tmp | head -n 1); 
      fi ; 
    else
      if [ -f "$TARGET"  ] ; then
        rm -f $tmp
        echo $TARGET
        break;
      else 
        # is not a regular file, restart:
        TARGET="$ROOT"
        FILE=""
      fi
    fi
done;

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.