Bagaimana saya bisa mengocok baris file teks pada baris perintah Unix atau dalam skrip shell?

285

Saya ingin mengocok garis file teks secara acak dan membuat file baru. File mungkin memiliki beberapa ribu baris.

Bagaimana saya bisa melakukannya dengan cat, awk, cut, dll?

Ruggiero Spearman
sumber
4
Gandakan stackoverflow.com/questions/886237/…
Dijeda hingga pemberitahuan lebih lanjut.
Yap, ada beberapa jawaban bagus lainnya dalam pertanyaan awal itu juga.
Ruggiero Spearman
jadi, apakah Anda membuat daftar kata wpa? (tebakan acak)
thahgr

Jawaban:

360

Anda bisa menggunakannya shuf. Paling tidak pada beberapa sistem (tampaknya tidak ada dalam POSIX).

Seperti yang ditunjukkan oleh jleedev: sort -Rmungkin juga menjadi pilihan. Setidaknya pada beberapa sistem; yah, Anda mendapatkan fotonya. Telah ditunjukkan bahwa sort -Rtidak benar-benar mengacak tetapi mengurutkan item sesuai dengan nilai hash mereka.

[Catatan Editor: sort -R hampir mengocok, kecuali bahwa garis duplikat / kunci sortir selalu berakhir bersebelahan . Dengan kata lain: hanya dengan jalur input / kunci unik yang benar-benar acak. Meskipun benar bahwa urutan output ditentukan oleh nilai hash , keacakan berasal dari memilih fungsi hash acak - lihat manual .]

Joey
sumber
31
shufdan sort -Rsedikit berbeda, karena sort -Rmemerintahkan elemen secara acak sesuai hash , yang sort -Rakan menyatukan elemen yang diulang, sementara shufmengocok semua elemen secara acak.
SeMeKh
146
Untuk pengguna OS X brew install coreutilsgshuf ...
:,
15
sort -Rdan shufharus dilihat sebagai sangat berbeda. sort -Rbersifat deterministik. Jika Anda memanggilnya dua kali pada waktu yang berbeda pada input yang sama, Anda akan mendapatkan jawaban yang sama. shuf, di sisi lain, menghasilkan output acak, sehingga kemungkinan besar akan memberikan output berbeda pada input yang sama.
EfForEffort
18
Itu tidak benar. "sort -R" menggunakan kunci hash acak yang berbeda setiap kali Anda memintanya, sehingga menghasilkan output yang berbeda setiap kali.
Mark Pettit
3
Catatan tentang keacakan: per dokumen GNU, "Secara default perintah ini menggunakan generator pseudo-acak internal yang diinisialisasi oleh sejumlah kecil entropi, tetapi dapat diarahkan untuk menggunakan sumber eksternal dengan opsi file --random-source = file."
Royce Williams
85

Perl one-liner akan menjadi versi sederhana dari solusi Maxim

perl -MList::Util=shuffle -e 'print shuffle(<STDIN>);' < myfile
Moonyoung Kang
sumber
6
Saya alias ini untuk mengocok OS X. Terima kasih!
The Unfun Cat
Ini adalah satu-satunya skrip pada halaman ini yang menghasilkan garis acak NYATA. Solusi awk lainnya sering kali mencetak hasil rangkap.
Felipe Alvarez
1
Tapi hati-hati karena di luar Anda akan kehilangan satu baris :) Itu hanya akan bergabung dengan baris lain :)
JavaRunner
@JavaRunner: Saya berasumsi Anda sedang berbicara tentang input tanpa trailing \n; ya, yang \nharus hadir - dan itu biasanya adalah - jika tidak Anda akan mendapatkan apa yang Anda gambarkan.
mklement0
1
Sangat singkat. Saya menyarankan untuk mengganti <STDIN>dengan <>, jadi solusinya bekerja dengan input dari file juga.
mklement0
60

Jawaban ini melengkapi banyak jawaban hebat yang ada dengan cara-cara berikut:

  • Jawaban yang ada dikemas ke dalam fungsi shell fleksibel :

    • Fungsi tidak hanya mengambil stdininput, tetapi juga argumen nama file
    • Fungsi mengambil langkah-langkah tambahan untuk menangani SIGPIPEdengan cara biasa (penghentian diam dengan kode keluar 141), sebagai lawan melanggar berisik. Hal ini penting ketika pipa output fungsi untuk pipa yang ditutup lebih awal, seperti ketika pipa ke head.
  • Sebuah perbandingan kinerja dibuat.


  • POSIX-compliant fungsi berdasarkan awk, sortdancut , diadaptasi dari jawaban OP sendiri :
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))
' "$@"; }

