Jak przetasować wiersze pliku tekstowego w wierszu poleceń systemu Unix lub w skrypcie powłoki?

285

Chcę losowo przetasować wiersze pliku tekstowego i utworzyć nowy plik. Plik może zawierać kilka tysięcy linii.

Jak mogę to zrobić z cat, awk, cutitp?

Ruggiero Spearman
źródło
4
Duplikat stackoverflow.com/questions/886237/...
Wstrzymany do odwołania.
Tak, w tym pierwotnym pytaniu jest jeszcze kilka fajnych odpowiedzi.
Ruggiero Spearman
więc tworzyłeś listę słów wpa? (tylko losowe przypuszczenie)
thahgr

Odpowiedzi:

360

Możesz użyć shuf. Przynajmniej w niektórych systemach (wydaje się, że nie jest w POSIX).

Jak zauważył Jleedev: sort -Rmoże być również opcją. Przynajmniej na niektórych systemach; Cóż, rozumiesz, o co chodzi. Wskazano, że sort -Rtak naprawdę nie tasuje, ale sortuje elementy według ich wartości skrótu.

[Nota redaktora: sort -R prawie tasuje, z tym że zduplikowane linie / klucze sortowania zawsze kończą się obok siebie . Innymi słowy: tylko przy unikalnych liniach / klawiszach wprowadzania jest to prawdziwy los. Chociaż prawdą jest, że kolejność wyjściowa zależy od wartości skrótu , losowość wynika z wyboru losowej funkcji skrótu - patrz instrukcja .]

Joey
źródło
31
shuf i sort -R różnią się nieznacznie, ponieważ sort -Rlosowo porządkuje elementy zgodnie z ich skrótem , to znaczy sort -Rłączy powtarzające się elementy razem, a shuflosowo tasuje wszystkie elementy.
SeMeKh
146
Dla użytkowników OS X brew install coreutilsgshuf ...
:,
15
sort -Ri shufpowinny być postrzegane jako zupełnie inne.sort -Rjest deterministyczny. Jeśli zadzwonisz dwukrotnie w różnych momentach na tym samym wejściu, otrzymasz tę samą odpowiedź. shuf, z drugiej strony, generuje losowy wynik, więc najprawdopodobniej da inny wynik na tym samym wejściu.
EfForEffort
18
To nie jest poprawne. „sort -R” używa innego losowego klucza skrótu za każdym razem, gdy go wywołujesz, więc za każdym razem generuje inny wynik.
Mark Pettit
3
Uwaga na temat losowości: według dokumentów GNU: „Domyślnie te polecenia używają wewnętrznego generatora pseudolosowego zainicjowanego niewielką ilością entropii, ale można go polecić, aby używał zewnętrznego źródła z opcją --random-source = plik”.
Royce Williams,
85

One-liner Perla byłby prostą wersją rozwiązania Maxima

perl -MList::Util=shuffle -e 'print shuffle(<STDIN>);' < myfile
Moonyoung Kang
źródło
6
Skalowałem to, aby przetasować w OS X. Dzięki!
Kot Unfun
To był jedyny skrypt na tej stronie, który zwrócił PRAWDZIWE losowe linie. Inne rozwiązania awk często drukują duplikaty.
Felipe Alvarez
1
Ale bądź ostrożny, ponieważ na wyjściu stracisz jedną linię :) Po prostu zostanie ona połączona z inną linią :)
JavaRunner
@JavaRunner: Zakładam, że mówisz o danych wejściowych bez końcowego \n; tak, to \nmusi być obecne - i zwykle tak jest - w przeciwnym razie dostaniesz to, co opisujesz.
mklement0
1
Cudownie zwięzłe. Proponuję wymianie <STDIN>z <>, więc rozwiązanie działa z wejściem z plików też.
mklement0
60

Ta odpowiedź uzupełnia wiele wspaniałych istniejących odpowiedzi na następujące sposoby:

  • Istniejące odpowiedzi są spakowane w elastyczne funkcje powłoki :

    • Funkcje pobierają nie tylko stdindane wejściowe, ale alternatywnie także argumenty nazwy pliku
    • Funkcje wykonują dodatkowe kroki SIGPIPEw zwykły sposób (ciche zakończenie z kodem wyjścia 141), a nie głośne łamanie. Jest to ważne, gdy przesyłane jest wyjście funkcji do potoku, który jest wcześniej zamknięty, na przykład podczas przesyłania do head.
  • Dokonano porównania wydajności .


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))
' "$@"; }

W dolnej części znajduje się wersja tej funkcji dla systemu Windows .

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

