Cách dễ dàng để đọc dòng ngẫu nhiên từ một tệp trong dòng lệnh Unix là gì?


Câu trả lời:


383

Bạn có thể sử dụng shuf:

shuf -n 1 $FILE

Ngoài ra còn có một tiện ích gọi là rl. Trong Debian, nó nằm trong randomize-linesgói thực hiện chính xác những gì bạn muốn, mặc dù không có sẵn trong tất cả các bản phát hành. Trên trang chủ của nó, nó thực sự khuyên bạn nên sử dụng shufthay thế (điều này không tồn tại khi nó được tạo ra, tôi tin vậy). shuflà một phần của lõi GNU, rlthì không.

rl -c 1 $FILE

2
Cảm ơn vì tiền shufboa, nó được tích hợp sẵn trong Fedora.
Cheng

5
Andalso, sort -Rchắc chắn sẽ khiến người ta phải chờ đợi rất nhiều nếu xử lý các tệp khổng lồ đáng kể - dòng 80kk -, trong khi đó, shuf -nhoạt động khá tức thời.
Rubens

23
Bạn có thể tải shuf trên OS X bằng cách cài đặt coreutilstừ Homebrew. Có thể được gọi gshufthay vì shuf.
Alyssa Ross

2
Tương tự, bạn có thể sử dụng randomize-linestrên OS X bởibrew install randomize-lines; rl -c 1 $FILE
Jamie

4
Lưu ý rằng đó shuflà một phần của GNU Coreutils và do đó không nhất thiết phải có sẵn (theo mặc định) trên các hệ thống * BSD (hoặc Mac?). Một lớp lót bên dưới của @ Tracker1 dễ di chuyển hơn (và theo thử nghiệm của tôi, nhanh hơn một chút).
Adam Katz

74

Một cách khác:

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

28
$ {RANDOM} chỉ tạo các số nhỏ hơn 32768, vì vậy đừng sử dụng số này cho các tệp lớn (ví dụ: từ điển tiếng Anh).
Ralf

3
Điều này không cung cấp cho bạn xác suất chính xác như nhau cho mỗi dòng, do hoạt động modulo. Điều này hầu như không quan trọng nếu độ dài tệp là << 32768 (và hoàn toàn không nếu nó chia số đó), nhưng có lẽ đáng chú ý.
Anaphory

10
Bạn có thể mở rộng số này thành số ngẫu nhiên 30 bit bằng cách sử dụng (${RANDOM} << 15) + ${RANDOM}. Điều này làm giảm đáng kể độ lệch và cho phép nó hoạt động đối với các tệp chứa tới 1 tỷ dòng.
nneonneo

@nneonneo: Thủ thuật rất hay, mặc dù theo liên kết này, nó phải là OR'ing của $ {RANDOM} thay vì PLUS'ing stackoverflow.com/a/19602060/293064
Jay Taylor

+|giống nhau vì ${RANDOM}là 0..32767 theo định nghĩa.
nneonneo

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

(Tôi thích cách tiếp cận shuf ở trên thậm chí còn tốt hơn - tôi thậm chí còn không biết rằng nó tồn tại và tôi sẽ không bao giờ tự mình tìm thấy công cụ đó)


10
+1 Tôi thích nó, nhưng bạn có thể cần rất gần đây sort, không hoạt động trên bất kỳ hệ thống nào của tôi (CentOS 5.5, Mac OS 10.7.2). Ngoài ra, việc sử dụng mèo vô dụng, có thể được giảm xuốngsort --random-sort < $FILE | head -n 1
Steve Kehlet

sort -R <<< $'1\n1\n2' | head -1có khả năng trả về 1 và 2, vì sort -Rsắp xếp các dòng trùng lặp với nhau. Điều tương tự áp dụng cho sort -Ru, bởi vì nó loại bỏ các dòng trùng lặp.
Lri

5
Điều này là tương đối chậm, vì toàn bộ tập tin cần được xáo trộn sorttrước khi đưa nó vào head. shufthay vào đó chọn các dòng ngẫu nhiên từ tệp và nhanh hơn nhiều đối với tôi.
Bengt

1
@SteveKehlet trong khi chúng tôi đang ở đó, sort --random-sort $FILE | headsẽ là tốt nhất, vì nó cho phép nó truy cập trực tiếp vào tệp, có thể cho phép sắp xếp song song hiệu quả
WaelJ

5
Các tùy chọn --random-sort-Rdành riêng cho sắp xếp GNU (vì vậy chúng sẽ không hoạt động với BSD hoặc Mac OS sort). GNU sort đã học các cờ đó vào năm 2005, do đó bạn cần GNU coreutils 6.0 hoặc mới hơn (ví dụ: CentOS 6).
RJHunter

31

Cái này đơn giản.

cat file.txt | shuf -n 1

