Làm cách nào để hiển thị một dòng ngẫu nhiên từ một tệp văn bản?


26

Tôi đang cố gắng viết một kịch bản shell. Ý tưởng là chọn một dòng ngẫu nhiên từ tệp văn bản và hiển thị nó dưới dạng thông báo trên màn hình Ubuntu.

Nhưng tôi muốn các dòng khác nhau được chọn mỗi lần tôi thực thi tập lệnh. Có giải pháp nào để làm việc này không? Tôi không muốn toàn bộ kịch bản. Chỉ là điều đơn giản mà thôi.


Đồng thời truy cập: Askubfox.com/q/492572/256099
Pandya

Câu trả lời:


40

Bạn có thể sử dụng shuftiện ích để in các dòng ngẫu nhiên từ tệp

$ shuf -n 1 filename

-n : số dòng cần in

Ví dụ:

$ 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

Nhưng bằng cách sử dụng này, tôi phải thay đổi giá trị của n bằng tay phải không? Tôi muốn shell đó tự động chọn một dòng khác một cách ngẫu nhiên. Không chính xác cần thiết để được ngẫu nhiên. Nhưng một số dòng khác.
Anandu M Das

4
@AnanduMDas Không, bạn không phải nbiểu thị số lượng dòng cần in. (tức là cho dù bạn chỉ muốn một dòng hay hai dòng). Không phải số dòng (tức là dòng thứ 2 dòng thứ nhất).
aneeshep

@AnanduMDas: Tôi đã thêm một số ví dụ vào câu trả lời của mình. Hy vọng nó rõ ràng bây giờ.
aneeshep

1
Cảm ơn bạn đã rõ rồi :) Tôi cũng tìm thấy một thuật toán khác, giống như, lưu thời gian hiện tại (chỉ thứ hai, bởi date +%S) vào một biến x, và sau đó chọn dòng xth đó bằng cách sử dụng các lệnh headtailtừ tệp văn bản. Dù sao phương pháp của bạn dễ dàng hơn. Cảm ơn
Anandu M Das

+1: shuflà trong coreutils nên nó có sẵn theo mặc định. Lưu ý: nó tải tập tin đầu vào vào bộ nhớ. Có một thuật toán hiệu quả không yêu cầu nó .
jfs


8

Chỉ để cho vui, đây là một giải pháp bash tinh khiết mà không sử dụng shuf, sort, wc, sed, head, tailhoặc bất kỳ công cụ bên ngoài khác.

Ưu điểm duy nhất so với shufbiến thể là nó nhanh hơn một chút, vì đó là bash thuần túy. Trên máy của tôi, đối với tệp 1000 dòng, shufbiến thể mất khoảng 0,1 giây, trong khi tập lệnh sau mất khoảng 0,01 giây;) Vì vậy, trong khi đó shuflà biến thể dễ nhất và ngắn nhất, tốc độ này nhanh hơn.

Thành thật mà nói tôi vẫn sẽ đi tìm shufgiải pháp, trừ khi hiệu quả cao là một mối quan tâm quan trọng.

#!/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 Cảm ơn những gợi ý và điểm tốt. Tôi sẽ thừa nhận có khá nhiều trường hợp góc mà tôi đã không thực sự suy nghĩ quá nhiều. Tôi đã viết điều này thực sự nhiều hơn cho niềm vui của nó. Sử dụng shuflà tốt hơn nhiều dù sao. Nghĩ về nó, tôi không tin rằng bash thuần thực sự hiệu quả hơn sử dụng shuf, như tôi đã viết trước đây. Có thể có chi phí nhỏ nhất (không đổi) khi kích hoạt một công cụ bên ngoài, nhưng sau đó nó sẽ chạy mach nhanh hơn so với bash diễn giải. Vì vậy, shufchắc chắn quy mô tốt hơn. Vì vậy, hãy nói rằng kịch bản phục vụ mục đích giáo dục: Thật tuyệt khi thấy nó có thể được thực hiện;)
Malte Skoruppa

GNU / Linux / Un * x có rất nhiều bánh xe được thử nghiệm trên đường mà tôi không muốn phát minh lại, trừ khi đó là một bài tập thuần túy học thuật. "Vỏ" được dự định sẽ được sử dụng để lắp ráp rất nhiều bộ phận nhỏ hiện có có thể được lắp ráp lại theo nhiều cách khác nhau thông qua các tùy chọn đầu vào / đầu ra và nhiều o '. Bất cứ điều gì khác là hình thức xấu, trừ khi nó dành cho thể thao (ví dụ: codegolf.stackexchange.com/tour ), trong trường hợp đó, hãy chơi trên ...!
michael

