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

144

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

Марло Гатри
источник
1
Также хороший ответ для Unix и Linux: unix.stackexchange.com/a/38344/24170
Никана Рекламикс
16
ls | shuf -n 5 Источник от Unix Stackexchange
jgomo3
Аналогично: stackoverflow.com/questions/2153882/…
AAAfarmclub

Ответы:

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
sort: неверный параметр - R Попробуйте `sort --help 'для получения дополнительной информации.
2
Кажется, не работает для файлов, в которых есть пробелы.
Houshalter
Это должно работать для файлов с пробелами (конвейер обрабатывает строки). Это не работает для имен с новой строкой в ​​них. Только использование "$file", не показанное, будет чувствительным к пробелам.
Янн Вернье
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. Комментарий к принятому ответу указывает на превосходный ответ Луната, который явно демонстрирует хорошую практику, но не совсем отвечает ОП.

gniourf_gniourf
источник
Первый и второй произвели «плохую замену»; ему не нравилась "{1..42}"часть, оставляющая след "1". Кроме того, $RANDOMтолько 15 бит, и метод не будет работать с более чем 32767 файлами на выбор.
Янн Вернье
13
ls | shuf -n 10 # ten random files
silgon
источник
1
Вы не должны полагаться на вывод ls. Это не будет работать, если, например, имя файла содержит символы новой строки.
bfontaine
3
@bfontaine, кажется, вас преследуют переводы строк в именах файлов :). Они действительно так распространены? Другими словами, есть ли какой-нибудь инструмент, который создает файлы с символами новой строки в их имени? Поскольку как пользователь очень сложно создать такое имя файла. То же самое для файлов, поступающих из Интернета
Ciprian Tomoiagă
3
@CiprianTomoiaga Это пример проблем, которые вы можете получить. lsНе гарантируется, что вы получите «чистые» имена файлов, поэтому вам не следует полагаться на это, точка. Тот факт, что эти проблемы редки или необычны, не меняет проблему; особенно учитывая, что есть лучшие решения для этого.
bfontaine
lsможет включать в себя каталоги и пустые строки. Я бы предложил что-то вроде find . -type f | shuf -n10этого.
Чердт
9

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

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

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

SCAI
источник
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и один раз для безопасной обработки имени файла.)

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

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

Особенностью # 2 является расширение параметров Bash для массивов , один вложенный в другой. Это начинается с того ${#ARRAY[@]}, что расширяется до длины $ARRAY.

Это расширение затем используется для индексации массива. Стандартный способ найти случайное число от 1 до N состоит в том, чтобы взять значение случайного числа по модулю N. Нам нужно случайное число от 0 до длины нашего массива. Вот подход, разбитый на две строки для ясности:

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

Но это решение делает это в одной строке, удаляя ненужное присвоение переменной.

Особенностью # 3 является расширение Bash Brace , хотя я должен признаться, что не совсем понимаю. Фигурные скобки используются, например, для формирования списка из 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
benmarbles
источник
1

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

Сценарий должен быть легко модифицирован для остановки после N выборок с использованием счетчика с if или цикла gniourf_gniourf's for с 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;
bzimage
источник