Lihat bagian bawah untuk versi Windows dari fungsi ini.

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

Perbandingan kinerja:

Catatan: Angka-angka ini diperoleh pada iMac akhir 2012 dengan 3,2 GHz Intel Core i5 dan Fusion Drive, menjalankan OSX 10.10.3. Sementara pengaturan waktu akan bervariasi dengan OS yang digunakan, spesifikasi mesin, awkimplementasi yang digunakan (misalnya, awkversi BSD yang digunakan pada OSX biasanya lebih lambat daripada GNU awkdan khususnya mawk), ini harus memberikan perasaan umum tentang kinerja relatif .

File input adalah file 1-juta-baris yang dihasilkan seq -f 'line %.0f' 1000000.
Waktu terdaftar dalam urutan menaik (tercepat pertama):

  • shuf
    • 0.090s
  • Ruby 2.0.0
    • 0.289s
  • Perl 5.18.2
    • 0.589s
  • Python
    • 1.342sdengan Python 2.7.6; 2.407s(!) dengan Python 3.4.2
  • awk+ sort+cut
    • 3.003sdengan BSD awk; 2.388sdengan GNU awk(4.1.1); 1.811sdengan mawk(1.3.4);

Untuk perbandingan lebih lanjut, solusi yang tidak dikemas sebagai fungsi di atas:

  • sort -R (bukan shuffle sejati jika ada jalur input duplikat)
    • 10.661s - mengalokasikan lebih banyak memori sepertinya tidak membuat perbedaan
  • Scala
    • 24.229s
  • bash loop + sort
    • 32.593s

Kesimpulan :

  • Gunakan shuf, jika Anda bisa - ini yang tercepat sejauh ini.
  • Ruby bekerja dengan baik, diikuti oleh Perl .
  • Python jauh lebih lambat daripada Ruby dan Perl, dan, membandingkan versi Python, 2.7.6 sedikit lebih cepat dari 3.4.1
  • Gunakan combo + awk+ yang sesuai dengan POSIX sebagai pilihan terakhirsortcut ; yang awkpelaksanaannya Anda menggunakan hal-hal ( mawklebih cepat dari GNU awk, BSD awkpaling lambat).
  • Tinggal jauh dari sort -R, bashloop, dan Scala.

Versi Windows dari solusi Python (kode Python identik, kecuali untuk variasi dalam kutipan dan penghapusan pernyataan terkait sinyal, yang tidak didukung pada Windows):

  • Untuk PowerShell (di Windows PowerShell, Anda harus menyesuaikan $OutputEncodingjika Anda ingin mengirim karakter non-ASCII melalui pipa):
# 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  
}

Perhatikan bahwa PowerShell dapat secara acak mengocok melalui Get-Randomcmdlet -nya (meskipun kinerja mungkin menjadi masalah); misalnya:
Get-Content someFile.txt | Get-Random -Count ([int]::MaxValue)

  • Untuk cmd.exe(file batch):

Simpan ke file shuf.cmd, misalnya:

@echo off
python -c "import sys, random, fileinput; lines=[line for line in fileinput.input()]; random.shuffle(lines); sys.stdout.write(''.join(lines))" %*
mklement0
sumber
SIGPIPE tidak ada di Windows, jadi saya menggunakan one-liner sederhana ini sebagai gantinya: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]));"
syarat
@elig: Terima kasih, tetapi menghilangkan from signal import signal, SIGPIPE, SIG_DFL; signal(SIGPIPE, SIG_DFL);dari solusi asli sudah cukup, dan mempertahankan fleksibilitas karena juga mampu melewati argumen nama file - tidak perlu mengubah apa pun (kecuali untuk mengutip) - silakan lihat bagian baru yang saya tambahkan di bawah.
mklement0
27

Saya menggunakan skrip perl kecil, yang saya sebut "unsort":

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

Saya juga punya versi terbatas-NULL, yang disebut "unsort0" ... berguna untuk digunakan dengan find -print0 dan seterusnya.

PS: Dipilih sebagai 'shuf' juga, saya tidak tahu bahwa ada di coreutils hari ini ... di atas mungkin masih berguna jika sistem Anda tidak memiliki 'shuf'.