Porównanie wydajności:

Uwaga: Liczby te uzyskano na komputerze iMac z końca 2012 roku z procesorem Intel Core i5 3,2 GHz i dyskiem Fusion z systemem OSX 10.10.3. Chociaż czasy różnią się w zależności od używanego systemu operacyjnego, specyfikacji komputera i awkstosowanej implementacji (np. awkWersja BSD używana w OSX jest zwykle wolniejsza niż GNU, awka zwłaszcza mawk), powinno to zapewnić ogólne poczucie względnej wydajności .

Wejście plik jest plik 1 miliona linii produkowane seq -f 'line %.0f' 1000000.
Czasy są wymienione w kolejności rosnącej (najpierw najszybsze):

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

W celu dalszego porównania rozwiązania niepakowane jako funkcje powyżej:

  • sort -R (nie jest to prawdziwe losowanie, jeśli istnieją zduplikowane linie wejściowe)
    • 10.661s - przydzielanie większej ilości pamięci nie wydaje się mieć znaczenia
  • Scala
    • 24.229s
  • bash pętle + sort
    • 32.593s

Wnioski :

  • Użyj shuf, jeśli możesz - zdecydowanie najszybszy.
  • Ruby ma się dobrze, a za nią Perl .
  • Python jest zauważalnie wolniejszy niż Ruby i Perl, a porównując wersje Pythona, 2.7.6 jest nieco szybszy niż 3.4.1
  • Użyj zestawu awk+ sort+ zgodnego z POSIX- cutem w ostateczności ; której awkimplementacji używasz ma znaczenie ( mawkjest szybszy niż GNU awk, BSD awkjest najwolniejszy).
  • Trzymaj się z dala od sort -R, bashpętle i Scala.

Okna wersje Pythona roztworu (kod Python jest identyczne, z wyjątkiem zmian w cytowaniu i usuwania sprawozdania związane sygnału, które nie są obsługiwane w systemie Windows):

  • W przypadku programu PowerShell (w Windows PowerShell musisz dostosować, $OutputEncodingjeśli chcesz wysyłać znaki inne niż ASCII przez potok):
# 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  
}

Zauważ, że PowerShell może natywnie tasować za pomocą polecenia Get-Randomcmdlet (choć wydajność może być problemem); na przykład:
Get-Content someFile.txt | Get-Random -Count ([int]::MaxValue)

  • Dla cmd.exe(plik wsadowy):

Zapisz do pliku shuf.cmd, na przykład:

@echo off
python -c "import sys, random, fileinput; lines=[line for line in fileinput.input()]; random.shuffle(lines); sys.stdout.write(''.join(lines))" %*
mklement0
źródło
SIGPIPE nie istnieje w systemie Windows, więc zamiast tego użyłem tego prostego 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]));"
linijki
@elig: Dzięki, ale pominięcie from signal import signal, SIGPIPE, SIG_DFL; signal(SIGPIPE, SIG_DFL);oryginalnego rozwiązania jest wystarczające i zachowuje elastyczność umożliwiającą przekazywanie argumentów nazw plików - nie trzeba nic zmieniać (oprócz cytowania) - zapoznaj się z nową sekcją, którą dodałem na Dolny.
mklement0
27

Używam małego skryptu perla, który nazywam „nieposortowanym”:

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

Mam również wersję rozdzielaną przez NULL, o nazwie „unsort0” ... przydatną do użycia z find -print0 i tak dalej.

PS: Zagłosowałem też na „shuf”, nie miałem pojęcia, że ​​jest tam teraz w coreutils… powyższe może być nadal przydatne, jeśli twój system nie ma „shuf”.

