Який простий спосіб зчитувати випадковий рядок з файлу в командному рядку Unix?


263

Який простий спосіб зчитувати випадковий рядок з файлу в командному рядку Unix?


Чи кожен рядок оброблений фіксованою довжиною?
Tracker1

ні, у кожному рядку є змінна кількість символів

Відповіді:


383

Ви можете використовувати shuf:

shuf -n 1 $FILE

Існує також утиліта під назвою rl. У Debian він знаходиться в randomize-linesпакеті, який робить саме те, що ви хочете, але доступний не у всіх дистрибутивах. На своїй домашній сторінці він фактично рекомендує використовувати shufзамість цього (якого я не вірив, коли він був створений, я вважаю). shufє частиною GNU coreutils, rlні.

rl -c 1 $FILE

2
Дякую за shufпораду, він вбудований у Fedora.
Чен

5
Андальсо, sort -Rбезумовно, змусить зачекати багато, якщо мати справу зі значно величезними файлами - 80kk-рядками -, тоді як shuf -nдіє досить миттєво.
Рубенс

23
Ви можете отримати shuf на OS X, встановивши програму coreutilsHomebrew. Можна називати gshufзамість shuf.
Alyssa Ross

2
Так само ви можете використовувати randomize-linesOS X відbrew install randomize-lines; rl -c 1 $FILE
Джеймі

4
Зауважте, що вона shufє частиною GNU Coreutils і тому не обов'язково буде доступна (за замовчуванням) у * BSD системах (або Mac?). @ Однолінійний Perl-Tracker1 нижче є більш портативним (і, за моїми тестами, трохи швидшим).
Адам Кац

74

Ще одна альтернатива:

head -$((${RANDOM} % `wc -l < file` + 1)) file | tail -1

28
$ {RANDOM} генерує лише цифри менше 32768, тому не використовуйте це для великих файлів (наприклад, англійський словник).
Ральф

3
Це не дає точну однакову ймовірність для кожного рядка через модульну операцію. Це навряд чи має значення, якщо довжина файлу становить << 32768 (і зовсім не, якщо він розділяє це число), але, можливо, це варто зазначити.
Анафорія

10
Ви можете розширити це до 30-бітових випадкових чисел, використовуючи (${RANDOM} << 15) + ${RANDOM}. Це значно зменшує ухил і дозволяє йому працювати з файлами, що містять до 1 мільярда рядків.
nneonneo

@nneonneo: Дуже крутий трюк, хоча за цим посиланням слід OR'ing в $ {RANDOM} 'з замість PLUS'ing stackoverflow.com/a/19602060/293064
Джей Тейлор

+і |однакові, оскільки ${RANDOM}є 0..32767 за визначенням.
nneonneo

71
sort --random-sort $FILE | head -n 1

(Мені подобається підхід шуфа вище навіть краще - я навіть не знав, що існує, і я ніколи не знайшов би цей інструмент самостійно)


10
+1 Мені це подобається, але вам може знадобитися зовсім недавнє sort, не працювало в жодній моїй системі (CentOS 5.5, Mac OS 10.7.2). Крім того, марне використання котів може бути зведене доsort --random-sort < $FILE | head -n 1
Стів Келет

sort -R <<< $'1\n1\n2' | head -1так само ймовірно, що повертаються 1 і 2, тому що sort -Rсортують повторювані рядки разом. Це ж стосується sort -Ru, оскільки він видаляє повторювані рядки.
Лрі

5
Це відносно повільно, оскільки весь файл потрібно перетасувати, sortперш ніж передати його head. shufзамість цього вибирає випадкові рядки з файлу і для мене це набагато швидше.
Бенгт

1
@SteveKehlet, поки ми в ньому, sort --random-sort $FILE | headбуло б найкраще, оскільки це дозволяє йому безпосередньо отримувати доступ до файлу, можливо, дозволяючи ефективно паралельне сортування
WaelJ

5
Параметри --random-sortта -Rпараметри характерні для сортування GNU (тому вони не працюватимуть із BSD чи Mac OS sort). Сортування GNU вивчило ці прапори у 2005 році, тому вам потрібні GNU coreutils 6.0 або новіші (наприклад, CentOS 6).
RJHunter

31

Це просто.

cat file.txt | shuf -n 1

Зрозуміло, що це лише в рази повільніше, ніж "shuf -n 1 file.txt" самостійно.


2
Найкраща відповідь. Я не знав про цю команду. Зверніть увагу, що -n 1вказується 1 рядок, і ви можете змінити його на більше, ніж 1. shufможна використовувати і для інших речей; Я просто трубопроводом ps auxі grepразом з ним випадково вбиваю процеси, що частково відповідають імені.
судо

