Bagaimana cara menampilkan garis acak dari file teks?

26

Saya mencoba menulis skrip shell. Idenya adalah untuk memilih satu baris secara acak dari file teks dan menampilkannya sebagai notifikasi desktop Ubuntu.

Tapi saya ingin baris yang berbeda dipilih setiap kali saya menjalankan skrip. Apakah ada solusi untuk melakukan ini? Saya tidak ingin seluruh skrip. Hanya hal sederhana itu saja.

Anandu M Das
sumber
Kunjungi juga: askubuntu.com/q/492572/256099
Pandya
stackoverflow.com/questions/448005/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Jawaban:

40

Anda dapat menggunakan shufutilitas untuk mencetak garis acak dari file

$ shuf -n 1 filename

-n : jumlah garis untuk dicetak

Contoh:

$ 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
aneeshep
sumber
Tetapi dengan menggunakan ini, saya harus mengubah nilai n secara manual bukan? Saya ingin shell itu secara otomatis memilih baris lain secara acak. Tidak perlu secara acak. Tetapi beberapa baris lainnya.
Anandu M Das
4
@AnanduMDas Tidak, Anda tidak perlu nmenunjukkan jumlah baris yang akan dicetak. (Yaitu apakah Anda ingin hanya satu baris atau dua baris). Bukan nomor baris (yaitu baris kedua baris pertama).
aneeshep
@AnanduMDas: Saya telah menambahkan beberapa contoh untuk jawaban saya. Semoga ini jelas sekarang.
aneeshep
1
Terima kasih sudah jelas sekarang :) Saya juga menemukan algoritma lain, seperti, menyimpan waktu saat ini (hanya kedua, oleh date +%S) ke dalam variabel x, dan kemudian memilih baris ke-X menggunakan perintah headdan taildari file teks. Bagaimanapun metode Anda lebih mudah. Terima kasih
Anandu M Das
+1: shufada di coreutils sehingga tersedia secara default. Catatan: ini memuat file input ke dalam memori. Ada algoritma efisien yang tidak memerlukannya .
jfs
13

Anda juga dapat menggunakan sortperintah untuk mendapatkan garis acak dari file.

sort -R filename | head -n1
g_p
sumber
catatan: sort -Rmenghasilkan hasil yang berbeda dari shuf -n1atau select-randomjika ada garis duplikat di input. Lihat komentar @ EliahKagan .
jfs
8

Just for fun, di sini adalah solusi pesta murni yang tidak menggunakan shuf, sort, wc, sed, head, tailatau alat eksternal lainnya.

Satu-satunya keunggulan dibandingkan shufvarian adalah sedikit lebih cepat, karena ini murni bash. Di komputer saya, untuk file 1000 baris, shufvarian membutuhkan waktu sekitar 0,1 detik, sedangkan skrip berikut membutuhkan waktu sekitar 0,01 detik;) Jadi, sementara itu shufadalah varian termudah dan terpendek, ini lebih cepat.

Dalam semua kejujuran saya masih akan mencari shufsolusinya, kecuali efisiensi tinggi adalah masalah penting.