NickZoic
sumber
bagus, RHEL 5.6 tidak memiliki shuf (
Maxim Egorushkin
1
Bagus sekali; Saya sarankan mengganti <STDIN>dengan <>untuk membuat pekerjaan solusi dengan masukan dari file juga.
mklement0
20

Berikut ini adalah percobaan pertama yang mudah pada koder tetapi sulit pada CPU yang menambahkan angka acak ke setiap baris, mengurutkan mereka dan kemudian menghapus nomor acak dari setiap baris. Akibatnya, garis diurutkan secara acak:

cat myfile | awk 'BEGIN{srand();}{print rand()"\t"$0}' | sort -k1 -n | cut -f2- > myfile.shuffled
Ruggiero Spearman
sumber
8
UUOC. lulus file untuk awk sendiri.
ghostdog74
1
Benar, saya debug dengan head myfile | awk .... Lalu saya hanya mengubahnya menjadi kucing; itu sebabnya dibiarkan di sana.
Ruggiero Spearman
Tidak perlu -k1 -ndisortir, karena output awk rand()adalah desimal antara 0 dan 1 dan karena yang penting adalah bahwa ia akan disusun ulang entah bagaimana. -k1mungkin membantu mempercepatnya dengan mengabaikan sisa baris, meskipun output rand () harus cukup unik untuk membuat hubungan pendek perbandingan.
bonsaiviking
@ ghostdog74: Kebanyakan penggunaan kucing yang tidak berguna sebenarnya berguna untuk konsisten antara perintah yang di-piped dan tidak. Lebih baik menyimpan cat filename |(atau < filename |) daripada mengingat bagaimana setiap program mengambil input file (atau tidak).
ShreevatsaR
2
shuf () {awk 'BEGIN {srand ()} {print rand () "\ t" $ 0}' "$ @" | sortir | cut -f2-;}
Meow
16

inilah skrip 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

keluaran

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

$ ./shell.sh
7
5
10
9
6
8
2
1
3
4
ghostdog74
sumber
Bagus sekali, tetapi dalam praktiknya jauh lebih lambat daripada jawaban OP sendiri , yang menggabungkan awkdengan sortdan cut. Untuk tidak lebih dari beberapa ribu baris itu tidak membuat banyak perbedaan, tetapi dengan jumlah garis yang lebih tinggi itu penting (ambang batas tergantung pada awkimplementasi yang digunakan). Penyederhanaan sedikit akan menggantikan while (1){dan if (e==d) {break}dengan garis while (e<d).
mklement0
11

Satu kalimat untuk python:

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

Dan untuk mencetak hanya satu baris acak:

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

Tetapi lihat posting ini untuk kelemahan python random.shuffle(). Itu tidak akan bekerja dengan baik dengan banyak (lebih dari 2080) elemen.

scai
sumber
2
"kekurangannya" tidak spesifik untuk Python. Hingga periode PRNG dapat diselesaikan dengan memasang kembali PRNG dengan entropi dari sistem seperti /dev/urandomhalnya. Untuk menggunakannya dari Python: random.SystemRandom().shuffle(L).
jfs
bukankah join () harus ada di '\ n' sehingga garis-garisnya dicetak masing-masing?
syarat
@elig: Tidak, karena .readLines()mengembalikan garis dengan baris baru tambahan.
mklement0
9

Fungsi berbasis awk sederhana akan melakukan pekerjaan:

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

pemakaian:

any_command | shuffle

Ini harus bekerja pada hampir semua UNIX. Diuji di Linux, Solaris dan HP-UX.

Memperbarui:

Perhatikan, bahwa nol di depan ( %06d) dan rand()multiplikasi membuatnya bekerja dengan baik juga pada sistem yang sorttidak memahami angka. Itu dapat diurutkan melalui urutan leksikografis (alias perbandingan string normal).

Michał Šrajer
sumber
Ide bagus untuk mengemas jawaban OP sendiri sebagai fungsi; jika Anda menambahkan "$@", itu juga akan berfungsi dengan file sebagai input. Tidak ada alasan untuk berkembang biak rand(), karena sort -nmampu mengurutkan pecahan desimal. Namun, ide yang baik untuk mengontrol awkformat output, karena dengan format default %.6g,, rand()akan menampilkan angka sesekali dalam notasi eksponensial . Sementara mengocok hingga 1 juta baris bisa dibilang cukup dalam praktiknya, mudah untuk mendukung lebih banyak lini tanpa membayar banyak penalti kinerja; mis %.17f.
mklement0
1
@ mklement0 Saya tidak melihat jawaban OPs saat menulis milik saya. rand () dikalikan dengan 10e6 untuk membuatnya bekerja dengan solaris atau hpux sorting sejauh yang saya ingat. Ide bagus dengan "$ @"
Michał Šrajer
1
Terima kasih, terima kasih; mungkin Anda bisa menambahkan alasan ini untuk perkalian dengan jawaban itu sendiri; umumnya, menurut POSIX, sortharus mampu menangani pecahan desimal (bahkan dengan ribuan pemisah, seperti yang baru saja saya perhatikan).
mklement0
7

Ruby FTW:

ls | ruby -e 'puts STDIN.readlines.shuffle'
hoffmanc
sumber
1
Hal-hal besar; Jika Anda menggunakan puts ARGF.readlines.shuffle, Anda bisa membuatnya bekerja dengan input stdin dan argumen nama file.
mklement0
Bahkan lebih pendek ruby -e 'puts $<.sort_by{rand}'- ARGF sudah merupakan enumerable, jadi kita dapat mengacak garis dengan mengurutkannya dengan nilai acak.
akuhn
6

Satu liner untuk Python berdasarkan jawaban scai , tetapi a) mengambil stdin, b) membuat hasil berulang dengan seed, c) memilih hanya 200 dari semua baris.

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