NickZoic
źródło
fajny, RHEL 5.6 nie ma shufa (
Maxim Egorushkin
1
Ładnie wykonane; Proponuję zastąpienie <STDIN>ze <>w celu dokonania prac rozwiązanie z wejściem z plików też.
mklement0
20

Oto pierwsza próba, która jest łatwa dla kodera, ale trudna dla procesora, który przygotowuje losową liczbę do każdej linii, sortuje je, a następnie usuwa losową liczbę z każdej linii. W efekcie linie są sortowane losowo:

cat myfile | awk 'BEGIN{srand();}{print rand()"\t"$0}' | sort -k1 -n | cut -f2- > myfile.shuffled
Ruggiero Spearman
źródło
8
UUOC. przekazać plik do samego awk.
ghostdog74
1
Racja, debuguję z head myfile | awk .... Potem zmieniam go na kota; dlatego tam zostało.
Ruggiero Spearman
Nie trzeba -k1 -nsortować, ponieważ dane wyjściowe awk rand()są dziesiętne od 0 do 1, a wszystko, co się liczy, to że w jakiś sposób zostanie zmieniony porządek. -k1może to przyspieszyć, ignorując resztę wiersza, chociaż dane wyjściowe rand () powinny być na tyle unikalne, aby zewrzeć porównanie.
bonsaiviking
@ ghostdog74: Większość tak zwanych bezużytecznych zastosowań cat jest w rzeczywistości przydatna do zachowania spójności między poleceniami potokowymi, a nie. Lepiej zachować cat filename |(lub < filename |) niż pamiętać, jak każdy program pobiera dane wejściowe (lub nie).
ShreevatsaR
2
shuf () {awk 'BEGIN {srand ()} {print rand () "\ t" $ 0}' "$ @" | sortuj | cut -f2-;}
Meow
16

oto skrypt 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

wynik

$ 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
źródło
Ładnie wykonane, ale w praktyce znacznie wolniejsze niż własna odpowiedź PO , która łączy się awkz sorti cut. Dla nie więcej niż kilku tysięcy linii nie ma to większego znaczenia, ale przy większej liczbie linii ma to znaczenie (próg zależy od zastosowanej awkimplementacji). Nieznaczne uproszczeniem byłoby zastąpienie linii while (1){i if (e==d) {break}z while (e<d).
mklement0
11

Jednowierszowy dla Pythona:

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

A do drukowania tylko jednej losowej linii:

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

Ale zobacz ten post, aby poznać wady Pythona random.shuffle(). Nie działa dobrze z wieloma (więcej niż 2080) elementami.

scai
źródło
2
„wada” nie jest specyficzna dla Pythona. Skończone okresy PRNG można obejść przez ponowne wysunięcie PRNG z entropią z systemu, podobnie jak /dev/urandomrobi. Aby wykorzystać ją od Python: random.SystemRandom().shuffle(L).
jfs
czy funkcja join () nie musi znajdować się na „\ n”, więc linie drukowane są osobno?
elig
@elig: Nie, ponieważ .readLines()zwraca linie z końcowym znakiem nowej linii.
mklement0
9

Prosta funkcja oparta na awk wykona zadanie:

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

stosowanie:

any_command | shuffle

Powinno to działać na prawie każdym systemie UNIX. Testowane na systemach Linux, Solaris i HP-UX.

Aktualizacja:

Zauważ, że wiodące zera ( %06d) i rand()mnożenie sprawiają, że działa poprawnie również w systemach, w których sortnie rozumie liczb. Można go sortować według porządku leksykograficznego (inaczej zwykłe porównywanie ciągów).

Michał Šrajer
źródło
Dobry pomysł, aby spakować własną odpowiedź PO jako funkcję; jeśli dodasz "$@", będzie również działać z plikami jako danymi wejściowymi. Nie ma powodu do mnożenia rand(), ponieważ sort -njest w stanie sortować ułamki dziesiętne. Jest to jednak dobry pomysł do sterowania awkformatem wyjściowym jest, bo z domyślnego formatu, %.6g, rand()wyjściem Czy liczba okazjonalne w wykładniczej notacji. Podczas gdy przetasowanie do 1 miliona linii jest wystarczające w praktyce, łatwo jest obsłużyć więcej linii bez płacenia znacznej kary za wydajność; np %.17f.
mklement0
1
@ mklement0 Nie zauważyłem odpowiedzi OP podczas pisania mojej. rand () jest mnożony przez 10e6, aby działał z sortowaniem solaris lub hpux, o ile pamiętam. Dobry pomysł z „$ @”
Michał Šrajer
1
Mam to, dzieki; być może mógłbyś dodać to uzasadnienie pomnożenia do samej odpowiedzi; ogólnie, zgodnie z POSIX, sortpowinien być w stanie obsługiwać ułamki dziesiętne (nawet z tysiącami separatorów, jak właśnie zauważyłem).
mklement0
7

Ruby FTW:

ls | ruby -e 'puts STDIN.readlines.shuffle'
hoffmanc
źródło
1
Świetne rzeczy; Jeśli używasz puts ARGF.readlines.shuffle, możesz sprawić, by działał zarówno z argumentami wejścia standardowego, jak i nazwą pliku.
mklement0
Jeszcze krótszy ruby -e 'puts $<.sort_by{rand}'- ARGF jest już wyliczalny, więc możemy przetasować linie, sortując je według losowych wartości.
akuhn
6

Jedna linijka dla Pythona oparta na odpowiedzi Scai , ale a) przyjmuje standardowe wejście , b) sprawia, że ​​wynik jest powtarzalny z ziarnem, c) wybiera tylko 200 wszystkich linii.

