Як відобразити випадковий рядок із текстового файлу?


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 вручну, чи не так? Я хочу, щоб оболонка автоматично вибрала інший рядок випадковим чином. Не точно потрібно було випадковим чином. Але якась інша лінія.
Anandu M Das

4
@AnanduMDas Ні, вам не доведеться nпозначати кількість рядків для друку. (тобто ви хочете лише один рядок або два рядки). Не номер рядка (тобто перший рядок 2-го рядка).
aneeshep

@AnanduMDas: Я додав кілька прикладів до своєї відповіді. Сподіваюся, це зрозуміло зараз.
aneeshep

1
Дякую, я зрозумів зараз :) Я також знайшов інший алгоритм, схожий на нього, зберігати поточний час (лише другий, за date +%S) в змінну x, а потім вибирати цей x-й рядок, використовуючи команди headта tailкоманди з текстового файлу. У будь-якому випадку ваш метод простіше. Спасибі
Anandu M Das

+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варіантом є те, що він трохи швидший, оскільки це чистий баш. На моїй машині для файлу з 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все одно набагато краще. Думаючи про це, я не вірю, що чистий баш насправді ефективніший, ніж використання shuf, як я писав раніше. При запуску зовнішнього інструменту може виникнути найдрібніший (постійний) накладний покрив, але тоді він запустить машину швидше, ніж інтерпретований баш. Тож 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і порівняйте. (Btw 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:

   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 ' '

Стилістична альтернатива (баш):LINES=$(wc -l < file.txt); R_LINE=$((RANDOM % LINES)); sed -n "${R_LINE}p" file.txt
michael


наприклад, подивіться останню картину в тесті PRNG, використовуючи сіру растрову карту, щоб зрозуміти, чому не годиться застосовувати % nдо випадкового числа.
jfs

2

Ось сценарій Python, який вибирає випадковий рядок із вхідних файлів або stdin:

#!/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

Мене вражає робота, яку робили Мальте Скоруппа та інші, але тут є набагато простіший спосіб "чистого удару":

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.