Làm cách nào tôi có thể xáo trộn các dòng của tệp văn bản trên dòng lệnh Unix hoặc trong tập lệnh shell?


285

Tôi muốn xáo trộn các dòng của tệp văn bản một cách ngẫu nhiên và tạo một tệp mới. Các tập tin có thể có vài ngàn dòng.

Làm thế nào tôi có thể làm điều đó với cat, awk, cut, vv?



Đúng, có một số câu trả lời hay khác trong câu hỏi ban đầu là tốt.
Ruggiero Spearman

Vì vậy, bạn đã làm một danh sách từ wpa? (chỉ là một phỏng đoán ngẫu nhiên)
thahgr

Câu trả lời:


360

Bạn có thể sử dụng shuf. Trên một số hệ thống ít nhất (dường như không có trong POSIX).

Như jleedev đã chỉ ra: sort -Rcũng có thể là một lựa chọn. Trên một số hệ thống ít nhất; Vâng, bạn có được hình ảnh. Nó đã được chỉ ra rằng sort -Rkhông thực sự xáo trộn mà thay vào đó sắp xếp các mục theo giá trị băm của chúng.

[Ghi chú của biên tập viên: sort -R gần như xáo trộn, ngoại trừ các dòng / khóa sắp xếp trùng lặp luôn luôn nằm cạnh nhau . Nói cách khác: chỉ với các dòng / khóa đầu vào duy nhất thì nó là một sự xáo trộn thực sự. Trong khi đó là sự thật rằng thứ tự đầu ra được xác định bởi giá trị băm , tính ngẫu nhiên xuất phát từ việc lựa chọn một băm ngẫu nhiên chức năng - xem tay ].


31
shufsort -Rhơi khác nhau, bởi vì sort -Rngẫu nhiên sắp xếp các phần tử theo hàm băm của chúng, nghĩa là sort -Rsẽ đặt các phần tử lặp lại với nhau, trong khi shufxáo trộn tất cả các phần tử một cách ngẫu nhiên.
SeMeKh

146
Đối với người dùng OS X : brew install coreutils, sau đó sử dụng gshuf ...(:
ELLIOTTCABLE

15
sort -Rshufnên được xem là hoàn toàn khác nhau. sort -Rmang tính quyết định. Nếu bạn gọi nó hai lần vào các thời điểm khác nhau trên cùng một đầu vào, bạn sẽ nhận được cùng một câu trả lời. shufmặt khác, tạo ra đầu ra ngẫu nhiên, do đó rất có thể sẽ cung cấp đầu ra khác nhau trên cùng một đầu vào.
EfForEffort

18
Đó là không đúng. "sort -R" sử dụng khóa băm ngẫu nhiên khác nhau mỗi lần bạn gọi nó, do đó, nó tạo ra đầu ra khác nhau mỗi lần.
Đánh dấu Pettit

3
Lưu ý về tính ngẫu nhiên: theo các tài liệu GNU, "Theo mặc định, các lệnh này sử dụng trình tạo giả ngẫu nhiên nội bộ được khởi tạo bởi một lượng nhỏ entropy, nhưng có thể được chuyển hướng để sử dụng nguồn bên ngoài với tùy chọn tệp --random-source =."
Royce Williams

85

Perl one-liner sẽ là phiên bản đơn giản của giải pháp Maxim's

perl -MList::Util=shuffle -e 'print shuffle(<STDIN>);' < myfile

6
Tôi đã bí danh điều này để xáo trộn trên OS X. Cảm ơn!
Mèo Unun

Đây là kịch bản duy nhất trên trang này trả về các dòng ngẫu nhiên THỰC SỰ. Các giải pháp awk khác thường được in đầu ra trùng lặp.
Felipe Alvarez

1
Nhưng hãy cẩn thận vì trong ra ngoài bạn sẽ mất một dòng :) Nó sẽ được nối với một dòng khác :)
JavaRunner

