Как выбрать случайные файлы из каталога в bash?

У меня есть каталог с примерно 2000 файлами. Как выбрать произвольную выборку файлов N с помощью либо bash script, либо списка команд с каналами?

Ответ 1

Здесь script, который использует случайную опцию сортировки 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

Ответ 2

Для этого вы можете использовать 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

Ответ 3

Вот несколько возможностей, которые не анализируют вывод 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
    

Примечание. Это поздний ответ на старое сообщение, но принятый ответ ссылается на внешнюю страницу, которая показывает страшный bash, а другой ответ не намного лучше, так как он также анализирует вывод ls. Комментарий к принятому ответу указывает на отличный ответ Лхунатха, который, очевидно, показывает хорошую практику, но точно не отвечает OP.

Ответ 4

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

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

Замените echo командой, которую вы хотите выполнить для ваших файлов.

Ответ 5

ls | shuf -n 10 # ten random files

Ответ 6

Если у вас установлен 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

Ответ 7

Это еще более поздний ответ на поздний ответ @gniourf_gniourf, который я только что поддержал, потому что он, безусловно, лучший ответ, дважды. (Один раз для избежания eval и один раз для безопасной обработки имен файлов.)

Но мне потребовалось несколько минут, чтобы распутать "не очень хорошо документированные" функции (-ы), которые использует этот ответ. Если ваши навыки Bash достаточно прочные, что вы сразу увидели, как это работает, пропустите этот комментарий. Но я этого не сделал, и, распутывая его, я думаю, что это стоит объяснить.

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

Функция # 2 - Bash расширения параметров для arrays, один вложен в другой. Это начинается с ${#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=( ** )

Это включает опцию shell, которая приводит к тому, что ** будет соответствовать рекурсивно. Теперь ваш массив $a содержит все файлы во всей иерархии.

Ответ 8

Это единственный script, с которым я могу играть с bash в MacOS. Я объединил и отредактировал фрагменты из следующих двух ссылок:

ls command: как я могу получить рекурсивный список полного пути, по одной строке на файл?

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 just an example.
  ls -l "${file_matrix[$((RANDOM%num_files))]}"
fi

exit 0

Ответ 9

В MacOS нет команд sort -R и shuf, поэтому мне понадобилось только решение bash, которое рандомизировало все файлы без дубликатов и не нашло здесь. Это решение похоже на решение gniourf_gniourf # 4, но, надеюсь, добавляет лучшие комментарии.

script следует легко модифицировать, чтобы остановить после N выборок с помощью счетчика с if или gniourf_gniourf для цикла с 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

Ответ 10

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

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

Ответ 11

Если у вас в папке больше файлов, вы можете использовать приведенную ниже команду, которую я нашел в unix stackexchange.

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

Здесь я хотел скопировать файлы, но если вы хотите переместить файлы или сделать что-то еще, просто измените последнюю команду, в которой я использовал cp.