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

285

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

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

Руджеро Спирман
источник
4
Дубликат stackoverflow.com/questions/886237/…
Приостановлен до дальнейшего уведомления.
Да, есть и другие хорошие ответы на этот оригинальный вопрос.
Руджеро Спирман
Итак, вы делали список слов wpa? (просто случайное предположение)
thahgr

Ответы:

361

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

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

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

детеныш
источник
31
shufи sort -Rнемного отличаются, потому что sort -Rслучайным образом упорядочивает элементы в соответствии с их хешем , то есть sort -Rобъединяет повторяющиеся элементы, в то время как shufвсе элементы перемешиваются случайным образом.
СЕМЕХ
146
Для пользователей OS X:, brew install coreutilsзатем используйте gshuf ...(:
ELLIOTTCABLE
15
sort -Rи shufдолжен рассматриваться как совершенно другой. sort -Rявляется детерминированным. Если вы вызываете его дважды в разное время на одном и том же входе, вы получите один и тот же ответ. shufс другой стороны, производит случайный вывод, так что он, скорее всего, будет давать разные выходные данные на одном и том же входе.
EfForEffort
18
Это не правильно. «sort -R» использует разные случайные хэш-ключи каждый раз, когда вы вызываете его, поэтому каждый раз выдает разные выходные данные.
Марк Петтит
3
Примечание о случайности: согласно документации GNU: «По умолчанию эти команды используют внутренний псевдослучайный генератор, инициализированный небольшой энтропией, но могут быть направлены на использование внешнего источника с параметром --random-source = file».
Ройс Уильямс
86

Perl one-liner будет простой версией решения Максима

perl -MList::Util=shuffle -e 'print shuffle(<STDIN>);' < myfile
Moonyoung Kang
источник
6
Я связал это, чтобы перемешать на OS X. Спасибо!
Несчастный кот
Это был единственный скрипт на этой странице, который возвращал РЕАЛЬНЫЕ случайные строки. Другие решения awk часто выводят дубликаты.
Фелипе Альварес
1
Но будьте осторожны, потому что на выходе вы потеряете одну строку :) Она просто будет соединена с другой строкой :)
JavaRunner
@JavaRunner: я полагаю, вы говорите о вводе без трейлинга \n; да, это \nдолжно присутствовать - и это , как правило , это - в противном случае вы получите то , что вы описываете.
mklement0
1
Чудесно лаконично. Я предлагаю заменить <STDIN>на <>, так что решение работает и с вводом из файлов .
mklement0
60

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

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

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


shuf() { awk 'BEGIN {srand(); OFMT="%.17f"} {print rand(), $0}' "$@" |
               sort -k1,1n | cut -d ' ' -f2-; }
shuf() { perl -MList::Util=shuffle -e 'print shuffle(<>);' "$@"; }
  • Python- основанная функция, адаптированная из ответа scai :
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))
' "$@"; }

См. Нижний раздел для версии Windows для этой функции.

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используемой реализации (например, awkверсия BSD, используемая в OSX, обычно медленнее, чем GNU awkи особенно mawk), это должно дать общее представление об относительной производительности .

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

  • shuf
    • 0.090s
  • Ruby 2.0.0
    • 0.289s
  • Perl 5.18.2
    • 0.589s
  • питон
    • 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 - выделение большего количества памяти, кажется, не имеет значения
  • Scala
    • 24.229s
  • bash петли + sort
    • 32.593s

Выводы :

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

Версии решения Python для Windows (код Python идентичен, за исключением различий в кавычках и удалении операторов, связанных с сигналами, которые не поддерживаются в Windows):

  • Для PowerShell (в Windows PowerShell вам придется настроить, $OutputEncodingесли вы хотите отправлять не-ASCII символы через конвейер):
# 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  
}

Обратите внимание, что PowerShell может напрямую перетасовываться с помощью своего Get-Randomкомандлета (хотя производительность может быть проблемой); например:
Get-Content someFile.txt | Get-Random -Count ([int]::MaxValue)

  • Для cmd.exe(пакетный файл):

Сохранить в файл shuf.cmd, например:

@echo off
python -c "import sys, random, fileinput; lines=[line for line in fileinput.input()]; random.shuffle(lines); sys.stdout.write(''.join(lines))" %*
mklement0
источник
SIGPIPE не существует в Windows, поэтому я использовал этот простой однострочный 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: Спасибо, но достаточно опустить from signal import signal, SIGPIPE, SIG_DFL; signal(SIGPIPE, SIG_DFL);исходное решение и сохранить гибкость, позволяя также передавать аргументы имени файла - не нужно ничего менять (кроме цитирования) - смотрите новый раздел, который я добавил на низ.
mklement0
27

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

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

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

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

NickZoic
источник
хороший, RHEL 5.6 не имеет шуфа (
Максим Егорушкин,
1
Красиво сделано; Я предлагаю заменить <STDIN>с <>тем чтобы сделать работу решения с вводом из файлов тоже.
mklement0
20

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

cat myfile | awk 'BEGIN{srand();}{print rand()"\t"$0}' | sort -k1 -n | cut -f2- > myfile.shuffled
Руджеро Спирман
источник
8
UUOC. передать файл в awk сам.
ghostdog74
1
Хорошо, я отлаживаю с head myfile | awk .... Тогда я просто изменяю это на кошку; вот почему он был оставлен там.
Руджеро Спирман
Не нужно -k1 -nдля сортировки, поскольку выходные данные awk rand()- это десятичные числа от 0 до 1, и потому, что все, что имеет значение, это то, что они каким-то образом переупорядочиваются. -k1может помочь ускорить его, игнорируя оставшуюся часть строки, хотя вывод rand () должен быть достаточно уникальным, чтобы замкнуть сравнение.
Бонсайвинг
@ ghostdog74: Большинство так называемых бесполезных применений cat на самом деле полезно для согласованности между переданными командами командами, а не для них. Лучше сохранить cat filename |(или < filename |), чем помнить, как каждая отдельная программа принимает входной файл (или нет).
ShreevatsaR
2
shuf () {awk 'BEGIN {srand ()} {print rand () "\ t" $ 0}' "$ @" | сортировать | cut -f2-;}
Мяу
16

вот сценарий 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
ghostdog74
источник
Хорошо сделано, но на практике гораздо медленнее, чем собственный ответ ОП , который сочетается awkс sortи cut. Для не более нескольких тысяч строк это не имеет большого значения, но при большем количестве строк это имеет значение (порог зависит от используемой awkреализации). Небольшое упрощение было бы заменить строки while (1){и if (e==d) {break}с while (e<d).
mklement0
11

Однострочник для 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 random.shuffle(). Это не будет хорошо работать со многими (более 2080) элементами.

SCAI
источник
2
«Недостаток» не является специфическим для Python. Конечные периоды PRNG можно обойти путем повторного заполнения PRNG энтропией из системы, как это /dev/urandomделается. Для того, чтобы использовать его в Python: random.SystemRandom().shuffle(L).
JFS
разве join () не должен быть в '\ n', чтобы строки печатались по-своему?
elig
@elig: Нет, потому что .readLines()возвращает строки с завершающим переводом строки.
mklement0
9

Простая функция на основе 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не понимают числа. Это может быть отсортировано по лексикографическому порядку (иначе как сравнение строк).

Михал Шрайер
источник
Хорошая идея - упаковать собственный ответ ОП как функцию; если вы добавите "$@", он также будет работать с файлами в качестве входных данных. Нет смысла умножать rand(), потому что sort -nспособен сортировать десятичные дроби. Это, однако, хорошая идея управления awkформатом вывода «s, потому что с форматом по умолчанию, в %.6g, rand()будет выводить случайные числа в экспоненциальной нотации. Хотя на практике, возможно, достаточно перетасовать до 1 миллиона строк, легко поддерживать большее количество строк без значительных потерь производительности; например %.17f.
mklement0
1
@ mklement0 Я не заметил ответ ОП при написании своего. Насколько я помню, rand () умножается на 10e6, чтобы он работал с солярисом или hpux. Хорошая идея с "$ @"
Михал Шрайер
1
Понял, спасибо; возможно, вы могли бы добавить это обоснование умножения к самому ответу; обычно, согласно POSIX, он sortдолжен обрабатывать десятичные дроби (даже с тысячами разделителей, как я только что заметил).
mklement0
7

Ruby FTW:

ls | ruby -e 'puts STDIN.readlines.shuffle'
hoffmanc
источник
1
Отличный материал; Если вы используете puts ARGF.readlines.shuffle, вы можете заставить его работать как с вводом stdin, так и с аргументами имени файла.
mklement0
Еще короче ruby -e 'puts $<.sort_by{rand}'- ARGF уже перечислим, поэтому мы можем перетасовать строки, сортируя их по случайным значениям.
Акюн
6

Один лайнер для 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
dfrankow
источник
6

Простой и интуитивно понятный способ будет использовать shuf.

Пример:

Предположим words.txtкак:

the
an
linux
ubuntu
life
good
breeze

Чтобы перемешать строки, выполните:

$ shuf words.txt

который бросил бы перемешанные строки к стандартному выводу ; Итак, вы должны передать его в выходной файл, например:

$ shuf words.txt > shuffled_words.txt

Один такой случайный ход может дать:

breeze
the
linux
an
ubuntu
good
life
kmario23
источник
4

У нас есть пакет, чтобы сделать саму работу:

sudo apt-get install randomize-lines

Пример:

Создайте упорядоченный список номеров и сохраните его в 1000.txt:

seq 1000 > 1000.txt

чтобы перемешать, просто используйте

rl 1000.txt
navigaid
источник
3

Это скрипт на python, который я сохранил как rand.py в моей домашней папке:

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

На Mac OSX sort -Rи shufне доступны, поэтому вы можете использовать псевдоним этого в вашем bash_profile как:

alias shuf='python rand.py'
Джефф Ву
источник
3

Если, как и я, вы пришли сюда, чтобы найти альтернативу shufдля macOS, используйте randomize-lines.

Установить randomize-lines(homebrew) пакет, в котором есть rlкоманда, аналогичная функциональности 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
Установка Coreutils с помощью brew install coreutilsобеспечивает shufбинарный как gshuf.
Shadowtalker
2

Если у вас установлен Scala, вот одна строка для перетасовки ввода:

ls -1 | scala -e 'for (l <- util.Random.shuffle(io.Source.stdin.getLines.toList)) println(l)'
swartzrock
источник
Заманчиво просто, но если Java VM не должен быть запущен в любом случае, эта стоимость запуска является значительной; плохо работает с большим количеством строк.
mklement0
1

Эта функция bash имеет минимальную зависимость (только sort и bash):

shuf() {
while read -r x;do
    echo $RANDOM$'\x1f'$x
done | sort |
while IFS=$'\x1f' read -r x y;do
    echo $y
done
}
мяу
источник
Хорошее решение bash, которое аналогично собственному awkрешению OP, но производительность будет проблемой при большем вводе; использование вами одного $RANDOMзначения корректно перетасовывает только до 32 768 строк ввода; хотя вы могли бы расширить этот диапазон, это, вероятно, того не стоит: например, на моем компьютере выполнение сценария на 32 768 коротких строках ввода занимает около 1 секунды, что примерно в 150 раз больше времени выполнения shuf, и примерно в 10–15 раз. столько, сколько awkтребуется собственному решению ОП. Если вы можете положиться на sortприсутствие, awkдолжно быть там.
mklement0
0

В Windows Вы можете попробовать этот пакетный файл, чтобы помочь вам перетасовать ваш data.txt. Использование пакетного кода

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

После выполнения этой команды maclist_temp.txt будет содержать рандомизированный список строк.

Надеюсь это поможет.

Ayfan
источник
Не работает для больших файлов. Я сдался через 2 часа из-за файла с миллионом строк
Стефан Хаберл
0

Пока не упоминается:

  1. unsortUtil. Синтаксис (несколько ориентированный на плейлист):

    unsort [-hvrpncmMsz0l] [--help] [--version] [--random] [--heuristic]
           [--identity] [--filenames[=profile]] [--separator sep] [--concatenate] 
           [--merge] [--merge-random] [--seed integer] [--zero-terminated] [--null] 
           [--linefeed] [file ...]
  2. msort может перетасовать строку, но обычно это перебор:

    seq 10 | msort -jq -b -l -n 1 -c r
АРУ
источник
0

Другой awkвариант:

#!/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
источник