Как отобразить случайную строку из текстового файла?


26

Я пытаюсь написать сценарий оболочки. Идея состоит в том, чтобы выбрать произвольную строку из текстового файла и отобразить ее как уведомление рабочего стола Ubuntu.

Но я хочу, чтобы при каждом выполнении сценария были выбраны разные строки. Есть ли решение для этого? Я не хочу весь сценарий. Только эта простая вещь.


Также посетите: askubuntu.com/q/492572/256099
Pandya

Ответы:


40

Вы можете использовать shufутилиту для печати случайных строк из файла

$ shuf -n 1 filename

-n : количество строк для печати

Примеры:

$ shuf -n 1 /etc/passwd

git:x:998:998:git daemon user:/:/bin/bash

$ shuf -n 2 /etc/passwd

avahi:x:84:84:avahi:/:/bin/false
daemon:x:2:2:daemon:/sbin:/bin/false

Но используя это, я должен изменить значение n вручную, верно? Я хочу, чтобы эта оболочка автоматически выбирала другую строку случайным образом. Не совсем нужно быть случайным. Но какая-то другая линия.
Ананду М Дас

4
@ AnanduMDas Нет, вам не нужно указывать nколичество строк для печати. (т.е. хотите ли вы только одну строку или две строки). Не номер строки (т.е. первая строка 2-я строка).
Aneeshep

@AnanduMDas: я добавил несколько примеров к своему ответу. Надеюсь, теперь все ясно.
Aneeshep

1
Спасибо и его теперь ясно :) Я также нашел другой алгоритм, его , как, сохранить текущее время (второй только, по date +%S) в переменной х, а затем выбрать эту XTH линию , используя headи tailкоманды из текстового файла. В любом случае ваш метод проще. Спасибо
Ананду М Дас

+1: shufв coreutils, поэтому он доступен по умолчанию. Примечание: он загружает входной файл в память. Существует эффективный алгоритм, который этого не требует .
JFS

13

Вы также можете использовать sortкоманду, чтобы получить случайную строку из файла.

sort -R filename | head -n1

примечание: sort -Rдает другой результат, чем shuf -n1или select-randomесли во входе есть повторяющиеся строки. Смотрите комментарий @ EliahKagan .
JFS

8

Просто для удовольствия, здесь чисто баш решение , которое не используется shuf, sort, wc, sed, head, tailили любые другие внешние инструменты.

Единственное преимущество по сравнению с shufвариантом в том, что он немного быстрее, так как это чистый bash. На моей машине для файла из 1000 строк shufвариант занимает около 0,1 секунды, в то время как следующий скрипт занимает около 0,01 секунды;) Так что, хотя shufэто самый простой и самый короткий вариант, это быстрее.

Честно говоря, я все равно пошел бы на shufрешение, если высокая эффективность не является важной проблемой.

#!/bin/bash

FILE=file.txt

# get line count for $FILE (simulate 'wc -l')
lc=0
while read -r line; do
 ((lc++))
done < $FILE

# get a random number between 1 and $lc
rnd=$RANDOM
let "rnd %= $lc"
((rnd++))

# traverse file and find line number $rnd
i=0
while read -r line; do
 ((i++))
 [ $i -eq $rnd ] && break
done < $FILE

# output random line
printf '%s\n' "$line"

@EliahKagan Спасибо за предложения и хорошие моменты. Я признаю, что есть немало угловых случаев, о которых я не особо задумывался. Я написал это действительно больше для удовольствия. Использование shufв любом случае намного лучше. Думая об этом, я не верю, что чистый bash на самом деле более эффективен, чем использование shuf, как я писал ранее. При запуске внешнего инструмента могут быть минимальные (постоянные) издержки, но тогда он будет работать быстрее, чем интерпретируемый bash. Так что, shufконечно, весы лучше. Допустим, сценарий служит образовательным целям: приятно видеть, что это можно сделать;)
Malte Skoruppa

В GNU / Linux / Un * x есть много очень хорошо протестированных на дороге колес, которые я не хотел бы изобретать заново, если только это не было чисто академическим упражнением. «Оболочка» предназначалась для сборки множества маленьких существующих деталей, которые можно (пере) собирать различными способами с помощью параметров ввода / вывода и множества других опций. Все остальное - плохая форма, если только это не для спорта (например, codegolf.stackexchange.com/tour ), в этом случае играйте на ...!
Майкл

2
@michael_n Хотя способ «чистого удара» в основном полезен для обучения и модификации для других задач, это более разумно для «реальной» реализации, чем может показаться. Bash широко доступен, но shufспецифичен для GNU Coreutils (например, не во FreeBSD 10.0). sort -Rявляется переносимым, но решает другую (связанную) проблему: строки, представленные в виде нескольких строк, имеют вероятность, равную тем, которые появляются только один раз. (Конечно, wcможно использовать и другие утилиты.) Я думаю, что основным ограничением здесь является то, что после 32768-й строки ничего не выбирается (и становится менее случайным, что несколько раньше).
Элия ​​Каган,

