У меня есть каталог с примерно 2000 файлами. Как выбрать произвольную выборку файлов N
с помощью либо bash script, либо списка команд с каналами?
Как выбрать случайные файлы из каталога в bash?
Ответ 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: как я могу получить рекурсивный список полного пути, по одной строке на файл?
#!/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
.
Ответ 12
Как насчет решения Perl, слегка подготовленного от г-на Канга здесь:
Как перетасовать строки текстового файла в командной строке Unix или в оболочке script?
$ls | perl -MList:: Util = shuffle -e '@lines = shuffle (< > ); Распечатать @строки [0..4] '