У мене каталог з приблизно 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та один раз для безпечної обробки імені файлу.)
Але мені знадобилося кілька хвилин, щоб розплутати "не дуже добре задокументовані" функції, які використовується у цій відповіді. Якщо ваші навички Баша досить міцні, що ви відразу побачили, як це працює, пропустіть цей коментар. Але я цього не зробив, і, розплутавши це, я думаю, що це варто пояснити.
Особливість №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масив містить кожен файл у всій ієрархії.
Якщо у вашій папці є більше файлів, ви можете скористатися наведеною нижче командою, що я знайшов в 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 немає команд сортування -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
Я використовую це: він використовує тимчасовий файл, але заглиблюється в каталог, поки не знайде звичайний файл і не поверне його.
# 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 = перетасувати -e '@lines = shuffle (<>); print @lines [0..4] '