У меня есть каталог с около 2000 файлов. Как я могу выбрать случайный образец N
файлов, используя либо скрипт bash, либо список команд по конвейеру?
ls | shuf -n 5
Источник от Unix Stackexchange
У меня есть каталог с около 2000 файлов. Как я могу выбрать случайный образец N
файлов, используя либо скрипт bash, либо список команд по конвейеру?
ls | shuf -n 5
Источник от Unix Stackexchange
Ответы:
Вот скрипт, который использует случайную опцию сортировки 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
"$file"
, не показанное, будет чувствительным к пробелам.
Вы можете использовать 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
N
случайные файлы, поэтому использование 1
немного вводит в заблуждение.
find dirname -type f -print0 | shuf -zn1
Вот несколько возможностей, которые не анализируют выходные данные 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 файлами на выбор.
ls | shuf -n 10 # ten random files
ls
. Это не будет работать, если, например, имя файла содержит символы новой строки.
ls
Не гарантируется, что вы получите «чистые» имена файлов, поэтому вам не следует полагаться на это, точка. Тот факт, что эти проблемы редки или необычны, не меняет проблему; особенно учитывая, что есть лучшие решения для этого.
ls
может включать в себя каталоги и пустые строки. Я бы предложил что-то вроде find . -type f | shuf -n10
этого.
Простое решение для выбора 5
случайных файлов, избегая при этом разбора ls . Он также работает с файлами, содержащими пробелы, символы новой строки и другие специальные символы:
shuf -ezn 5 * | xargs -0 -n1 echo
Замените echo
на команду, которую вы хотите выполнить для ваших файлов.
read
имеет тех же проблем, что и разбор ls
? а именно, он читает строку за строкой, поэтому он не работает для файлов с символами новой строки в их имени
Если у вас установлен 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
Это еще более поздний ответ на поздний ответ @ 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
массив содержит каждый файл во всей иерархии.
Если у вас есть больше файлов в вашей папке, вы можете использовать приведенную ниже команду, которую я нашел в unix stackexchange .
find /some/dir/ -type f -print0 | xargs -0 shuf -e -n 8 -z | xargs -0 cp -vt /target/dir/
Здесь я хотел скопировать файлы, но если вы хотите переместить файлы или сделать что-то еще, просто измените последнюю команду, которую я использовал cp
.
Это единственный скрипт, который я могу хорошо сыграть с bash на MacOS. Я соединил и отредактировал фрагменты из следующих двух ссылок:
Команда ls: как получить рекурсивный полный путь, по одной строке на файл?
#!/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 нет команд 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
Я использую это: он использует временный файл, но идет глубоко в каталог, пока не найдет обычный файл и не вернет его.
# 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;
Как насчет решения Perl, слегка подправленного мистером Кангом, здесь:
как я могу перетасовать строки текстового файла в командной строке Unix или в сценарии оболочки?
$ ls | perl -MList :: Util = shuffle -e '@lines = shuffle (<>); print @lines [0..4] '