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

263

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

codeforester
sumber
Apakah setiap baris diisi dengan panjang tetap?
Tracker1
tidak, setiap baris memiliki jumlah karakter yang bervariasi
file besar: stackoverflow.com/questions/29102589/…
Ciro Santilli 郝海东 冠状 病 六四 六四 事件

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
rogerdpack
sumber
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
PolyThinker
sumber
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)

Thomas Vander Stichele
sumber
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.

Yokai
sumber
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.

Pelacak1
sumber
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}
Paolo Tedesco
sumber
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.

asalamon74
sumber
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
Adam Rosenfield
sumber
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
Baskar
sumber
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.
jrjc
sumber
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]}
Ken
sumber
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.

dreday13
sumber
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

puncak
sumber
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