Як я можу переміщувати рядки текстового файлу в командному рядку Unix або в сценарії оболонки?


285

Хочу випадково перетасувати рядки текстового файлу та створити новий файл. Файл може мати кілька тисяч рядків.

Як я можу це зробити з cat, awk, cutі т.д.?



Так, є і інші приємні відповіді і в цьому оригінальному запитанні.
Ruggiero Spearman

Отже, ви складали список слів WPA? (лише випадкова здогадка)
thahgr

Відповіді:


360

Можна використовувати shuf. Принаймні, у деяких системах (схоже, це не в POSIX).

Як зазначав jleedev: sort -Rце також може бути варіантом. Принаймні, на деяких системах; ну ви отримаєте картину. Було зазначено, що sort -Rнасправді не перетасовувати, а замість цього сортувати елементи відповідно до їх хеш-цінності.

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


31
shufі sort -Rнезначно відрізняються, тому що sort -Rвипадковим чином впорядковує елементи відповідно до хешу з них, тобто sort -Rз’єднує повторювані елементи разом, а shufпереміщує всі елементи випадковим чином.
SeMeKh

146
Для користувачів ОС X:, brew install coreutilsтоді використовуйте gshuf ...(:
ELLIOTTCABLE

15
sort -Rі їх shufслід розглядати як зовсім інші. sort -Rє детермінованим. Якщо ви зателефонуєте двічі за різний час на одному вході, ви отримаєте ту саму відповідь. shufз іншого боку, виробляє рандомізований вихід, тому він, швидше за все, дасть різний вихід на одному вході.
EfForEffort

18
Це не правильно. "Сортувати -R" використовує інший випадковий хеш-ключ щоразу, коли ви викликаєте його, тому він щоразу створює різний вихід.
Марк Петтіт

3
Зауважте про випадковість: за документами GNU "За замовчуванням ці команди використовують внутрішній псевдовипадковий генератор, ініціалізований невеликою кількістю ентропії, але може бути спрямований на використання зовнішнього джерела з опцією --random-source = файл".
Royce Williams

85

Perl one-liner - це проста версія рішення Максима

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

6
Я провів це переміщення на ОС X. Дякую!
The Unfun Cat

Це єдиний скрипт на цій сторінці, який повертав РЕАЛЬНІ випадкові рядки. Інші рішення awk часто друкують дублюючий вихід.
Феліпе Альварес

1
Але будьте обережні, бо ви втратите один рядок :) Він просто
з'єднається

@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-; }
  • Функція на основі Perl - адаптована з відповіді Мунінгунга :
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))
' "$@"; }

Дивіться нижній розділ для Windows- версії цієї функції.

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

Порівняння продуктивності:

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

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

  • shuf
    • 0.090s
  • Рубін 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 - виділення більше пам'яті, схоже, не має значення
  • Скала
    • 24.229s
  • bash петлі + sort
    • 32.593s

Висновки :

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

Версії Windows для рішення Python (код 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))" %*

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, який я називаю "несоромним":

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

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

PS: Я також проголосував за "шуф", я не мав уявлення про те, що це було в coreutils в наші дні ... вищезгадане все ще може бути корисним, якщо у ваших системах немає "shuf".


приємний, 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. передайте файл, щоб розбудити себе.
ghostdog74

1
Правильно, я налагоджую head myfile | awk .... Тоді я просто міняю його на кота; тому його і залишили там.
Ruggiero Spearman

Не потрібно -k1 -nсортувати, оскільки вихід awk's rand()- це десятковий від 0 до 1 і тому, що все важливо, це те, що він як-небудь упорядковується. -k1може допомогти прискорити його, ігноруючи решту рядка, хоча вихід rand () повинен бути достатньо унікальним для короткого замикання порівняння.
бонсайвінг

@ ghostdog74: Більшість так званих марних застосувань котів фактично корисні для того, щоб бути послідовними між трубопровідними командами, а не. Краще зберегти cat filename |(або < filename |), ніж пам'ятати, як кожна окрема програма приймає введення файлів (чи ні).
ShreevatsaR