18

perlfaq5: Як вибрати випадковий рядок із файлу? Ось алгоритм відбору проб водойми з книги верблюдів:

perl -e 'srand; rand($.) < 1 && ($line = $_) while <>; print $line;' file

Це має значну перевагу в просторі перед читанням всього файлу в. Доказ цього методу ви можете знайти в «Мистецтві комп’ютерного програмування», том 2, розділ 3.4.2, Дональд Е. Кнут.


1
Тільки для цілей включення (у випадку, якщо згаданий сайт знижується), ось код, на який вказував Tracker1: "ім'я файлу cat | perl -e" while (<>) {push (@ _, $ _);} print @ _ [rand () * @ _]; '; "
Анірван

3
Це марне використання кота. Ось невелика модифікація коду, знайденого в perlfaq5 (і люб'язно надано книзі верблюдів): srand perl -e; rand ($.) <1 && ($ line = $ _), а <>; друкувати $ рядок; ' ім'я файлу
Пан Мускет

еее ... пов'язаний сайт, тобто
Натан Fellman

Я просто порівняв N-рядкову версію цього коду проти shuf. Код perl дуже трохи швидший (на 8% швидше за користувальницьким часом, на 24% швидше за системним часом), хоча анекдотично я виявив, що код perl "здається" менш випадковим (я написав автомат, використовуючи його).
Адам Кац

2
Більше їжі для роздумів: shufзберігає весь вхідний файл у пам'яті , що є жахливою ідеєю, тоді як цей код зберігає лише один рядок, тому межа цього коду - це кількість рядків INT_MAX (2 ^ 31 або 2 ^ 63 залежно від вашого арка), припускаючи, що будь-який із обраних потенційних ліній вписується в пам’ять.
Адам Кац

11

використовуючи скрипт bash:

#!/bin/bash
# replace with file to read
FILE=tmp.txt
# count number of lines
NUM=$(wc - l < ${FILE})
# generate random number in range 0-NUM
let X=${RANDOM} % ${NUM} + 1
# extract X-th line
sed -n ${X}p ${FILE}

1
Випадково може бути 0, sed для першого рядка потрібно 1. sed -n 0p повертає помилку.
asalamon74

mhm - як приблизно $ 1 за "tmp.txt" і $ 2 за NUM?
blabla999

але навіть з помилкою, яка стоїть на балі, тому що їй не потрібен perl чи python та настільки ж ефективний, як ви можете (читати файл рівно двічі, але не в пам'яті - так він би працював навіть з величезними файлами).
blabla999

@ asalamon74: спасибі @ blabla999: якщо ми робимо з нього функцію, нормально за 1 долар, але чому б не обчислити NUM?
Паоло Тедеско

Зміна лінії sed на: head - $ {X} $ {FILE} | хвіст -1 повинен це зробити
JeffK

4

Одинарна лінія баш:

sed -n $((1+$RANDOM%`wc -l test.txt | cut -f 1 -d ' '`))p test.txt

Незначна проблема: копія імені файлу.


2
легша проблема. виконуючи це на / usr / share / dict / слова, як правило, надаються переваги слова, що починаються з «A». Граючи з ним, я складаю приблизно 90% "А" слів до 10% "В" слів. Жодне, починаючи з цифр, які складають голову файлу.
bibby

wc -l < test.txtуникає необхідності трубопроводів cut.
fedorqui "Так перестань шкодити"

3

Ось простий скрипт Python, який зробить цю роботу:

import random, sys
lines = open(sys.argv[1]).readlines()
print(lines[random.randrange(len(lines))])

Використання:

python randline.py file_to_get_random_line_from

1
Це не зовсім працює. Він зупиняється після єдиного рядка. Щоб він працював, я зробив це так: import random, sys lines = open(sys.argv[1]).readlines() для i in range (len (рядки)): rand = random.randint (0, len (рядки) -1) друкується lines.pop (rand),
Jed Daniels

Дурна система коментарів з шаленим форматуванням. Чи форматування в коментарях не працювало один раз?
Джед Даніельс

randint включено, тому len(lines)може призвести до IndexError. Ви можете використовувати print(random.choice(list(open(sys.argv[1])))). Існує також алгоритм відбору проб резервуара, ефективного для пам'яті .
jfs

2
Досить місця голодне; розглянути файл 3TB.
Майкл Кемпбелл

@MichaelCampbell: алгоритм відбору проб для резервуарів, про який я згадував вище, може працювати з файлом 3TB (якщо розмір рядка обмежений).
jfs

2

Інший спосіб використання ' awk '

awk NR==$((${RANDOM} % `wc -l < file.name` + 1)) file.name

2
Це використовує awk і bash ( $RANDOMце башизм ). Ось чистий метод awk (mawk), що використовує ту ж логіку, що і цитований perlfaq5 код вище Tracker1: awk 'rand() * NR < 1 { line = $0 } END { print line }' file.name(ух, це навіть коротше коду perl!)
Адам Кац

Цей код повинен прочитати файл ( wc), щоб отримати кількість рядків, а потім знов прочитати (частину) файлу ( awk), щоб отримати вміст заданого випадкового номера рядка. Введення / виведення буде набагато дорожчим, ніж отримання випадкового числа. Мій код читає файл лише один раз. Проблема з awk's rand()полягає в тому, що вона насідає за секунди, тож ви отримаєте дублікати, якщо будете запускати її занадто швидко.
Адам Кац

1

Рішення, яке також працює на MacOSX, а також має працювати в Linux (?):

N=5
awk 'NR==FNR {lineN[$1]; next}(FNR in lineN)' <(jot -r $N 1 $(wc -l < $file)) $file 

Де:

  • N - кількість випадкових рядків, які ви хочете

  • NR==FNR {lineN[$1]; next}(FNR in lineN) file1 file2 -> зберегти записані номери рядків file1і потім надрукувати відповідний рядок уfile2

  • jot -r $N 1 $(wc -l < $file)-> намалювати Nчисла випадковим чином ( -r) в діапазоні (1, number_of_line_in_file)з jot. Заміна процесу <()зробить це схожим на файл для перекладача, як file1у попередньому прикладі.

0
#!/bin/bash

IFS=$'\n' wordsArray=($(<$1))

numWords=${#wordsArray[@]}
sizeOfNumWords=${#numWords}

while [ True ]
do
    for ((i=0; i<$sizeOfNumWords; i++))
    do
        let ranNumArray[$i]=$(( ( $RANDOM % 10 )  + 1 ))-1
        ranNumStr="$ranNumStr${ranNumArray[$i]}"
    done
    if [ $ranNumStr -le $numWords ]
    then
        break
    fi
    ranNumStr=""
done

noLeadZeroStr=$((10#$ranNumStr))
echo ${wordsArray[$noLeadZeroStr]}

Оскільки $ RANDOM генерує числа, менші за кількість слів у / usr / share / dict / words, що має 235886 (так чи інакше на моєму Mac), я просто генерую 6 окремих випадкових чисел між 0 і 9 і з'єдную їх між собою. Потім я переконуюсь, що число менше 235886. Потім видаліть провідні нулі, щоб проіндексувати слова, які я зберігав у масиві. Оскільки кожне слово є власним рядком, це може бути легко використане для будь-якого файлу для випадкового вибору рядка.
Кен

0

Ось що я відкрив, оскільки моя ОС Mac не використовує всіх простих відповідей. Я використовував команду jot, щоб генерувати число, оскільки рішення змінної $ RANDOM, здається, не дуже випадкові в моєму тесті. Під час тестування мого рішення у мене було широке розходження в рішеннях, що надаються у висновку.

  RANDOM1=`jot -r 1 1 235886`
   #range of jot ( 1 235886 ) found from earlier wc -w /usr/share/dict/web2
   echo $RANDOM1
   head -n $RANDOM1 /usr/share/dict/web2 | tail -n 1

Відлуння змінної полягає в тому, щоб отримати візуальне згенероване випадкове число.


0

Використовуючи тільки sed і awk vanilla та без використання $ RANDOM, простий, просторовий та досить швидкий «однолінійний» для вибору псевдовипадкового випадкового ряду з файлу з назвою FILENAME:

sed -n $(awk 'END {srand(); r=rand()*NR; if (r<NR) {sub(/\..*/,"",r); r++;}; print r}' FILENAME)p FILENAME

(Це працює, навіть якщо FILENAME порожній, і в цьому випадку жодна рядок не випромінюється.)

Однією з можливих переваг такого підходу є те, що він викликає rand () лише один раз.

Як вказував @AdamKatz у коментарях, іншою можливістю буде викликати rand () для кожного рядка:

awk 'rand() * NR < 1 { line = $0 } END { print line }' FILENAME

(Простий доказ правильності можна навести на основі індукції.)

Кава о rand()

"У більшості дивних реалізацій, включаючи gawk, rand () починає генерувати номери з одного і того ж початкового номера або насіння, щоразу запускаючи awk."

- https://www.gnu.org/software/gawk/manual/html_node/Numeric-Functions.html


Дивіться коментар, який я опублікував за рік до цієї відповіді , де є більш просте рішення awk, яке не потребує sed. Також зауважте мій застереження про генератор випадкових чисел awk, який висіває цілі секунди.
Адам Кац
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.