$ cat file | python -c "import random, sys; 
  random.seed(100); print ''.join(random.sample(sys.stdin.readlines(), 200))," \
  > 200lines.txt
dfrankow
źródło
6

Prostym i intuicyjnym sposobem byłoby użycie shuf.

Przykład:

Załóż words.txtjako:

the
an
linux
ubuntu
life
good
breeze

Aby przetasować linie, wykonaj:

$ shuf words.txt

co wyrzuca tasowane linie na standardowe wyjście ; Więc musisz go potokować do pliku wyjściowego, takiego jak:

$ shuf words.txt > shuffled_words.txt

Jeden taki losowy przebieg może dać:

breeze
the
linux
an
ubuntu
good
life
kmario23
źródło
4

Mamy pakiet do wykonania tej samej pracy:

sudo apt-get install randomize-lines

Przykład:

Utwórz uporządkowaną listę liczb i zapisz ją w 1000.txt:

seq 1000 > 1000.txt

aby go przetasować, po prostu użyj

rl 1000.txt
navigaid
źródło
3

To jest skrypt Pythona, który zapisałem jako rand.py w moim katalogu domowym:

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

W systemie Mac OSX sort -Ri shufnie są one dostępne, więc możesz użyć aliasu w pliku bash_profile jako:

alias shuf='python rand.py'
Jeff Wu
źródło
3

Jeśli tak jak ja, przyszedłeś tu poszukać alternatywy shufdla MacOS, a następnie użyjrandomize-lines .

Zainstaluj randomize-linespakiet (homebrew), który ma rlpolecenie o podobnej funkcjonalności 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
źródło
1
Instalowanie Coreutils z brew install coreutilszapewnia shufplik binarny jako gshuf.
shadowtalker
2

Jeśli masz zainstalowaną Scalę, oto linijka do przetasowania danych wejściowych:

ls -1 | scala -e 'for (l <- util.Random.shuffle(io.Source.stdin.getLines.toList)) println(l)'
swartzrock
źródło
Kusząco proste, ale o ile i tak nie trzeba uruchamiać maszyny wirtualnej Java, koszt uruchomienia jest znaczny; nie działa też dobrze przy dużych liczbach linii.
mklement0
1

Ta funkcja bash ma minimalną zależność (tylko sortowanie i bash):

shuf() {
while read -r x;do
    echo $RANDOM$'\x1f'$x
done | sort |
while IFS=$'\x1f' read -r x y;do
    echo $y
done
}
Miauczeć
źródło
Ładne rozwiązanie bash, które przypomina równoległe rozwiązanie OP awk, ale wydajność będzie stanowić problem przy większym nakładzie; użycie jednej $RANDOMwartości poprawnie tasuje tylko do 32 768 linii wejściowych; chociaż możesz rozszerzyć ten zakres, prawdopodobnie nie warto: na przykład na moim komputerze uruchomienie skryptu na 32768 krótkich wierszach wejściowych zajmuje około 1 sekundy, czyli około 150 razy dłużej niż uruchomienie shufi około 10-15 razy tak długo, jak awkpotrzeba własnego rozwiązania PO . Jeśli możesz polegać na sortbyciu obecnym, awkpowinieneś tam również być.
mklement0
0

W systemie Windows możesz wypróbować ten plik wsadowy, aby pomóc w tasowaniu danych.txt, użycie kodu wsadowego jest

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

Po wydaniu tego polecenia plik maclist_temp.txt będzie zawierał losową listę wierszy.

Mam nadzieję że to pomoże.

Ayfan
źródło
Nie działa w przypadku dużych plików. Po 2 godzinach zrezygnowałem z pliku o wartości 1 miliona linii
Stefan Haberl
0

Dotychczas nie wspomniano:

  1. unsortUtil. Składnia (nieco zorientowana na listy odtwarzania):

    unsort [-hvrpncmMsz0l] [--help] [--version] [--random] [--heuristic]
           [--identity] [--filenames[=profile]] [--separator sep] [--concatenate] 
           [--merge] [--merge-random] [--seed integer] [--zero-terminated] [--null] 
           [--linefeed] [file ...]
  2. msort może tasować po linii, ale zwykle jest to przesada:

    seq 10 | msort -jq -b -l -n 1 -c r
agc
źródło
0

Kolejny awkwariant:

#!/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
źródło