Cara yang sederhana dan intuitif akan digunakan shuf.

Contoh:

Asumsikan words.txtsebagai:

the
an
linux
ubuntu
life
good
breeze

Untuk mengocok garis, lakukan:

$ shuf words.txt

yang akan melempar garis yang dikocok ke output standar ; Jadi, Anda harus mem - pipe- nya ke file output seperti:

$ shuf words.txt > shuffled_words.txt

Satu menjalankan acak seperti itu dapat menghasilkan:

breeze
the
linux
an
ubuntu
good
life
kmario23
sumber
4

Kami memiliki paket untuk melakukan pekerjaan:

sudo apt-get install randomize-lines

Contoh:

Buat daftar angka yang diurutkan, dan simpan ke 1000.txt:

seq 1000 > 1000.txt

untuk mengocoknya, cukup gunakan

rl 1000.txt
navigaid
sumber
3

Ini adalah skrip python yang saya simpan sebagai rand.py di folder rumah saya:

#!/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()

Di Mac OSX sort -Rdan shuftidak tersedia sehingga Anda dapat alias ini di bash_profile Anda sebagai:

alias shuf='python rand.py'
Jeff Wu
sumber
3

Jika seperti saya Anda datang ke sini untuk mencari alternatif shufuntuk MacOS kemudian gunakan randomize-lines.

Instal randomize-lines(homebrew) paket, yang memiliki rlperintah yang memiliki fungsi serupa dengan 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
Ahmad Awais
sumber
1
Menginstal Coreutils dengan brew install coreutilsmenyediakan shufbiner sebagai gshuf.
shadowtalker
2

Jika Anda telah menginstal Scala, berikut ini adalah one-liner untuk mengacak input:

ls -1 | scala -e 'for (l <- util.Random.shuffle(io.Source.stdin.getLines.toList)) println(l)'
swartzrock
sumber
Sederhananya sederhana, tetapi kecuali Java VM harus dijalankan, biaya startup itu cukup besar; tidak berkinerja baik dengan jumlah garis besar baik.
mklement0
1

Fungsi bash ini memiliki ketergantungan minimal (hanya sort and bash):

shuf() {
while read -r x;do
    echo $RANDOM$'\x1f'$x
done | sort |
while IFS=$'\x1f' read -r x y;do
    echo $y
done
}
meong
sumber
Solusi bash bagus yang sejajar dengan solusi buatan OP sendiri awk, tetapi kinerja akan menjadi masalah dengan input yang lebih besar; penggunaan $RANDOMnilai tunggal Anda mengocok dengan benar hanya hingga 32.768 jalur input; sementara Anda dapat memperluas rentang itu, itu mungkin tidak sepadan: misalnya, di komputer saya, menjalankan skrip Anda di 32.768 jalur input pendek membutuhkan waktu sekitar 1 detik, yaitu sekitar 150 kali lebih lama dari shufwaktu berjalan , dan sekitar 10-15 kali selama awksolusi yang dibantu sendiri OP mengambil. Jika Anda bisa mengandalkan sortkehadiran, awkharus ada di sana juga.
mklement0
0

Di windows Anda dapat mencoba file batch ini untuk membantu Anda mengocok data.txt Anda, Penggunaan kode batch

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

Setelah mengeluarkan perintah ini, maclist_temp.txt akan berisi daftar garis acak.

Semoga ini membantu.

Ayfan
sumber
Tidak berfungsi untuk file besar. Saya menyerah setelah 2 jam untuk file + 1 juta baris
Stefan Haberl
0

Belum disebutkan:

  1. The unsortutil. Sintaks (berorientasi playlist):

    unsort [-hvrpncmMsz0l] [--help] [--version] [--random] [--heuristic]
           [--identity] [--filenames[=profile]] [--separator sep] [--concatenate] 
           [--merge] [--merge-random] [--seed integer] [--zero-terminated] [--null] 
           [--linefeed] [file ...]
  2. msort dapat mengocok per baris, tetapi biasanya berlebihan:

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

awkVarian lain :

#!/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];
  }
}
biziclop
sumber