Mam katalog zawierający około 2000 plików. Jak mogę wybrać losową próbkę N
plików za pomocą skryptu bash lub listy poleceń potokowych?
ls | shuf -n 5
Źródło z Unix Stackexchange
Mam katalog zawierający około 2000 plików. Jak mogę wybrać losową próbkę N
plików za pomocą skryptu bash lub listy poleceń potokowych?
ls | shuf -n 5
Źródło z Unix Stackexchange
Odpowiedzi:
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
"$file"
, nie pokazane, byłoby wrażliwe na spacje.
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=COUNT
wartość, 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
N
losowe pliki, więc użycie 1
jest nieco mylące.
find dirname -type f -print0 | shuf -zn1
Oto kilka możliwości, które nie analizują wyniku programu ls
i są w 100% bezpieczne w przypadku plików ze spacjami i zabawnymi symbolami w nazwie. Wszystkie z nich wypełnią tablicę randf
listą 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 N
musi 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 N
nie 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ę eval
i 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 okropnegrzmotnąćpraktyki, 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.
"{1..42}"
część pozostawia ślad "1"
. Ponadto $RANDOM
jest tylko 15-bitowy, a metoda nie będzie działać z ponad 32767 plikami do wyboru.
ls | shuf -n 10 # ten random files
ls
. To nie zadziała, jeśli np. Nazwa pliku zawiera znaki nowej linii.
ls
nie 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.
ls
może zawierać katalogi i puste wiersze. find . -type f | shuf -n10
Zamiast tego sugerowałbym coś takiego .
Proste rozwiązanie do wybierania 5
losowych 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 echo
polecenie, które chcesz wykonać dla swoich plików.
read
ma takich samych problemów jak parsowanie ls
? mianowicie, czyta wiersz po wierszu, więc nie działa w przypadku plików z
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ć N
pliki / linie, użyj (uwaga N
jest 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 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ąć eval
i 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ę, $a
któ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.txt
itp: 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 $a
tablica zawiera każdy plik w całej hierarchii.
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
.
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?
#!/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
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
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;
A co z rozwiązaniem w Perlu, które nieco przerobił pan Kang tutaj:
Jak mogę przetasować wiersze w pliku tekstowym w linii poleceń Unixa lub w skrypcie powłoki?
$ ls | perl -MList :: Util = shuffle -e '@lines = shuffle (<>); print @lines [0..4] '