Cấp điều này chỉ là một chút chậm hơn so với "shuf -n 1 file.txt" của riêng mình.


2
Câu trả lời tốt nhất. Tôi không biết về lệnh này. Lưu ý rằng -n 1chỉ định 1 dòng và bạn có thể thay đổi nó thành nhiều hơn 1. shufcũng có thể được sử dụng cho những thứ khác; Tôi chỉ đường ống ps auxgrepvới nó để giết ngẫu nhiên các quá trình khớp một phần tên.
sudo

18

perlfaq5: Làm cách nào để chọn một dòng ngẫu nhiên từ một tệp? Đây là một thuật toán lấy mẫu hồ chứa từ Sách lạc đà:

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

Điều này có một lợi thế đáng kể về không gian so với việc đọc toàn bộ tệp. Bạn có thể tìm thấy bằng chứng về phương pháp này trong Nghệ thuật lập trình máy tính, Tập 2, Mục 3.4.2, của Donald E. Knuth.


1
Chỉ nhằm mục đích đưa vào (trong trường hợp trang web được giới thiệu bị hỏng), đây là mã mà Tracker1 đã chỉ: "tên tệp mèo | perl -e 'while (<>) {đẩy (@ _, $ _);} print @ _ [rand () * @ _]; '; "
Anirvan

3
Đây là một cách sử dụng mèo vô dụng. Đây là một sửa đổi nhỏ của mã được tìm thấy trong perlfaq5 (và lịch sự của cuốn sách Lạc đà): perl -e 'srand; rand ($.) <1 && ($ line = $ _) trong khi <>; in dòng $; ' tên tệp
Ông Muskrat

ờ ... trang web được liên kết, đó là
Nathan Fellman

Tôi vừa điểm chuẩn một phiên bản N-lines của mã này shuf. Mã perl nhanh hơn một chút (nhanh hơn 8% theo thời gian của người dùng, nhanh hơn 24% theo thời gian hệ thống), mặc dù vậy, tôi đã tìm thấy mã perl "có vẻ" ít ngẫu nhiên hơn (tôi đã viết một máy hát tự động sử dụng nó).
Adam Katz

2
Thêm thực phẩm cho suy nghĩ: shuflưu trữ toàn bộ tệp đầu vào trong bộ nhớ , đó là một ý tưởng khủng khiếp, trong khi mã này chỉ lưu trữ một dòng, do đó giới hạn của mã này là số lượng dòng INT_MAX (2 ^ 31 hoặc 2 ^ 63 tùy thuộc vào vòm), giả sử bất kỳ dòng tiềm năng nào được chọn của nó phù hợp với bộ nhớ.
Adam Katz

11

sử dụng tập lệnh 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
Ngẫu nhiên có thể là 0, sed cần 1 cho dòng đầu tiên. sed -n 0p trả về lỗi.
asalamon74

mhm - khoảng $ 1 cho "tmp.txt" và $ 2 cho NUM?
blabla999

nhưng ngay cả với lỗi đáng giá một điểm, vì nó không cần perl hoặc python và hiệu quả như bạn có thể nhận được (đọc tệp chính xác hai lần nhưng không vào bộ nhớ - vì vậy nó sẽ hoạt động ngay cả với các tệp lớn).
blabla999

@ asalamon74: cảm ơn @ blabla999: nếu chúng tôi thực hiện một chức năng từ nó, ok với giá 1 đô la, nhưng tại sao không tính toán NUM?
Paolo Tedesco

Thay đổi dòng sed thành: head - $ {X} $ {FILE} | đuôi -1 nên làm điều đó
JeffK

4

Dòng bash đơn:

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

Vấn đề nhẹ: tên tập tin trùng lặp.


2
vấn đề nhẹ hơn. thực hiện điều này trên / usr / share / dict / words có xu hướng ủng hộ các từ bắt đầu bằng "A". Chơi với nó, tôi có khoảng 90% từ "A" đến 10% từ "B". Không có bắt đầu với số nào, mà tạo nên phần đầu của tập tin.
bibby

wc -l < test.txttránh phải có đường ống đến cut.
fedorqui 'SO ngừng gây hại'

3

Đây là một kịch bản Python đơn giản sẽ thực hiện công việc:

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

Sử dụng:

python randline.py file_to_get_random_line_from

1
Điều này không hiệu quả lắm. Nó dừng lại sau một dòng duy nhất. Để làm cho nó hoạt động, tôi đã làm điều này: import random, sys lines = open(sys.argv[1]).readlines() cho tôi trong phạm vi (len (dòng)): rand = Random.randint (0, len (dòng) -1) in dòng.pop (rand),
Jed Daniels

Hệ thống bình luận ngu ngốc với định dạng crappy. Không định dạng trong các bình luận hoạt động một lần?
Jed Daniels