@JavaRunner: Tôi giả sử bạn đang nói về đầu vào mà không có dấu vết \n; vâng, điều đó \nphải có mặt - và thông thường - nếu không bạn sẽ nhận được những gì bạn mô tả.
mkuity0

1
Tuyệt vời súc tích. Tôi đề nghị thay thế <STDIN>bằng <>, vì vậy giải pháp cũng hoạt động với đầu vào từ các tệp .
mkuity0

60

Câu trả lời này bổ sung cho nhiều câu trả lời tuyệt vời hiện có theo các cách sau:

  • Các câu trả lời hiện có được đóng gói thành các hàm shell linh hoạt :

    • Các hàm không chỉ stdinnhập, mà cả các đối số tên tệp
    • Các chức năng thực hiện các bước bổ sung để xử lý SIGPIPEtheo cách thông thường (chấm dứt yên tĩnh với mã thoát 141), trái ngược với việc phá vỡ ồn ào. Điều này rất quan trọng khi đường ống đầu ra chức năng đến một đường ống được đóng sớm, chẳng hạn như khi đường ống đến head.
  • Một so sánh hiệu suất được thực hiện.


shuf() { awk 'BEGIN {srand(); OFMT="%.17f"} {print rand(), $0}' "$@" |
               sort -k1,1n | cut -d ' ' -f2-; }
