วิธีการแสดงสายสุ่มจากไฟล์ข้อความ?


24

ฉันพยายามเขียนเชลล์สคริปต์ แนวคิดคือการเลือกบรรทัดเดียวโดยการสุ่มจากไฟล์ข้อความและแสดงเป็นการแจ้งเตือนบนเดสก์ท็อปของ Ubuntu

แต่ฉันต้องการให้มีการเลือกบรรทัดที่แตกต่างกันในแต่ละครั้งที่ฉันเรียกใช้งานสคริปต์ มีวิธีแก้ปัญหานี้หรือไม่? ฉันไม่ต้องการสคริปต์ทั้งหมด เพียงแค่สิ่งที่เรียบง่ายเท่านั้น


โปรดเยี่ยมชม: askubuntu.com/q/492572/256099
Pandya

คำตอบ:


38

คุณสามารถใช้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ถ้ามีบรรทัดที่ซ้ำกันในอินพุต ดู@ คิดเห็น
jfs

8

เพียงเพื่อความสนุกสนานที่นี่เป็นวิธีการแก้ปัญหาทุบตีบริสุทธิ์ที่ไม่ได้ใช้shuf, sort, wc, sed, head, tailหรือเครื่องมือภายนอกอื่น ๆ

ข้อได้เปรียบเพียงอย่างเดียวของshufรุ่นนี้คือมันเร็วกว่าเล็กน้อยเนื่องจากเป็นทุบตีบริสุทธิ์ บนเครื่องของฉันสำหรับไฟล์ 1,000 บรรทัด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แน่นอนเครื่องชั่งน้ำหนักที่ดีขึ้น ดังนั้นสมมติว่าสคริปต์มีจุดประสงค์เพื่อการศึกษา:
ดีใจที่ได้

GNU / Linux / Un * x มีล้อทดสอบที่ผ่านการทดสอบมาอย่างดีจำนวนมากฉันไม่ต้องการประดิษฐ์ใหม่อีกต่อไปเว้นแต่ว่าเป็นการฝึกหัดทางวิชาการอย่างแท้จริง "shell" มีวัตถุประสงค์เพื่อใช้ในการประกอบชิ้นส่วนเล็ก ๆ น้อย ๆ ที่มีอยู่จำนวนมากซึ่งสามารถประกอบใหม่ในรูปแบบต่าง ๆ ผ่านทางอินพุต / เอาต์พุตและตัวเลือกมากมาย สิ่งอื่นใดที่มีรูปแบบไม่ดีเว้นแต่เป็นกีฬา (เช่นcodegolf.stackexchange.com/tour ) ซึ่งในกรณีนี้ให้เล่นบน ... !
ไมเคิล

2
@michael_n แม้ว่าวิธี "pure bash" ส่วนใหญ่จะมีประโยชน์สำหรับการสอนและการปรับเปลี่ยนสำหรับงานอื่น ๆ นี่คือการดำเนินการที่สมเหตุสมผล "สำหรับของจริง" มากกว่าที่คิด Bash มีให้บริการอย่างกว้างขวาง แต่shufเฉพาะ GNU Coreutils (เช่นไม่ใช่ FreeBSD 10.0) sort -Rเป็นแบบพกพา แต่แก้ปัญหา (เกี่ยวข้อง) ที่แตกต่างกัน: สตริงที่ปรากฏเป็นหลายบรรทัดมีความน่าจะเป็นเท่ากับที่ปรากฏเพียงครั้งเดียว (แน่นอนwcและยังสามารถใช้ยูทิลิตี้อื่น ๆ ได้) ฉันคิดว่าข้อ จำกัด หลักที่นี่คือสิ่งนี้ไม่เคยเลือกอะไรเลยหลังจากบรรทัดที่ 32768 (และกลายเป็นแบบสุ่มน้อยกว่าเร็วกว่านี้)
Eliah Kagan

2
Malte Skoruppa: ฉันเห็นว่าคุณได้ย้ายคำถาม PRNG bash ไปที่ 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 และยังคงมากพอที่จะแสดงความแตกต่าง.) ดูเพิ่มเติมขรุขระสาธิตภาพมากขึ้นและบิตของความโง่เขลานี้แสดงให้เห็นว่าการทำงานบนปัจจัยการผลิตขนาดใหญ่ที่ทุกสายที่มีความถี่สูง
Eliah Kagan

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มากเกินไป
Eliah Kagan

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
ไมเคิล


เช่นดูรูปภาพสุดท้ายในการทดสอบ 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 https://stackoverflow.com/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) -space มันใช้งานได้กับไฟล์ที่มีขนาดใหญ่กว่า 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 บรรทัดนั้นสามารถเอาชนะได้ด้วยการ $ string RANDOMs พร้อมกันตามต้องการ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.