Apa cara mudah untuk membaca baris acak dari file di baris perintah Unix?


Jawaban:


383

Anda bisa menggunakan shuf:

shuf -n 1 $FILE

Ada juga utilitas yang disebut rl. Di Debian ada dalam randomize-linespaket yang melakukan persis apa yang Anda inginkan, meskipun tidak tersedia di semua distro. Di halaman beranda sebenarnya merekomendasikan penggunaan shufsebagai gantinya (yang tidak ada saat itu dibuat, saya percaya). shufadalah bagian dari GNU coreutils, rlbukan.

rl -c 1 $FILE

2
Terima kasih atas shuftipnya, ini built-in di Fedora.
Cheng

5
Andalso, sort -Rpasti akan membuat orang menunggu banyak jika berurusan dengan file yang sangat besar - 80kb baris -, sedangkan, shuf -nbertindak cukup instan.
Rubens

23
Anda bisa mendapatkan shuf di OS X dengan menginstal coreutilsdari Homebrew. Mungkin bisa disebut gshufbukan shuf.
Alyssa Ross

2
Demikian pula, Anda dapat menggunakan randomize-linesOS X olehbrew install randomize-lines; rl -c 1 $FILE
Jamie

4
Perhatikan bahwa shufini adalah bagian dari GNU Coreutils dan karena itu tidak akan selalu tersedia (secara default) pada sistem * BSD (atau Mac?). Perl satu-liner @ tracker1 di bawah ini lebih portabel (dan menurut tes saya, sedikit lebih cepat)
Adam Katz

74

Alternatif lain:

head -$((${RANDOM} % `wc -l < file` + 1)) file | tail -1

28
$ {RANDOM} hanya menghasilkan angka kurang dari 32768, jadi jangan gunakan ini untuk file besar (misalnya kamus bahasa Inggris).
Ralf

3
Ini tidak memberi Anda probabilitas yang sama persis untuk setiap baris, karena operasi modulo. Ini tidak masalah jika panjang file << 32768 (dan tidak sama sekali jika membagi nomor itu), tetapi mungkin perlu dicatat.
Anaphory

10
Anda dapat memperpanjang ini ke angka acak 30-bit dengan menggunakan (${RANDOM} << 15) + ${RANDOM}. Ini secara signifikan mengurangi bias dan memungkinkannya bekerja untuk file yang berisi hingga 1 miliar baris.
nneonneo

@nneonneo: Trik yang sangat keren, meskipun menurut tautan ini, seharusnya ATAU $ {RANDOM} bukannya PLUS'ing stackoverflow.com/a/19602060/293064
Jay Taylor

+dan |sama karena ${RANDOM}adalah 0..32767 menurut definisi.
nneonneo

71
sort --random-sort $FILE | head -n 1

(Saya suka pendekatan shuf di atas bahkan lebih baik - saya bahkan tidak tahu itu ada dan saya tidak akan pernah menemukan alat itu sendiri)


10
+1 Saya menyukainya, tetapi Anda mungkin membutuhkan yang terbaru sort, tidak bekerja pada sistem saya (CentOS 5.5, Mac OS 10.7.2). Juga, penggunaan kucing yang tidak berguna, dapat dikurangi menjadisort --random-sort < $FILE | head -n 1
Steve Kehlet

sort -R <<< $'1\n1\n2' | head -1lebih mungkin untuk mengembalikan 1 dan 2, karena sort -Rmemilah garis duplikat bersama. Hal yang sama berlaku untuk sort -Ru, karena menghapus garis duplikat.
Lri

5
Ini relatif lambat, karena seluruh file perlu dikocok sortsebelum dikirim head. shufmemilih garis acak dari file, sebagai gantinya dan jauh lebih cepat bagi saya.
Bengt

1
@SteveKehlet sementara kita melakukannya, sort --random-sort $FILE | headakan lebih baik, karena memungkinkannya untuk mengakses file secara langsung, mungkin memungkinkan penyortiran paralel yang efisien
WaelJ

5
The --random-sortdan -Ropsi khusus untuk GNU semacam (sehingga mereka tidak akan bekerja dengan BSD atau Mac OS sort). GNU mengurutkannya pada tahun 2005 sehingga Anda membutuhkan GNU coreutils 6.0 atau yang lebih baru (mis. CentOS 6).
RJHunter

31

Ini sederhana.

cat file.txt | shuf -n 1

Memang ini hanya sedikit lebih lambat daripada "shuf -n 1 file.txt" sendiri.


2
Jawaban Terbaik. Saya tidak tahu tentang perintah ini. Perhatikan bahwa -n 1menentukan 1 baris, dan Anda dapat mengubahnya menjadi lebih dari 1. shufdapat digunakan untuk hal-hal lain juga; Saya baru saja menyalurkan ps auxdan grepdengan itu untuk secara acak membunuh proses pencocokan sebagian nama.
sudo

18

perlfaq5: Bagaimana cara memilih garis acak dari suatu file? Berikut algoritma pengambilan sampel reservoir dari Buku Unta:

perl -e 'srand; rand($.) < 1 && ($line = $_) while <>; print $line;' file

Ini memiliki keuntungan yang signifikan dalam ruang dibandingkan membaca seluruh file. Anda dapat menemukan bukti metode ini di The Art of Computer Programming, Volume 2, Bagian 3.4.2, oleh Donald E. Knuth.


1
Hanya untuk tujuan penyertaan (dalam kasus situs yang dirujuk turun), inilah kode yang ditunjuk Tracker1: "nama file kucing | perl -e 'while (<>) {push (@ _, $ _);} print @ _ [rand () * @ _]; '; "
Anirvan

3
Ini adalah penggunaan kucing yang tidak berguna. Berikut sedikit modifikasi dari kode yang ditemukan di perlfaq5 (dan milik buku Camel): perl -e 'srand; rand ($.) <1 && ($ line = $ _) sementara <>; cetak $ line; ' filename
Mr. Muskrat

err ... situs tertaut, yaitu
Nathan Fellman

Saya baru saja membandingkan versi N-lines dari kode ini shuf. Kode perl sangat sedikit lebih cepat (8% lebih cepat oleh waktu pengguna, 24% lebih cepat dengan waktu sistem), meskipun secara anekdot saya telah menemukan kode perl "tampaknya" kurang acak (saya menulis jukebox menggunakannya).
Adam Katz

2
Lebih banyak bahan untuk dipikirkan: shufmenyimpan seluruh file input dalam memori , yang merupakan ide yang mengerikan, sementara kode ini hanya menyimpan satu baris, sehingga batas kode ini adalah jumlah baris INT_MAX (2 ^ 31 atau 2 ^ 63 tergantung pada Anda arch), dengan asumsi salah satu jalur potensial yang dipilih sesuai dengan memori.
Adam Katz

11

menggunakan skrip bash:

#!/bin/bash
# replace with file to read
FILE=tmp.txt
# count number of lines
NUM=$(wc - l < ${FILE})
# generate random number in range 0-NUM
let X=${RANDOM} % ${NUM} + 1
# extract X-th line
sed -n ${X}p ${FILE}

1
Acak bisa 0, sed perlu 1 untuk baris pertama. sed -n 0p mengembalikan kesalahan.
asalamon74

mhm - bagaimana dengan $ 1 untuk "tmp.txt" dan $ 2 untuk NUM?
blabla999

tetapi bahkan dengan bug itu ada benarnya, karena tidak perlu perl atau python dan seefisien yang Anda bisa (membaca file persis dua kali tetapi tidak ke dalam memori - sehingga itu akan bekerja bahkan dengan file besar).
blabla999

@ asalamon74: terima kasih @ blabla999: jika kita membuat fungsi darinya, ok untuk $ 1, tapi mengapa tidak menghitung NUM?
Paolo Tedesco

Mengubah baris sed ke: head - $ {X} $ {FILE} | tail -1 harus melakukannya
JeffK

4

Garis bash tunggal:

sed -n $((1+$RANDOM%`wc -l test.txt | cut -f 1 -d ' '`))p test.txt

Sedikit masalah: duplikat nama file.


2
masalah yang lebih ringan. melakukan ini di / usr / share / dict / kata cenderung mendukung kata-kata yang dimulai dengan "A". Bermain dengan itu, saya sekitar 90% kata "A" menjadi 10% kata "B". Belum ada yang dimulai dengan angka, yang merupakan kepala file.
Bibby

wc -l < test.txtmenghindari harus pipa ke cut.
fedorqui 'SO berhenti merugikan'

3

Berikut skrip Python sederhana yang akan melakukan pekerjaan:

import random, sys
lines = open(sys.argv[1]).readlines()
print(lines[random.randrange(len(lines))])

Pemakaian:

python randline.py file_to_get_random_line_from

1
Ini tidak berhasil. Itu berhenti setelah satu baris. Untuk membuatnya bekerja, saya melakukan ini: import random, sys lines = open(sys.argv[1]).readlines() untuk saya dalam jangkauan (len (baris)): rand = random.randint (0, len (lines) -1) mencetak lines.pop (rand),
Jed Daniels

Sistem komentar bodoh dengan format jelek. Tidak memformat dalam komentar berfungsi satu kali?
Jed Daniels

Randand inklusif karena itu len(lines)dapat menyebabkan IndexError. Anda bisa menggunakannya print(random.choice(list(open(sys.argv[1])))). Ada juga algoritma pengambilan sampel reservoir efisien memori .
jfs

2
Cukup lapar; pertimbangkan file 3TB.
Michael Campbell

@MichaelCampbell: algoritma sampling reservoir yang telah saya sebutkan di atas dapat bekerja dengan file 3TB (jika ukuran garis terbatas).
jfs

2

Cara lain menggunakan ' awk '

awk NR==$((${RANDOM} % `wc -l < file.name` + 1)) file.name

2
Itu menggunakan awk dan bash ( $RANDOMadalah bashism ). Berikut ini adalah metode awk (mawk) murni menggunakan logika yang sama dengan kode perlfaq5 yang dikutip oleh @ Tracker1 di atas: awk 'rand() * NR < 1 { line = $0 } END { print line }' file.name(wow, ini bahkan lebih pendek dari kode perl!)
Adam Katz