shuf() { perl -MList::Util=shuffle -e 'print shuffle(<>);' "$@"; }
shuf() { python -c '
import sys, random, fileinput; from signal import signal, SIGPIPE, SIG_DFL;    
signal(SIGPIPE, SIG_DFL); lines=[line for line in fileinput.input()];   
random.shuffle(lines); sys.stdout.write("".join(lines))
' "$@"; }

Xem phần dưới cùng cho phiên bản Windows của chức năng này.

shuf() { ruby -e 'Signal.trap("SIGPIPE", "SYSTEM_DEFAULT");
                     puts ARGF.readlines.shuffle' "$@"; }

So sánh hiệu suất:

Lưu ý: Những con số này đã thu được trên iMac cuối năm 2012 với Intel Core i5 3,2 GHz và Fusion Drive, chạy OSX 10.10.3. Mặc dù thời gian sẽ thay đổi theo hệ điều hành được sử dụng, thông số kỹ thuật của máy, awkviệc triển khai được sử dụng (ví dụ: awkphiên bản BSD được sử dụng trên OSX thường chậm hơn GNU awkvà đặc biệt mawk), điều này sẽ mang lại cảm giác chung về hiệu suất tương đối .

Tệp đầu vào là tệp 1 triệu dòng được tạo bằng seq -f 'line %.0f' 1000000.
Thời gian được liệt kê theo thứ tự tăng dần (đầu tiên nhanh nhất):

  • shuf
    • 0.090s
  • Ruby 2.0.0
    • 0.289s
  • Perl 5.18.2
    • 0.589s
  • Con trăn
    • 1.342svới Python 2.7.6; 2.407s(!) với Python 3.4.2
  • awk+ sort+cut
    • 3.003svới BSD awk; 2.388svới GNU awk(4.1.1); 1.811svới mawk(1.3.4);

Để so sánh thêm, các giải pháp không được đóng gói như các chức năng trên:

  • sort -R (không phải là một sự xáo trộn thực sự nếu có các dòng đầu vào trùng lặp)
    • 10.661s - phân bổ thêm bộ nhớ dường như không tạo ra sự khác biệt
  • Scala
    • 24.229s
  • bash vòng lặp + sort
    • 32.593s

Kết luận :

  • Sử dụng shuf, nếu bạn có thể - đó là nhanh nhất cho đến nay.
  • Ruby làm tốt, tiếp theo là Perl .
  • Python chậm hơn đáng kể so với Ruby và Perl, và, so sánh các phiên bản Python, 2.7.6 nhanh hơn một chút so với 3.4.1
  • Sử dụng POSIX-compliant awk+ sort+ cutkết hợp như một phương sách cuối cùng ; mà awkthực hiện bạn sử dụng các vấn đề ( mawknhanh hơn GNU awk, BSD awklà chậm nhất).
  • Tránh xa sort -R, bashvòng lặp và Scala.

Các phiên bản Windows của giải pháp Python (mã Python giống hệt nhau, ngoại trừ các biến thể trong trích dẫn và loại bỏ các câu lệnh liên quan đến tín hiệu không được hỗ trợ trên Windows):

  • Đối với PowerShell (trong Windows PowerShell, bạn sẽ phải điều chỉnh $OutputEncodingnếu bạn muốn gửi các ký tự không phải ASCII qua đường ống):
# Call as `shuf someFile.txt` or `Get-Content someFile.txt | shuf`
function shuf {
  $Input | python -c @'
import sys, random, fileinput;
lines=[line for line in fileinput.input()];
random.shuffle(lines); sys.stdout.write(''.join(lines))
'@ $args  
}

Lưu ý rằng PowerShell có thể tự xáo trộn thông qua Get-Randomlệnh ghép ngắn của nó (mặc dù hiệu suất có thể là một vấn đề); ví dụ:
Get-Content someFile.txt | Get-Random -Count ([int]::MaxValue)

  • Dành cho cmd.exe(một tệp bó):

Lưu vào tập tin shuf.cmd, ví dụ:

@echo off
python -c "import sys, random, fileinput; lines=[line for line in fileinput.input()]; random.shuffle(lines); sys.stdout.write(''.join(lines))" %*

SIGPIPE không tồn tại trên Windows, vì vậy tôi đã sử dụng một lớp lót đơn giản này thay vào đó:python -c "import sys, random; lines = [x for x in sys.stdin.read().splitlines()] ; random.shuffle(lines); print(\"\n\".join([line for line in lines]));"
elig

@elig: Cảm ơn, nhưng bỏ qua from signal import signal, SIGPIPE, SIG_DFL; signal(SIGPIPE, SIG_DFL);giải pháp ban đầu là đủ và vẫn giữ được tính linh hoạt để có thể vượt qua các đối số tên tệp - không cần thay đổi bất cứ điều gì khác (ngoại trừ trích dẫn) - vui lòng xem phần mới tôi đã thêm vào đáy.
mkuity0

27

Tôi sử dụng một tập lệnh perl nhỏ, mà tôi gọi là "unort":

#!/usr/bin/perl
use List::Util 'shuffle';
@list = <STDIN>;
print shuffle(@list);

Tôi cũng đã có một phiên bản được phân định bằng NULL, được gọi là "unsort0" ... tiện dụng để sử dụng với find -print0, v.v.

PS: Được bình chọn là 'shuf', tôi không biết rằng có trong coreutils những ngày này ... những điều trên có thể vẫn hữu ích nếu hệ thống của bạn không có 'shuf'.


một cái hay, RHEL 5.6 không có shuf (
Maxim Egorushkin

1
Hoàn thành tốt; Tôi đề nghị thay thế <STDIN>bằng <>để làm cho giải pháp hoạt động với đầu vào từ các tập tin quá.
mkuity0

20

Đây là lần thử đầu tiên dễ dàng với bộ mã hóa nhưng khó với CPU có số ngẫu nhiên cho mỗi dòng, sắp xếp chúng và sau đó tách số ngẫu nhiên từ mỗi dòng. Trong thực tế, các dòng được sắp xếp ngẫu nhiên:

cat myfile | awk 'BEGIN{srand();}{print rand()"\t"$0}' | sort -k1 -n | cut -f2- > myfile.shuffled

8
UUOC. truyền file cho awk chính nó.
ghostdog74

1
Phải, tôi gỡ lỗi với head myfile | awk .... Sau đó, tôi chỉ cần thay đổi nó thành con mèo; đó là lý do tại sao nó bị bỏ lại ở đó
Ruggiero Spearman

Không cần -k1 -nsắp xếp, vì đầu ra của awk rand()là số thập phân từ 0 đến 1 và bởi vì tất cả vấn đề là nó được sắp xếp lại theo cách nào đó. -k1có thể giúp tăng tốc nó bằng cách bỏ qua phần còn lại của dòng, mặc dù đầu ra của rand () phải đủ duy nhất để đoản mạch so sánh.
bonsaiviking 19/03 '

@ ghostdog74: Hầu hết cái gọi là sử dụng vô dụng của mèo thực sự hữu ích cho việc thống nhất giữa các lệnh có đường ống và không. Tốt hơn là giữ cat filename |(hoặc < filename |) hơn là nhớ cách mỗi chương trình lấy tệp đầu vào (hoặc không).
ShreevatsaR

2
shuf () {awk 'BEGIN {srand ()} {in rand () "\ t" $ 0}' "$ @" | sắp xếp | cắt -f2-;}
Meow

16

đây là một kịch bản awk

awk 'BEGIN{srand() }
{ lines[++d]=$0 }
END{
    while (1){
    if (e==d) {break}
        RANDOM = int(1 + rand() * d)
        if ( RANDOM in lines  ){
            print lines[RANDOM]
            delete lines[RANDOM]
            ++e
        }
    }
}' file

đầu ra

$ cat file
1
2
3
4
5
6
7
8
9
10

$ ./shell.sh
7
5
10
9
6
8
2
1
3
4

Hoàn thành tốt, nhưng trong thực tế chậm hơn nhiều so với câu trả lời của chính OP , kết hợp awkvới sortcut. Đối với không quá vài nghìn dòng, nó không tạo ra nhiều sự khác biệt, nhưng với số dòng cao hơn thì nó có vấn đề (ngưỡng phụ thuộc vào việc awktriển khai được sử dụng). Một đơn giản hóa nhỏ sẽ là để thay thế các dòng while (1){if (e==d) {break}với while (e<d).
mkuity0

11

Một lớp lót cho trăn:

python -c "import random, sys; lines = open(sys.argv[1]).readlines(); random.shuffle(lines); print ''.join(lines)," myFile

Và để in chỉ một dòng ngẫu nhiên duy nhất:

python -c "import random, sys; print random.choice(open(sys.argv[1]).readlines())," myFile

Nhưng hãy xem bài viết này để biết nhược điểm của python random.shuffle(). Nó sẽ không hoạt động tốt với nhiều yếu tố (hơn 2080).


2
"nhược điểm" không dành riêng cho Python. Các giai đoạn PRNG hữu hạn có thể được giải quyết bằng cách sắp xếp lại PRNG với entropy từ hệ thống như /dev/urandomhiện tại. Để sử dụng nó từ Python : random.SystemRandom().shuffle(L).
jfs

không phải tham gia () cần phải ở trên '\ n' để các dòng được in riêng?
elig

@elig: Không, vì .readLines()trả về các dòng dòng mới.
mkuity0

9

Hàm dựa trên awk đơn giản sẽ thực hiện công việc:

shuffle() { 
    awk 'BEGIN{srand();} {printf "%06d %s\n", rand()*1000000, $0;}' | sort -n | cut -c8-
}

sử dụng:

any_command | shuffle

Điều này sẽ làm việc trên hầu hết các UNIX. Đã thử nghiệm trên Linux, Solaris và HP-UX.

Cập nhật:

Lưu ý rằng các số 0 ( %06d) và rand()phép nhân hàng đầu làm cho nó hoạt động chính xác cũng trên các hệ thống sortkhông hiểu số. Nó có thể được sắp xếp thông qua thứ tự từ điển (hay còn gọi là so sánh chuỗi bình thường).


Ý tưởng tốt để đóng gói câu trả lời của OP là một chức năng; nếu bạn nối thêm "$@", nó cũng sẽ hoạt động với các tệp làm đầu vào. Không có lý do để nhân rand(), bởi vì sort -ncó khả năng sắp xếp các phân số thập phân. Tuy nhiên, nên kiểm soát awkđịnh dạng đầu ra của định dạng, bởi vì với định dạng mặc định %.6g, rand()sẽ xuất ra số thỉnh thoảng theo ký hiệu số mũ . Mặc dù việc xáo trộn lên tới 1 triệu dòng là đủ trong thực tế, nhưng thật dễ dàng để hỗ trợ nhiều dòng hơn mà không phải trả nhiều tiền cho hiệu suất; ví dụ %.17f.
mkuity0

1
@ mkuity0 Tôi không nhận thấy câu trả lời của OP trong khi viết của tôi. rand () được nhân với 10e6 để làm cho nó hoạt động với solaris hoặc sắp xếp hpux theo như tôi nhớ. Ý tưởng hay với "$ @"
Michał rajer

1
Hiểu rồi, cảm ơn; có lẽ bạn có thể thêm lý do này cho phép nhân cho câu trả lời; nói chung, theo POSIX, sortsẽ có thể xử lý các phân số thập phân (ngay cả với hàng nghìn dấu phân cách, như tôi vừa nhận thấy).
mkuity0

7

Ruby FTW:

ls | ruby -e 'puts STDIN.readlines.shuffle'

1
Công cụ tuyệt vời; Nếu bạn sử dụng puts ARGF.readlines.shuffle, bạn có thể làm cho nó hoạt động với cả đối số đầu vào stdin và tên tệp.
mkuity0

Thậm chí ngắn hơn ruby -e 'puts $<.sort_by{rand}'- ARGF đã là một vô số, vì vậy chúng ta có thể xáo trộn các dòng bằng cách sắp xếp nó theo các giá trị ngẫu nhiên.
akuhn 24/07/2015

6

Một lớp lót cho Python dựa trên câu trả lời của scai , nhưng a) lấy stdin, b) làm cho kết quả có thể lặp lại với seed, c) chỉ chọn ra 200 dòng.

$ cat file | python -c "import random, sys; 
  random.seed(100); print ''.join(random.sample(sys.stdin.readlines(), 200))," \
  > 200lines.txt

6

Một cách đơn giản và trực quan sẽ được sử dụng shuf.

Thí dụ:

Giả sử words.txtnhư:

the
an
linux
ubuntu
life
good
breeze

Để xáo trộn các dòng, làm:

$ shuf words.txt

trong đó sẽ ném các dòng xáo trộn đến đầu ra tiêu chuẩn ; Vì vậy, bạn đã để ống nó vào một tập tin đầu ra như sau:

$ shuf words.txt > shuffled_words.txt

Một lần chạy ngẫu nhiên như vậy có thể mang lại:

breeze
the
linux
an
ubuntu
good
life

4

Chúng tôi có một gói để thực hiện công việc:

sudo apt-get install randomize-lines

Thí dụ:

Tạo một danh sách các số theo thứ tự và lưu nó vào 1000.txt:

seq 1000 > 1000.txt

để xáo trộn nó, chỉ cần sử dụng

rl 1000.txt

3

Đây là tập lệnh python mà tôi đã lưu dưới dạng rand.py trong thư mục nhà của mình:

#!/bin/python

import sys
import random

if __name__ == '__main__':
  with open(sys.argv[1], 'r') as f:
    flist = f.readlines()
    random.shuffle(flist)

    for line in flist:
      print line.strip()

Trên Mac OSX sort -Rshufkhông có sẵn để bạn có thể đặt bí danh này trong bash_profile của mình dưới dạng:

alias shuf='python rand.py'

3

Nếu như tôi, bạn đã đến đây để tìm kiếm một thay thế shufcho macOS thì hãy sử dụng randomize-lines.

Cài đặt randomize-linesgói (homebrew), có rllệnh có chức năng tương tự shuf.

brew install randomize-lines

Usage: rl [OPTION]... [FILE]...
Randomize the lines of a file (or stdin).

  -c, --count=N  select N lines from the file
  -r, --reselect lines may be selected multiple times
  -o, --output=FILE
                 send output to file
  -d, --delimiter=DELIM
                 specify line delimiter (one character)
  -0, --null     set line delimiter to null character
                 (useful with find -print0)
  -n, --line-number
                 print line number with output lines
  -q, --quiet, --silent
                 do not output any errors or warnings
  -h, --help     display this help and exit
  -V, --version  output version information and exit

1
Cài đặt Coreutils với brew install coreutilscung cấp shufnhị phân như gshuf.
Shadowtalker

2

Nếu bạn đã cài đặt Scala, đây là một lớp lót để xáo trộn đầu vào:

ls -1 | scala -e 'for (l <- util.Random.shuffle(io.Source.stdin.getLines.toList)) println(l)'

Rất đơn giản, nhưng trừ khi Java VM phải được khởi động bằng mọi giá, chi phí khởi động đó là đáng kể; cũng không hoạt động tốt với số lượng dòng lớn.
mkuity0

1

Hàm bash này có sự phụ thuộc tối thiểu (chỉ sắp xếp và bash):

shuf() {
while read -r x;do
    echo $RANDOM$'\x1f'$x
done | sort |
while IFS=$'\x1f' read -r x y;do
    echo $y
done
}

Giải pháp bash đẹp tương đương với awkgiải pháp được phân bổ riêng của OP , nhưng hiệu suất sẽ là một vấn đề với đầu vào lớn hơn; việc bạn sử dụng một $RANDOMgiá trị xáo trộn chính xác chỉ tối đa 32.768 dòng đầu vào; trong khi bạn có thể mở rộng phạm vi đó, có thể không đáng: ví dụ: trên máy của tôi, chạy tập lệnh của bạn trên 32.768 dòng đầu vào ngắn mất khoảng 1 giây, tức là khoảng 150 lần khi chạy shuf, và khoảng 10 - 15 lần miễn là awkgiải pháp được phân bổ riêng của OP . Nếu bạn có thể dựa vào sortsự hiện diện, awkcũng nên có mặt ở đó.
mkuity0

0

Trong cửa sổ Bạn có thể thử tệp bó này để giúp bạn xáo trộn data.txt của bạn, Việc sử dụng mã bó là

C:\> type list.txt | shuffle.bat > maclist_temp.txt

Sau khi ban hành lệnh này, maclist_temp.txt sẽ chứa danh sách các dòng ngẫu nhiên.

Hi vọng điêu nay co ich.


Không hoạt động đối với các tệp lớn. Tôi đã bỏ cuộc sau 2 giờ cho tập tin 1 triệu + dòng
Stefan Haberl

0

Chưa được đề cập đến:

  1. Việc sử dụng unsort. Cú pháp (hơi hướng danh sách phát):

    unsort [-hvrpncmMsz0l] [--help] [--version] [--random] [--heuristic]
           [--identity] [--filenames[=profile]] [--separator sep] [--concatenate] 
           [--merge] [--merge-random] [--seed integer] [--zero-terminated] [--null] 
           [--linefeed] [file ...]
  2. msort có thể xáo trộn theo dòng, nhưng nó thường quá mức:

    seq 10 | msort -jq -b -l -n 1 -c r

0

Một awkbiến thể khác :

#!/usr/bin/awk -f
# usage:
# awk -f randomize_lines.awk lines.txt
# usage after "chmod +x randomize_lines.awk":
# randomize_lines.awk lines.txt

BEGIN {
  FS = "\n";
  srand();
}

{
  lines[ rand()] = $0;
}

END {
  for( k in lines ){
    print lines[k];
  }
}
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.