Як я можу вибрати випадкові файли з каталогу в bash?


144

У мене каталог з приблизно 2000 файлами. Як я можу вибрати випадкову вибірку Nфайлів за допомогою скрипта bash або списку трубних команд?


1
Також хороша відповідь у Unix & Linux: unix.stackexchange.com/a/38344/24170
Nikana Reklawyks


Відповіді:


180

Ось сценарій, який використовує випадковий параметр сортування GNU:

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

Класно, не знав сорту -R; Раніше я використовував bogosort :-p
alex

5
сортувати: недійсний варіант - R Спробуйте `сортувати - допомогти 'для отримання додаткової інформації.

2
Схоже, це не працює для файлів, у яких є пробіли.
Houshalter

Це повинно працювати для файлів з пробілами (конвеєр обробляє лінії). Він не працює для імен з новим рядком у них. Тільки використання "$file", не показане, було б чутливим до просторів.
Yann Vernier


108

Ви можете використовувати для цього shuf(з пакету GNU coreutils) для цього. Просто подайте йому список імен файлів і попросіть повернути перший рядок із випадкової перестановки:

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

Відрегулюйте -n, --head-count=COUNTзначення, щоб повернути кількість шуканих рядків. Наприклад, щоб повернути 5 випадкових імен файлів, які ви використовуєте:

find dirname -type f | shuf -n 5

4
ОП хотів вибрати Nвипадкові файли, тому використання 1трохи вводить в оману.
aioobe

4
Якщо у вас є назви файлів з новими рядками:find dirname -type f -print0 | shuf -zn1
Hitechcomputergeek

5
що робити, якщо мені доведеться скопіювати ці випадково вибрані файли в іншу папку? як виконувати операції над цими випадково вибраними файлами?
Рішабх Аграхарі

18

Ось декілька можливостей, які не розбирають вихід lsі які є на 100% безпечними щодо файлів із пробілами та смішними символами в їх імені. Усі вони заповнять масив randfзі списком випадкових файлів. Цей масив легко друкується за printf '%s\n' "${randf[@]}"потреби.

  • Цей, можливо, виведе один і той же файл кілька разів, і це Nпотрібно знати заздалегідь. Тут я вибрав N = 42.

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

    Ця особливість не дуже добре зафіксована.

  • Якщо N невідомо заздалегідь, але вам сподобалась попередня можливість, ви можете скористатися eval. Але це зло, і ви дійсно повинні переконатися, що Nвін не надходить безпосередньо з вводу користувача, не ретельно перевіряючи!

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

    Мені особисто не подобається evalі звідси ця відповідь!

  • Те ж саме, використовуючи більш прямий метод (цикл):

    N=42
    a=( * )
    randf=()
    for((i=0;i<N;++i)); do
        randf+=( "${a[RANDOM%${#a[@]}]}" )
    done
  • Якщо ви не хочете мати один раз один і той же файл:

    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

Примітка . Це пізня відповідь на стару публікацію, але прийнята відповідь посилається на зовнішню сторінку, яка показує жахливуПрактика, а інша відповідь не набагато краща, оскільки вона також аналізує результати виробництва ls. Коментар до прийнятої відповіді вказує на відмінну відповідь Луната, яка, очевидно, показує добру практику, але не відповідає точно ОП.


Перше і друге призвели до «поганої заміни»; це не сподобалось, що "{1..42}"частина залишає слід "1". Крім того, $RANDOMце лише 15 біт, і метод не працюватиме з більш ніж 32767 файлами на вибір.
Yann Vernier

13
ls | shuf -n 10 # ten random files

1
Ви не повинні покладатися на результат ls. Це не спрацює, якщо наприклад, ім'я файлу містить нові рядки.
bfontaine

3
@bfontaine вас, здається, переслідують нові рядки у назвах файлів :). Чи справді вони такі поширені? Іншими словами, чи існує якийсь інструмент, який створює файли з новими рядками на їх ім’я? Оскільки, як користувач, дуже важко створити таке ім’я файлу. Те саме для файлів, що надходять з Інтернету
Ciprian Tomoiagă

3
@CiprianTomoiaga Це приклад проблем, які можуть виникнути. lsне гарантовано дасть вам "чисті" імена файлів, тому не слід покладатися на неї, період. Той факт, що ці питання рідкісні чи незвичні, не змінює проблеми; особливо враховуючи, що для цього є кращі рішення.
bfontaine

lsможуть містити каталоги та порожні рядки. Я б find . -type f | shuf -n10замість цього запропонував щось подібне .
cherdt

9

Просте рішення для вибору 5випадкових файлів, уникаючи розбору ls . Він також працює з файлами, що містять пробіли, нові рядки та інші спеціальні символи:

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

Замініть echoкоманду, яку потрібно виконати для своїх файлів.


1
добре, чи не має труба + readті самі проблеми, що і розбір ls? а саме він читає рядок за рядком, тому не працює для файлів із новими рядками на їх ім’я
Ciprian Tomoiagă

3
Ти правий. Моє попереднє рішення не працювало для імен файлів, що містять нові рядки, і, ймовірно, переривається на інших з певними спеціальними символами. Я оновив свою відповідь, щоб використовувати нульове закінчення замість нових рядків.
scai

4

Якщо у вас встановлений Python (працює з Python 2 або Python 3):

Щоб вибрати один файл (або рядок з довільної команди), використовуйте

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

Щоб вибрати Nфайли / рядки, використовуйте (замітка N- в кінці команди, замініть це цифрою)

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

Це не працює, якщо ваше ім'я файлу містить нові рядки.
bfontaine

4

Це ще пізніша відповідь на пізню відповідь @ gniourf_gniourf, яку я просто схвалив, оскільки це, безумовно, найкраща відповідь, двічі. (Один раз для уникнення evalта один раз для безпечної обробки імені файлу.)

Але мені знадобилося кілька хвилин, щоб розплутати "не дуже добре задокументовані" функції, які використовується у цій відповіді. Якщо ваші навички Баша досить міцні, що ви відразу побачили, як це працює, пропустіть цей коментар. Але я цього не зробив, і, розплутавши це, я думаю, що це варто пояснити.

Особливість №1 - це власний файл оболонки оболонки. a=(*)створює масив, $aчленами якого є файли в поточному каталозі. Bash розуміє всі дивні назви файлів, так що цей список гарантовано правильний, гарантовано уникнути тощо. Не потрібно турбуватися про правильний аналіз імен текстових файлів, що повертаються ls.

Особливість №2 - розширення параметрів Bash для масивів , вкладених в інший. Це починається з того ${#ARRAY[@]}, що розширюється до довжини $ARRAY.

Потім розширення використовується для підписки масиву. Стандартний спосіб знайти випадкове число між 1 і N - це взяти значення модуля випадкового числа N. Ми хочемо випадкове число між 0 і довжиною нашого масиву. Ось підхід, розбитий на два рядки задля наочності:

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

Але це рішення робить це в один рядок, видаляючи зайве призначення змінної.

Особливість №3 - це розширення брекетів Bash , хоча, зізнаюся, я не зовсім це розумію. Фігурні дужки використовуються, наприклад, для формування списку з 25 файлів з іменами filename1.txt, filename2.txtі т.д .: echo "filename"{1..25}".txt".

Вираз усередині нижньої підрозділу "${a[RANDOM%${#a[@]}]"{1..42}"}"використовує цей трюк для створення 42 окремих розширень. Розширення дужок розміщує одну цифру між і ]та }, що спочатку я вважав, що підписує масив, але якщо так, то йому передуватиме двокрапка. (Він також повернув би 42 послідовні елементи з випадкової точки в масиві, що зовсім не те саме, що повернути 42 випадкових елемента з масиву.) Я думаю, що це просто змушує оболонку запустити розширення в 42 рази, тим самим повернувшись 42 випадкових елемента з масиву. (Але якщо хтось може це пояснити більш повно, я б хотів це почути.)

Причина N має бути жорстко кодованою (до 42) в тому, що розширення дужок відбувається перед змінним розширенням.

Нарешті, ось функція №4 , якщо ви хочете зробити це рекурсивно для ієрархії каталогів:

shopt -s globstar
a=( ** )

Це вмикає варіант оболонки, який призводить **до відповідності рекурсивно. Тепер ваш $aмасив містить кожен файл у всій ієрархії.


2

Якщо у вашій папці є більше файлів, ви можете скористатися наведеною нижче командою, що я знайшов в unix stackexchange .

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

Тут я хотів скопіювати файли, але якщо ви хочете перемістити файли або зробити щось інше, просто змініть останню команду там, де я використав cp.


1

Це єдиний сценарій, який мені вдається грати добре з bash на MacOS. Я поєднав і відредагував фрагменти з наступних двох посилань:

ls команда: як я можу отримати рекурсивний повний контур, один рядок у файлі?

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 немає команд сортування -R та shuf , тому мені було потрібне рішення лише для bash, яке рандомізує всі файли без дублікатів і не знайшло цього тут. Це рішення схоже на рішення gniourf_gniourf №4, але, сподіваємось, додає кращі коментарі.

Сценарій повинен бути легко модифікований, щоб зупинитися після N зразків, використовуючи лічильник з if, або gniourf_gniourf's для циклу з N. $ RANDOM обмежений ~ 32000 файлами, але це повинно робитись у більшості випадків.

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

Я використовую це: він використовує тимчасовий файл, але заглиблюється в каталог, поки не знайде звичайний файл і не поверне його.

# 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;

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