Kode itu harus membaca file ( wc) untuk mendapatkan jumlah baris, kemudian harus membaca (bagian dari) file itu lagi ( awk) untuk mendapatkan konten dari nomor baris acak yang diberikan. I / O akan jauh lebih mahal daripada mendapatkan nomor acak. Kode saya hanya membaca file sekali. Masalah dengan awk rand()adalah bahwa seed berdasarkan pada detik, sehingga Anda akan mendapatkan duplikat jika Anda menjalankannya terlalu cepat secara berurutan.
Adam Katz

1

Solusi yang juga berfungsi di MacOSX, dan seharusnya juga bekerja di Linux (?):

N=5
awk 'NR==FNR {lineN[$1]; next}(FNR in lineN)' <(jot -r $N 1 $(wc -l < $file)) $file 

Dimana:

  • N adalah jumlah garis acak yang Anda inginkan

  • NR==FNR {lineN[$1]; next}(FNR in lineN) file1 file2 -> simpan nomor baris yang ditulis file1dan kemudian cetak baris yang sesuaifile2

  • jot -r $N 1 $(wc -l < $file)-> menggambar Nangka secara acak ( -r) dalam kisaran (1, number_of_line_in_file)dengan jot. Substitusi proses <()akan membuatnya terlihat seperti file untuk penerjemah, jadi file1pada contoh sebelumnya.

0
#!/bin/bash

IFS=$'\n' wordsArray=($(<$1))

numWords=${#wordsArray[@]}
sizeOfNumWords=${#numWords}

while [ True ]
do
    for ((i=0; i<$sizeOfNumWords; i++))
    do
        let ranNumArray[$i]=$(( ( $RANDOM % 10 )  + 1 ))-1
        ranNumStr="$ranNumStr${ranNumArray[$i]}"
    done
    if [ $ranNumStr -le $numWords ]
    then
        break
    fi
    ranNumStr=""
done

noLeadZeroStr=$((10#$ranNumStr))
echo ${wordsArray[$noLeadZeroStr]}

Karena $ RANDOM menghasilkan angka lebih sedikit dari jumlah kata di / usr / share / dict / words, yang memiliki 235886 (pada Mac saya), saya hanya menghasilkan 6 angka acak terpisah antara 0 dan 9 dan merangkai mereka bersama-sama. Lalu saya memastikan bahwa jumlahnya kurang dari 235886. Kemudian hapus nol terkemuka untuk mengindeks kata-kata yang saya simpan dalam array. Karena setiap kata adalah barisnya sendiri, ini dapat dengan mudah digunakan untuk file apa pun untuk memilih satu baris secara acak.
Ken

0

Inilah yang saya temukan karena Mac OS saya tidak menggunakan semua jawaban mudah. Saya menggunakan perintah jot untuk menghasilkan angka karena solusi variabel $ RANDOM tampaknya tidak terlalu acak dalam pengujian saya. Saat menguji solusi saya, saya memiliki varian yang luas dalam solusi yang disediakan dalam output.

  RANDOM1=`jot -r 1 1 235886`
   #range of jot ( 1 235886 ) found from earlier wc -w /usr/share/dict/web2
   echo $RANDOM1
   head -n $RANDOM1 /usr/share/dict/web2 | tail -n 1

Gema variabel adalah untuk mendapatkan visual dari angka acak yang dihasilkan.


0

Hanya menggunakan vanilla sed dan awk, dan tanpa menggunakan $ RANDOM, "one-liner" sederhana, hemat ruang, dan cukup cepat untuk memilih satu baris pseudo-acak dari file bernama FILENAME adalah sebagai berikut:

sed -n $(awk 'END {srand(); r=rand()*NR; if (r<NR) {sub(/\..*/,"",r); r++;}; print r}' FILENAME)p FILENAME

(Ini berfungsi bahkan jika FILENAME kosong, dalam hal ini tidak ada garis yang dipancarkan.)

Satu keuntungan yang mungkin dari pendekatan ini adalah hanya memanggil rand () sekali.

Seperti yang ditunjukkan oleh @AdamKatz di komentar, kemungkinan lain adalah memanggil rand () untuk setiap baris:

awk 'rand() * NR < 1 { line = $0 } END { print line }' FILENAME

(Bukti kebenaran sederhana dapat diberikan berdasarkan induksi.)

Peringatan tentang rand()

"Di sebagian besar implementasi awk, termasuk gawk, rand () mulai menghasilkan angka dari nomor awal yang sama, atau seed, setiap kali Anda menjalankan awk."

- https://www.gnu.org/software/gawk/manual/html_node/Numeric-Functions.html


Lihat komentar yang saya posting setahun sebelum jawaban ini , yang memiliki solusi awk sederhana yang tidak memerlukan sed. Perhatikan juga peringatan saya tentang generator nomor acak awk, yang berbiji pada detik penuh.
Adam Katz
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.