2
Malte Skoruppa: Я вижу , вы переехали на баш PRNG вопрос U & L . Круто. Подсказка: $((RANDOM<<15|RANDOM))есть в 0..2 ^ 30-1. @JFSebastian Это shufне то sort -R, что склоняется к более частым входам. Поставь shuf -n 1вместо sort -R | head -n1и сравни. (Кстати, 10 ^ 3 итерации быстрее, чем 10 ^ 6, и все же вполне достаточно, чтобы показать разницу.) См. Также более грубую, более наглядную демонстрацию и этот кусочек глупости, показывающий, что он работает на больших входах, где все строки имеют высокую частоту .
Элия ​​Каган

1
@JFSebastian В этой команде входными данными, по- dieharderвидимому, являются все нули. Если предположить, что это не просто какая-то странная ошибка с моей стороны, это наверняка объяснит, почему это не случайно! Получаете ли вы красивые данные, если while echo $(( RANDOM << 17 | RANDOM << 2 | RANDOM >> 13 )); do :; done | perl -ne 'print pack "I>"' > outнекоторое время запускаете , а затем изучаете содержимое outс помощью шестнадцатеричного редактора? (Или это смотреть , однако еще вам нравится.) Я получаю все нули, а RANDOMне преступник: Я получаю все нули , когда я заменяю $(( RANDOM << 17 | RANDOM << 2 | RANDOM >> 13 ))с 100тоже.
Элия ​​Каган

4

Скажем, у вас есть файл notifications.txt. Нам нужно посчитать общее количество строк, чтобы определить диапазон случайного генератора:

$ cat notifications.txt | wc -l

Давайте напишем в переменную:

$ LINES=$(cat notifications.txt | wc -l)

Теперь для генерации числа от 0до $LINEмы будем использовать RANDOMпеременную.

$ echo $[ $RANDOM % LINES]

Давайте запишем это в переменную:

$  R_LINE=$(($RANDOM % LINES))

Теперь нам нужно только напечатать этот номер строки:

$ sed -n "${R_LINE}p" notifications.txt

О СЛУЧАЙНОМ:

   RANDOM Each time this parameter is referenced, a random integer between
          0 and 32767 is generated.  The sequence of random numbers may be
          initialized by assigning a value to RANDOM.  If RANDOM is unset,
          it  loses  its  special  properties,  even if it is subsequently
          reset.

Убедитесь, что в вашем файле меньше 32767 номеров строк. Смотрите это , если вам нужно больше генератор случайных чисел , который работает из коробки.

Пример:

$ od -A n -t d -N 3 /dev/urandom | tr -d ' '

Стилистическая альтернатива (Bash):LINES=$(wc -l < file.txt); R_LINE=$((RANDOM % LINES)); sed -n "${R_LINE}p" file.txt
Майкл


Например, посмотрите на последнее изображение в Test PRNG с использованием серого растрового изображения, чтобы понять, почему не следует применять % nслучайное число.
JFS

2

Вот скрипт Python, который выбирает случайную строку из входных файлов или стандартного ввода:

#!/usr/bin/env python
"""Usage: select-random [<file>]..."""
import random

def select_random(iterable, default=None, random=random):
    """Select a random element from iterable.

    Return default if iterable is empty.
    If iterable is a sequence then random.choice() is used for efficiency instead.
    If iterable is an iterator; it is exhausted.
    O(n)-time, O(1)-space algorithm.
    """
    try:
        return random.choice(iterable) # O(1) time and space
    except IndexError: # empty sequence
        return default
    except TypeError: # not a sequence
        return select_random_it(iter(iterable), default, random.randrange)

def select_random_it(iterator, default=None, randrange=random.randrange):
    """Return a random element from iterator.

    Return default if iterator is empty.
    iterator is exhausted.
    O(n)-time, O(1)-space algorithm.
    """
    # from /programming//a/1456750/4279
    # select 1st item with probability 100% (if input is one item, return it)
    # select 2nd item with probability 50% (or 50% the selection stays the 1st)
    # select 3rd item with probability 33.(3)%
    # select nth item with probability 1/n
    selection = default
    for i, item in enumerate(iterator, start=1):
        if randrange(i) == 0: # random [0..i)
            selection = item
    return selection

if __name__ == "__main__":
    import fileinput
    import sys

    random_line = select_random_it(fileinput.input(), '\n')
    sys.stdout.write(random_line)
    if not random_line.endswith('\n'):
        sys.stdout.write('\n') # always append newline at the end

Алгоритм имеет O (n) -время, O (1) -пространство. Он работает для файлов размером более 32767 строк. Он не загружает входные файлы в память. Он читает каждую входную строку ровно один раз, т. Е. Вы можете передать в нее произвольно большой (но конечный) контент. Вот объяснение алгоритма .


1

Я впечатлен работой, которую проделали Malte Skoruppa и другие, но вот гораздо более простой способ сделать это:

IFS=$'\012'
# set field separator to newline only
lines=( $(<test5) )
# slurp entire file into an array
numlines=${#lines[@]}
# count the array elements
num=$(( $RANDOM$RANDOM$RANDOM % numlines ))
# get a (more-or-less) random number within the correct range
line=${lines[$num]}
# select the element corresponding to the random number
echo $line
# display it

Как уже отмечалось, $ RANDOM не является случайным. Однако ограничение размера файла в 32767 строк преодолевается путем объединения $ RANDOM вместе при необходимости.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.