2
shuf () {awk 'ПОЧАК {srand ()} {print rand () "\ t" $ 0}' "$ @" | сортувати | скоротити -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

Чудово зроблено, але на практиці набагато повільніше, ніж власна відповідь ОП , яка поєднується awkз sortі cut. Для не більше кількох тисяч рядків це не має великої різниці, але при більшій кількості ліній це має значення (поріг залежить від використовуваної awkреалізації). Невеликим спрощенням було б замінити лінії while (1){та if (e==d) {break}с while (e<d).
mklement0

11

Однолінійний пітон:

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

Але дивіться цю публікацію щодо недоліків пітона random.shuffle(). Він не буде добре працювати з багатьма (більше 2080) елементами.


2
"недолік" не характерний для Python. Кінцеві періоди PRNG можуть бути усунені шляхом повторного перезавантаження PRNG на ентропію від системи, як /dev/urandomце робиться. Для того, щоб використовувати його в Python: random.SystemRandom().shuffle(L).
jfs

не потрібно, щоб приєднання () було ввімкнено '\ 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вихідний формат, тому що, використовуючи стандартний формат %.6g, rand()буде виводитися випадкове число у експоненціальній нотації. Хоча пересування до 1 мільйона рядків, мабуть, достатньо на практиці, легко підтримувати більше рядків, не сплачуючи велику суму ефективного штрафу; напр %.17f.
mklement0

1
@ mklement0 Я не помітив відповіді ОП під час написання моїх. Наскільки я пам'ятаю, rand () помножується на 10e6, щоб він працював з сортуванням solaris або hpux. Гарна ідея з "$ @"
Michał Šrajer

1
Зрозумів дякую; можливо, ви могли б додати це обґрунтування множення до самої відповіді; як правило, згідно з POSIX, він sortповинен вміти обробляти десяткові дроби (навіть із тисячами роздільників, як я щойно помітив).
mklement0

7

Ruby FTW:

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

1
Чудові речі; Якщо ви використовуєте puts ARGF.readlines.shuffle, ви можете змусити його працювати як з аргументами вводу stdin, так і з ім'ям файлу.
mklement0

Ще коротше ruby -e 'puts $<.sort_by{rand}'- ARGF - це вже безліч, тому ми можемо переміщувати лінії, сортуючи їх за випадковими значеннями.
акун

6

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

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

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

4

У нас є пакет, щоб зробити саму роботу:

sudo apt-get install randomize-lines

Приклад:

Створіть упорядкований список номерів та збережіть його до 1000.txt:

seq 1000 > 1000.txt

щоб перетасувати його, просто використовуйте

rl 1000.txt

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)'

Заманливо просто, але, якщо все-таки Java VM не потрібно запускати, ці кошти при запуску значні; не працює також з великим підрахунком рядків.
mklement0

1

Ця функція bash має мінімальну залежність (лише сортування та bash):

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

Приємне баш-рішення, яке паралельне рішенню з підтримкою ОП awk, але продуктивність буде проблемою з більшим вкладом; правильне використання єдиного $RANDOMзначення перетасовується лише до 32 768 вхідних рядків; хоча ви можете розширити цей діапазон, напевно, це не варто: наприклад, на моїй машині запуск сценарію на 32 768 коротких рядків введення займає близько 1 секунди, що приблизно в 150 разів більше, ніж shufзаймає біг , і приблизно в 10-15 разів до тих пір, поки awkпотрібне рішення власного сприяння ОП . Якщо ви можете розраховувати на sortприсутність, awkмає бути і там.
mklement0

0

У Windows Ви можете спробувати цей пакетний файл, який допоможе перетасувати дані.txt. Використання пакетного коду:

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

Після видачі цієї команди maclist_temp.txt буде містити рандомізований список рядків.

Сподіваюсь, це допомагає.


Не працює для великих файлів. Я відмовився через дві години за файл на 1 мільйон + рядків
Стефан Хаберль,

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];
  }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.