Embaralhamento de arquivos multilinha

Embaralhamento de arquivos multilinha

Tenho um arquivo de texto com linhas vazias separando blocos de texto. Eu gostaria de usar ferramentas de linha de comando *NIX para embaralhar esse arquivo respeitando a estrutura do bloco. Em outras palavras, na saída eu gostaria de ver a ordem alterada dos blocos; as linhas e sua ordem dentro do bloco permanecem as mesmas.

Exemplo de arquivo de entrada:

line 1
line 2

line 10
line 20
line 30

line 100
line 200

O arquivo de saída (após embaralhar):

line 10
line 20
line 30

line 1
line 2

line 100
line 200

É claro que correr repetidamente deve fornecer uma ordem diferente de blocos.

A primeira linha do arquivo sempre não está vazia. Não há linhas duplas em branco. A última linha do arquivo está sempre vazia.

Eu escrevi um script Python muito simples que lê todas as linhas em uma lista de listas, embaralha e gera. Estou curioso para saber se poderia fazer isso com ferramentas *NIX padrão.

Responder1

POSIXly, você poderia fazer algo como:

<file awk '
  BEGIN{srand(); n=rand()}
  {print n, NR, $0}
  !NF {n=rand()}
  END {if (NF) print n, NR+1, ""}' |
  sort -nk1 -k2 |
  cut -d' ' -f3-

Ou seja, prefixe cada linha com <a-random-number-that-changes-with-each-paragraph>o número da linha e, em seguida, classifique numericamente o primeiro número e o segundo para manter a ordem das linhas nos parágrafos e remover esses números extras.

Pode-se querer canalizar para sed '$d'remover a linha em branco final.

Cuidado, pois a maioria das awkimplementações srand()usa o tempo de época unix para propagar o gerador de números pseudo-aleatórios, portanto, você poderá obter o mesmo resultado se executar duas vezes no mesmo segundo (umbug histórico agora gravado na especificação POSIX, apesar dos meus esforços, infelizmente).

Responder2

Usando ferramentas GNU, isso divide os parágrafos em grupos separados por NUL, embaralha-os e depois remove os NULs:

$ sed '1s/^/\n/; s/^$/\x00/' input | shuf -z | sed '1d; s/\x00//'
line 100
line 200

line 10
line 20
line 30

line 1
line 2

Abordagem alternativa sem usar NUL

Como nem todas as ferramentas suportam caracteres NUL, aqui está uma alternativa. Isso lê parágrafos, substitui ~novas linhas, depois embaralha e converte ~novamente em novas linhas antes de exibir os resultados:

$ awk '{gsub(/\n/, "~")} 1' RS= input | shuf | awk '{gsub(/~/, "\n")} 1' ORS="\n\n"
line 10
line 20
line 30

line 100
line 200

line 1
line 2

Se o seu texto contiver ~, use outro caractere que o texto não contenha como separador de linha temporário.

Responder3

Usando Perl:

perl -MList::Util -00 -e 'chomp(my @a=<>); print join("\n\n", List::Util::shuffle @a) . "\n";' < input

Ou espalhe como um arquivo de script:

#!/usr/bin/perl
use List::Util 'shuffle';
local $/ = "";  ## paragraph mode
chomp(my @a = <>);
print join("\n\n", shuffle @a) . "\n";

informação relacionada