2
@michael_n Mặc dù cách "bash thuần túy" chủ yếu hữu ích cho việc giảng dạy và sửa đổi cho các nhiệm vụ khác, nhưng đây là cách thực hiện "thực tế" hợp lý hơn so với vẻ ngoài của nó. Bash có sẵn rộng rãi, nhưng shufGNU Coreutils - cụ thể (ví dụ, không có trong FreeBSD 10.0). sort -Rlà di động, nhưng giải quyết một vấn đề (liên quan) khác: các chuỗi xuất hiện dưới dạng nhiều dòng có xác suất bằng với các chuỗi chỉ xuất hiện một lần. (Tất nhiên, wcvà các tiện ích khác vẫn có thể được sử dụng.) Tôi nghĩ hạn chế chính ở đây là điều này không bao giờ chọn bất cứ thứ gì sau dòng 32768 (và trở nên ít ngẫu nhiên hơn một chút sớm hơn).
Eliah Kagan

2
Malte Skoruppa: Tôi thấy bạn đã chuyển câu hỏi bash PRNG U & L . Mát mẻ. Gợi ý: $((RANDOM<<15|RANDOM))nằm trong 0..2 ^ 30-1. @JFSebastian Nó shuf, không sort -R, mà nghiêng về đầu vào thường xuyên hơn. Đặt shuf -n 1vào vị trí sort -R | head -n1và so sánh. (Btw 10 ^ 3 lần lặp nhanh hơn 10 ^ 6 và vẫn khá đủ để thể hiện sự khác biệt.) Xem thêm một bản demo thô hơn, trực quan hơnmột chút silliness này cho thấy nó hoạt động trên các đầu vào lớn trong đó tất cả các chuỗi đều có tần số cao .
Eliah Kagan

1
@JFSebastian Trong lệnh đó, đầu vào dieharderdường như là tất cả các số không. Giả sử đây không chỉ là một sai lầm kỳ lạ từ phía tôi, điều đó chắc chắn sẽ giải thích tại sao nó không ngẫu nhiên! Bạn có nhận được dữ liệu đẹp nếu bạn chạy while echo $(( RANDOM << 17 | RANDOM << 2 | RANDOM >> 13 )); do :; done | perl -ne 'print pack "I>"' > outmột lúc và sau đó kiểm tra nội dung của outvới trình soạn thảo hex không? (Hoặc xem nó tuy nhiên khác bạn như thế nào.) Tôi nhận được tất cả số không, và RANDOMkhông phải là thủ phạm: Tôi nhận được tất cả số không khi tôi thay thế $(( RANDOM << 17 | RANDOM << 2 | RANDOM >> 13 ))với 100, quá.
Eliah Kagan

4

Nói rằng bạn có tập tin notifications.txt. Chúng ta cần đếm tổng số dòng, để xác định phạm vi của trình tạo ngẫu nhiên:

$ cat notifications.txt | wc -l

Cho phép ghi vào biến:

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

Bây giờ để tạo ra số từ 0để $LINEchúng ta sẽ sử dụng RANDOMbiến.

$ echo $[ $RANDOM % LINES]

Hãy viết nó thành biến:

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

Bây giờ chúng ta chỉ cần in số dòng này:

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

Về 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.

Hãy chắc chắn rằng tập tin của bạn có ít hơn 32767 số dòng. Xem điều này nếu bạn cần máy phát ngẫu nhiên lớn hơn hoạt động ra khỏi hộp.

Thí dụ:

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

Một thay thế phong cách (bash):LINES=$(wc -l < file.txt); R_LINE=$((RANDOM % LINES)); sed -n "${R_LINE}p" file.txt
michael


ví dụ: nhìn vào hình ảnh cuối cùng trong Test PRNG bằng cách sử dụng bitmap màu xám để hiểu lý do tại sao không nên áp dụng % ncho một số ngẫu nhiên.
jfs

2

Đây là tập lệnh Python chọn một dòng ngẫu nhiên từ các tệp đầu vào hoặc 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

Thuật toán là O (n) -time, O (1) -space. Nó hoạt động cho các tệp lớn hơn 32767 dòng. Nó không tải các tập tin đầu vào vào bộ nhớ. Nó đọc từng dòng đầu vào chính xác một lần, tức là bạn có thể chuyển nội dung lớn (nhưng hữu hạn) tùy ý vào nó. Đây là một lời giải thích của thuật toán .


1

Tôi ấn tượng với công việc mà Malte Skoruppa và những người khác đã làm, nhưng đây là một cách "bash thuần túy" đơn giản hơn nhiều để làm điều đó:

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

Như một số người đã lưu ý, $ RANDOM không phải là ngẫu nhiên. Tuy nhiên, giới hạn kích thước tệp của 32767 dòng được khắc phục bằng cách xâu chuỗi $ RANDOM với nhau khi cần.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.