#!/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"
Malte Skoruppa
sumber
@EliahKagan Terima kasih atas saran dan poin bagusnya. Saya akui ada beberapa kasus sudut yang tidak terlalu saya pikirkan. Saya menulis ini lebih untuk bersenang-senang. Lagi shufpula, menggunakan jauh lebih baik. Memikirkan itu, saya tidak percaya bahwa bash murni sebenarnya lebih efisien daripada menggunakan shuf, seperti yang saya tulis sebelumnya. Mungkin ada overhead terkecil (konstan) ketika menjalankan alat eksternal, tetapi kemudian itu akan menjalankan mach lebih cepat daripada bash yang ditafsirkan. Jadi shuftentu saja skala lebih baik. Jadi, katakanlah skrip tersebut memiliki tujuan pendidikan: Senang rasanya bisa dilakukan;)
Malte Skoruppa
GNU / Linux / Un * x memiliki banyak roda teruji jalan yang sangat baik, saya tidak ingin menemukan kembali, kecuali jika itu adalah latihan murni akademis. "Shell" dimaksudkan untuk digunakan untuk merakit banyak bagian kecil yang ada yang dapat (dipasang kembali) dengan berbagai cara melalui input / output & banyak opsi. Yang lainnya adalah bentuk yang buruk, kecuali untuk olahraga (misalnya, codegolf.stackexchange.com/tour ), dalam hal ini, mainkan ...!
michael
2
@michael_n Meskipun cara "bash murni" terutama berguna untuk mengajar dan memodifikasi untuk tugas-tugas lain, ini adalah implementasi "nyata" yang lebih masuk akal daripada yang terlihat. Bash tersedia secara luas, tetapi shufGNU Coreutils - spesifik (mis. Tidak dalam FreeBSD 10.0). sort -Rbersifat portabel, tetapi memecahkan masalah (terkait) yang berbeda: string yang muncul karena beberapa baris memiliki probabilitas yang sama dengan yang muncul hanya sekali. (Tentu saja, wcdan utilitas lain masih dapat digunakan.) Saya pikir batasan utama di sini adalah ini tidak pernah mengambil apa pun setelah garis 32768 (dan menjadi kurang acak agak cepat).
Eliah Kagan
2
Malte Skoruppa: Saya melihat Anda telah memindahkan pertanyaan PRNG bash ke U&L . Keren. Petunjuk: $((RANDOM<<15|RANDOM))ada di 0..2 ^ 30-1. @ JSFSebastian Bukan shuf, ini sort -Rcondong ke input yang lebih sering. Masukan shuf -n 1di tempat sort -R | head -n1dan membandingkan. (Btw 10 ^ 3 iterasi lebih cepat dari 10 ^ 6 dan masih cukup untuk menunjukkan perbedaannya.) Lihat juga demo yang lebih kasar dan lebih visual dan sedikit kekonyolan ini menunjukkan itu bekerja pada input besar di mana semua string frekuensi tinggi .
Eliah Kagan
1
@ JFSebastian Dalam perintah itu, input ke diehardertampaknya semua nol. Dengan asumsi ini bukan hanya kesalahan aneh di pihak saya, itu pasti akan menjelaskan mengapa itu tidak acak! Apakah Anda mendapatkan data yang terlihat bagus jika Anda menjalankannya while echo $(( RANDOM << 17 | RANDOM << 2 | RANDOM >> 13 )); do :; done | perl -ne 'print pack "I>"' > outsebentar dan kemudian memeriksa isinya outdengan hex editor? (Atau melihatnya namun lain Anda seperti.) Saya mendapatkan semua nol, dan RANDOMtidak pelakunya: saya mendapatkan semua nol ketika saya mengganti $(( RANDOM << 17 | RANDOM << 2 | RANDOM >> 13 ))dengan 100, juga.
Eliah Kagan
4

Katakanlah Anda punya file notifications.txt. Kita perlu menghitung jumlah garis, untuk menentukan rentang generator acak:

$ cat notifications.txt | wc -l

Mari kita menulis ke variabel:

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

Sekarang untuk menghasilkan angka dari 0ke $LINEkita akan menggunakan RANDOMvariabel.

$ echo $[ $RANDOM % LINES]

Mari kita tulis ke variabel:

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

Sekarang kita hanya perlu mencetak nomor baris ini:

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

Tentang ACAK:

   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.

Pastikan file Anda memiliki kurang dari 32767 nomor baris. Lihat ini jika Anda memerlukan generator acak yang lebih besar yang berfungsi di luar kotak.

Contoh:

$ od -A n -t d -N 3 /dev/urandom | tr -d ' '
c0rp
sumber
Alternatif gaya (bash):LINES=$(wc -l < file.txt); R_LINE=$((RANDOM % LINES)); sed -n "${R_LINE}p" file.txt
michael
misalnya, lihat gambar terakhir di Uji PRNG menggunakan bitmap abu-abu untuk memahami mengapa itu bukan ide yang baik untuk diterapkan % nke angka acak.
jfs
2

Berikut skrip Python yang memilih garis acak dari file input atau 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

Algoritma ini adalah O (n) -waktu, O (1) -ruang. Ini berfungsi untuk file yang lebih besar dari 32767 baris. Itu tidak memuat file input ke dalam memori. Bunyinya setiap baris input persis sekali yaitu, Anda dapat menyalurkan konten besar (tapi terbatas) sembarangan ke dalamnya. Berikut penjelasan algoritma .

jfs
sumber
1

Saya terkesan dengan pekerjaan yang dilakukan Malte Skoruppa dan yang lainnya, tetapi di sini ada cara "bash murni" yang jauh lebih sederhana untuk melakukannya:

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

Seperti yang telah dicatat beberapa orang, $ ACAK tidak acak. Namun, batas ukuran file 32767 baris diatasi dengan merangkai $ ACAK bersama-sama sesuai kebutuhan.

Pemboros
sumber