do đó, bao gồm len(lines)có thể dẫn đến IndexError. Bạn có thể sử dụng print(random.choice(list(open(sys.argv[1])))). Ngoài ra còn có thuật toán lấy mẫu hồ chứa hiệu quả bộ nhớ .
jfs

2
Không gian khá đói; xem xét một tệp 3TB.
Michael Campbell

@MichaelCampbell: thuật toán lấy mẫu hồ chứa mà tôi đã đề cập ở trên có thể hoạt động với tệp 3TB (nếu kích thước dòng bị giới hạn).
jfs

2

Một cách khác sử dụng ' awk '

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

2
Điều đó sử dụng awk và bash ( $RANDOMlà một bashism ). Đây là một phương pháp awk (mawk) thuần túy sử dụng logic tương tự như mã perlfaq5 được trích dẫn của @ Tracker1 ở trên: awk 'rand() * NR < 1 { line = $0 } END { print line }' file.name(wow, nó thậm chí còn ngắn hơn mã perl!)
Adam Katz

Mã đó phải đọc tệp ( wc) để có được số dòng, sau đó phải đọc lại (một phần) tệp ( awk) để lấy nội dung của số dòng ngẫu nhiên đã cho. I / O sẽ đắt hơn nhiều so với việc lấy một số ngẫu nhiên. Mã của tôi chỉ đọc tệp một lần. Vấn đề với awk rand()là nó tạo hạt dựa trên giây, do đó bạn sẽ nhận được các bản sao nếu bạn chạy liên tiếp quá nhanh.
Adam Katz

1

Một giải pháp cũng hoạt động trên MacOSX và cũng nên hoạt động trên Linux (?):

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

Ở đâu:

  • N là số dòng ngẫu nhiên bạn muốn

  • NR==FNR {lineN[$1]; next}(FNR in lineN) file1 file2 -> lưu số dòng được viết file1và sau đó in dòng tương ứngfile2

  • jot -r $N 1 $(wc -l < $file)-> vẽ Nsố ngẫu nhiên ( -r) trong phạm vi (1, number_of_line_in_file)với jot. Sự thay thế quá trình <()sẽ làm cho nó trông giống như một tệp cho trình thông dịch, vì vậy file1trong ví dụ trước.

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]}

Vì $ RANDOM tạo ra số lượng ít hơn số lượng từ trong / usr / share / dict / words, có 235886 (dù sao trên máy Mac của tôi), tôi chỉ tạo 6 số ngẫu nhiên riêng biệt từ 0 đến 9 và xâu chuỗi chúng lại với nhau. Sau đó, tôi đảm bảo rằng số đó nhỏ hơn 235886. Sau đó loại bỏ các số 0 đứng đầu để lập chỉ mục các từ mà tôi lưu trữ trong mảng. Vì mỗi từ là một dòng riêng, nên có thể dễ dàng sử dụng cho bất kỳ tệp nào để chọn ngẫu nhiên một dòng.
Ken

0

Đây là những gì tôi khám phá vì Mac OS của tôi không sử dụng tất cả các câu trả lời dễ dàng. Tôi đã sử dụng lệnh jot để tạo một số vì các giải pháp biến $ RANDOM dường như không phải là rất ngẫu nhiên trong thử nghiệm của tôi. Khi thử nghiệm giải pháp của tôi, tôi có sự khác biệt lớn trong các giải pháp được cung cấp ở đầu ra.

  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

Tiếng vang của biến là để có được một hình ảnh của số ngẫu nhiên được tạo ra.


0

Chỉ sử dụng vanilla sed và awk, và không sử dụng $ RANDOM, một "lớp lót" đơn giản, tiết kiệm không gian và nhanh chóng hợp lý để chọn một dòng giả ngẫu nhiên từ một tệp có tên là FILENAME như sau:

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

(Điều này hoạt động ngay cả khi FILENAME trống, trong trường hợp đó không có dòng nào được phát ra.)

Một lợi thế có thể có của phương pháp này là nó chỉ gọi rand () một lần.

Như @AdamKatz đã chỉ ra trong các bình luận, một khả năng khác là gọi rand () cho mỗi dòng:

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

(Một bằng chứng đơn giản về tính đúng đắn có thể được đưa ra dựa trên cảm ứng.)

Hãy cẩn thận về rand()

"Trong hầu hết các triển khai awk, bao gồm gawk, rand () bắt đầu tạo số từ cùng một số bắt đầu hoặc hạt giống, mỗi khi bạn chạy awk."

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


Xem bình luận tôi đã đăng một năm trước câu trả lời này , trong đó có một giải pháp awk đơn giản hơn mà không cần sed. Cũng lưu ý cảnh báo của tôi về trình tạo số ngẫu nhiên của awk, hạt giống trong cả giây.
Adam Katz
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.