Какой простой способ прочитать случайную строку из файла в командной строке 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
AndAlso, sort -Rопределенно собирается сделать один ждать много , если дело со значительно большими файлами - 80kk линии -, в то время как, shuf -nдействует совершенно мгновенно.
Рубенс

23
Вы можете получить shuf на OS X, установив coreutilsиз Homebrew. Может быть вызван gshufвместо shuf.
Алисса Росс

2
Точно так же вы можете использовать randomize-linesна OS Xbrew install randomize-lines; rl -c 1 $FILE
Jamie

4
Обратите внимание, что он shufявляется частью GNU Coreutils и поэтому не обязательно будет доступен (по умолчанию) в системах * BSD (или Mac?). Перл @ Tracker1 с одной строкой ниже более переносим (и, по моим тестам, немного быстрее).
Адам Кац

74

Другая альтернатива:

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

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

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

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

@nneonneo: Очень крутой трюк, хотя по этой ссылке он должен делать ИЛИ '$ RANDOM} вместо PLUS'а 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, потому что он удаляет дубликаты строк.
Lri

5
Это относительно медленно, так как весь файл должен быть перетасован sortперед передачей head. shufвместо этого выбирает случайные строки из файла и для меня это намного быстрее.
Bengt

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

5
--random-sortИ -Rопции являются специфическими для GNU рода (так что они не будут работать с BSD или Mac OS sort). GNU sort узнал эти флаги в 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 filename | perl -e 'while (<>) {push (@ _, $ _);} print @ _ [рандов () * @ _]; ';»
Анирван

3
Это бесполезное использование кошки. Вот небольшая модификация кода, найденного в perlfaq5 (и любезно предоставленного книгой Camel): perl -e 'srand; rand ($.) <1 && ($ line = $ _) while <>; выведите $ line; ' имя файла
г-н Маскрат

эээ ... связанный сайт, то есть
Натан Феллман

Я только что сравнил версию этого кода с 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

ммм - как насчет $ 1 для "tmp.txt" и $ 2 для NUM?
blabla999

но даже с ошибкой, заслуживающей внимания, так как она не требует Perl или Python и настолько эффективна, насколько вы можете получить (чтение файла ровно дважды, но не в память - так что он будет работать даже с огромными файлами).
blabla999

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

Изменение строки sed на: head - $ {X} $ {FILE} | Хвост -1 должен сделать это
JeffK

4

Одиночная линия bash:

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

Небольшая проблема: дублирование имени файла.


2
более легкая проблема. выполнение этого в / usr / share / dict / words приводит к появлению слов, начинающихся с «A». Играя с этим, у меня примерно 90% слов "A" и 10% слов "B". Ничего не начинается с цифр, которые составляют заголовок файла.
Бибби

wc -l < test.txtизбегает необходимости трубить к cut.
Федорки 'ТАК прекрати вредить'

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 в диапазоне (len (линии)): rand = random.randint (0, len (lines) -1) print lines.pop (rand),
Джед Дэниелс

Глупая система комментариев с дерьмовым форматированием. Разве форматирование в комментариях не работало когда-то давно?
Джед Дэниелс

randint включительно, поэтому len(lines)может привести к IndexError. Вы могли бы использовать print(random.choice(list(open(sys.argv[1])))). Существует также эффективный для памяти алгоритм отбора проб из пласта .
JFS

2
Довольно голодный; рассмотрим файл размером 3 ТБ.
Майкл Кэмпбелл

@MichaelCampbell: алгоритм отбора проб резервуара, о котором я упоминал выше, может работать с файлом размером 3 ТБ (если размер линии ограничен).
JFS

2

Еще один способ использования « awk »

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

2
Это использует awk и bash ( $RANDOMэто bashism ). Вот метод чистого AWK (Мок) , используя ту же логику, @ привел код perlfaq5 Tracker1 в выше: awk 'rand() * NR < 1 { line = $0 } END { print line }' file.name(ничего себе, это даже меньше , чем перловый код!)
Адам Кац

Этот код должен прочитать file ( wc), чтобы получить счетчик строк, а затем снова должен прочитать (часть) файл ( awk), чтобы получить содержимое заданного случайного номера строки. Ввод / вывод будет намного дороже, чем получение случайного числа. Мой код читает файл только один раз. Проблема с awk в 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 OS не использует все простые ответы. Я использовал команду 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

Использование только vanilla sed и awk и без использования $ 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()

«В большинстве реализаций awk, включая gawk, rand () начинает генерировать числа из одного и того же начального номера или seed при каждом запуске 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.