Как я могу перетасовать строки текстового файла в командной строке Unix или в сценарии оболочки? - PullRequest
264 голосов
/ 28 января 2010

Я хочу произвольно перемешать строки текстового файла и создать новый файл. Файл может содержать несколько тысяч строк.

Как я могу сделать это с cat, awk, cut и т. Д.?

Ответы [ 19 ]

335 голосов
/ 28 января 2010

Вы можете использовать shuf. По крайней мере, в некоторых системах (похоже, не в POSIX).

Как указал jleedev: sort -R также может быть вариантом. По крайней мере, в некоторых системах; ну, вы поняли. Было отмечено , что sort -R на самом деле не тасует, а сортирует элементы по их хэш-значению.

[Примечание редактора: sort -R почти перемешивает, за исключением того, что дубликаты строк / ключей сортировки всегда заканчиваются рядом друг с другом . Другими словами: только с уникальными входными строками / клавишами это настоящая случайность. Хотя порядок вывода определяется значениями хеша , случайность возникает при выборе случайного хеша функции - см. manual .]

80 голосов
/ 28 июня 2011

Однострочный Perl будет простой версией решения Максима

perl -MList::Util=shuffle -e 'print shuffle(<STDIN>);' < myfile
56 голосов
/ 09 мая 2015

Этот ответ дополняет многие великие существующие ответы следующими способами:

  • Существующие ответы упакованы в гибкие функции оболочки :

    • Функции принимают не только stdin ввод, но в качестве альтернативы также имя файла аргументы
    • Функции предпринимают дополнительные шаги для обработки SIGPIPE обычным способом (тихое завершение с кодом выхода 141), в отличие от шумного взлома. Это важно при передаче вывода функции в трубу, которая рано закрывается, например, при передаче в head.
  • A сравнение производительности сделано.


  • POSIX-совместимая функция на основе awk, sort и cut, адаптированная из собственного ответа OP :
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))
' "$@"; }
shuf() { ruby -e 'Signal.trap("SIGPIPE", "SYSTEM_DEFAULT");
                     puts ARGF.readlines.shuffle' "$@"; }

Сравнение производительности:

Примечание. Эти цифры были получены на iMac, выпущенном в конце 2012 года, с процессором Intel Core i5 с тактовой частотой 3,2 ГГц и диском Fusion, работающим под управлением OSX 10.10.3. Хотя время будет зависеть от используемой ОС, спецификации машины, awk используемая реализация (например, версия BSD awk, используемая в OSX, обычно медленнее, чем GNU awk и особенно mawk), это должно дать общее представление о относительной производительности .

Входной файл представляет собой файл 1 миллион строк , созданный с seq -f 'line %.0f' 1000000.
Время указано в порядке возрастания (сначала самое быстрое):

  • shuf
    • 0.090s
  • Рубин 2.0.0
    • 0.289s
  • Perl 5.18.2
    • 0.589s
  • Python
    • 1.342s с Python 2.7.6; 2.407s (!) С Python 3.4.2
  • awk + sort + cut
    • 3.003s с BSD awk; 2.388s с GNU awk (4.1.1); 1.811s с mawk (1.3.4);

Для дальнейшего сравнения решения, не упакованные как функции выше:

  • sort -R (не является случайным образом, если есть повторяющиеся строки ввода)
    • 10.661s - выделение большего объема памяти, кажется, не имеет значения
  • Скала
    • 24.229s
  • bash петли + sort
    • 32.593s

Выводы

  • Используйте shuf, если можете - это самый быстрый на сегодняшний день.
  • Ruby преуспевает, затем Perl .
  • Python заметно медленнее, чем Ruby и Perl, и, сравнивая версии Python, 2.7.6 немного быстрее, чем 3.4.1
  • Используйте POSIX-совместимый awk + sort + cut в качестве последнего средства ; какая awk реализация, которую вы используете, имеет значение (mawk быстрее, чем GNU awk, BSD awk медленнее).
  • Держитесь подальше от sort -R, bash петель и Scala.
27 голосов
/ 28 января 2010

Я использую крошечный Perl-скрипт, который я называю «unsort»:

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

У меня также есть версия с разделением NULL, которая называется "unsort0" ... удобная для использования с find -print0 и т. Д.

PS: проголосовал за 'shuf', я понятия не имел, что было в coreutils в эти дни ... выше может быть полезно, если в ваших системах нет 'shuf'.

19 голосов
/ 28 января 2010

Вот первая попытка, которая проста для кодировщика, но трудна для ЦП, которая добавляет случайное число к каждой строке, сортирует их и затем удаляет случайное число из каждой строки.По сути, строки сортируются случайным образом:

cat myfile | awk 'BEGIN{srand();}{print rand()"\t"$0}' | sort -k1 -n | cut -f2- > myfile.shuffled
16 голосов
/ 28 января 2010

вот скрипт 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

выход

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

$ ./shell.sh
7
5
10
9
6
8
2
1
3
4
11 голосов
/ 11 июля 2013

Однострочник для Python:

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

А для печати всего одна случайная строка:

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

Но посмотрите этот пост о недостатках Python's random.shuffle(). Он не будет хорошо работать со многими (более 2080) элементами.

9 голосов
/ 12 октября 2011

Простая функция на основе awk выполнит свою работу:

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

Использование:

any_command | shuffle

Это должно работать практически на любой UNIX.Протестировано на Linux, Solaris и HP-UX.

Обновление:

Обратите внимание, что ведущие нули (%06d) и умножение rand() позволяют работать должным образомтакже в системах, где sort не понимает числа.Его можно отсортировать по лексикографическому порядку (также как обычное сравнение строк).

7 голосов
/ 16 декабря 2014

Ruby FTW:

ls | ruby -e 'puts STDIN.readlines.shuffle'
6 голосов
/ 22 июля 2013

Один вкладыш для Python, основанный на ответе scai , но a) принимает stdin, b) делает результат повторяемым с seed, c) выбирает только 200 всех строк.

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