Quebra de linhas para um número especificado de campos

3

Eu tenho um arquivo de texto que contém vários vetores, como segue. Os componentes desses vetores são separados por espaço e são divididos em poucas linhas. Este arquivo é gerado depois de eu executar um comando no terminal do Ubuntu.

0 -1 -0.494 0.12 -0.919 0.112 0.914 -0.681 -0.067 -0.918 -0.443 -0.216 -0.48 0.55 0.701 0.429 0.699 -0.726 -0.39 0.172 0.61 -0.599 0.728 -0.883 -0.32 0.044 -0.189 -0.732 -0.309 -0.286 -0.859 0.107 0.298 
0 0 0.869 0.641 -0.331 -0.631 -0.236 0.303 0.998 0.153 -0.89 -0.927 -0.671 -0.478 0.693 -0.007 -0.64 0.091 -0.249 -0.881 0.641 0.689 0.222 -0.398 0.548 -0.268 -0.877 -0.333 -0.55 0.858 0.504 0.215 -0.178 
0 0 0 0.758 -0.214 0.768 0.329 0.667 -0.013 0.367 0.103 -0.307 -0.565 0.685 0.171 -0.903 0.32 -0.682 -0.887 -0.44 -0.467 0.409 -0.649 0.249 0.772 -0.962 0.443 -0.594 0.776 -0.427 0.088 -0.971 0.938 

Como posso converter esse arquivo em outro arquivo usando comandos shell com o seguinte formato, onde cada vetor está em uma linha separada e o cabeçalho do arquivo é o número de vetores de três componentes?

n
V1x V1y V1z
V2x V2y V2z
V3x V3y V3z
...
Vnx Vny Vnz

onde n é o número de vetores de três componentes no arquivo. No arquivo que tenho: V1x=0 , V1y=-1 , V1z=-0.494 . V2x=0.12 , V2y=-0.919 , V2z=0.112 e assim por diante.

    
por AFP 18.05.2014 / 10:35

5 respostas

3

Um one-liner Perl:

perl -p00E 'y/\n/ /;say s/(\S+\s+){3}\K/\n/g' file

Note que esta solução e Gnouc's assumem que o arquivo é pequeno o suficiente para ser armazenado na memória como um todo .

Explicação

  • -p significa alias a cada registro do arquivo para $_ e imprime o conteúdo de $_ após cada registro ter sido processado.
  • -00 significa definir o separador de registro como nulo para ler o arquivo inteiro como um registro.
  • -E significa tratar a seguinte cadeia como código Perl. Usar -E em vez do usual -e significa que posso usar o recurso say .
  • y/\n/ / torna o arquivo inteiro uma linha (observe que y/// é sinônimo de tr/// em Perl para obrigar sed usuários).
  • s/(\S+\s+){3}\K/\n/g significa anexar uma nova linha após cada padrão de (espaço em branco seguido de espaço em branco repetido 3 vezes = = um vetor).
  • Como s/// retornará o número de substituições feitas com sucesso, usá-lo como um argumento para say imprimirá o número de substituições (= vetores).
  • Depois que a contagem é impressa, o conteúdo de $_ é impresso porque usamos -p .

Atualizar

Se você quer o valor máximo:

perl -p00E 'y/\n/ /;s/(\S+\s+){1}\K/\n/g' file | sort -nr | head -1

A vantagem desta solução

Tem apenas um "número mágico". Em outras palavras, se você de repente começou a trabalhar com vetores 2D, tudo o que você precisa fazer é alterar o {3} no código para {2} .

A desvantagem desta solução

Se você não estiver familiarizado com o Perl, leia como um feitiço de magia negra.

    
por 18.05.2014 / 14:08
2

algo como

ruby -e 'ns = STDIN.read.split(/\s+/); puts(ns.size/3); 0.step(ns.size,3) do |i| puts(ns[i,3].join(" ")) end' < yourfile

deve funcionar, se você permitir que programas externos sejam chamados a partir do shell.

Edit: Talvez devêssemos levar isso no campo de golfe: -)

    
por 18.05.2014 / 12:36
2

Então você quer fazer duas coisas:

  • reenvie os dados para ter exatamente 3 coordenadas por linha;
  • prefixar uma linha com o número de vetores.

É mais simples lidar com isso como dois problemas sucessivos e independentes. Primeiro, reescreva os dados. Você pode usar o awk para isso, dizendo que qualquer sequência de espaço em branco é um separador de registro de entrada.

awk -v RS='[[:space:]]+' '{if (NR % 3) printf "%s ", $0; else print}' <input.txt >wrapped.txt

Você pode tornar isso um pouco mais curto configurando o separador de saída para uma nova linha em números de linhas que sejam múltiplos de 3 e um espaço diferente.

awk -v RS='[[:space:]]+' '{ORS = NR % 3 ? " " : "\n"; print}' <input.txt >wrapped.txt

Como a impressão é a ação padrão, isso pode ser encurtado para

awk -v RS='[[:space:]]+' 'ORS = NR % 3 ? " " : "\n"' <input.txt >wrapped.txt

O número de vetores é o número de linhas nos arquivos intermediários.

wc -l wrapped.txt >output.txt
cat wrapped.txt >>output.txt
    
por 19.05.2014 / 04:17
1

Uma solução perl :

$ perl -anle 'push @e,@F; 
END {
    print @e/3;
    for ($i=0;$i<@e;$i+=3) {
        printf "%-6s %-6s %-6s\n",$e[$i],$e[$i+1],$e[$i+2];
    }
} ' file
33
0      -1     -0.494
0.12   -0.919 0.112 
0.914  -0.681 -0.067
-0.918 -0.443 -0.216
-0.48  0.55   0.701 
0.429  0.699  -0.726
-0.39  0.172  0.61  
-0.599 0.728  -0.883
-0.32  0.044  -0.189
-0.732 -0.309 -0.286
-0.859 0.107  0.298 
0      0      0.869 
0.641  -0.331 -0.631
-0.236 0.303  0.998 
0.153  -0.89  -0.927
-0.671 -0.478 0.693 
-0.007 -0.64  0.091 
-0.249 -0.881 0.641 
0.689  0.222  -0.398
0.548  -0.268 -0.877
-0.333 -0.55  0.858 
0.504  0.215  -0.178
0      0      0     
0.758  -0.214 0.768 
0.329  0.667  -0.013
0.367  0.103  -0.307
-0.565 0.685  0.171 
-0.903 0.32   -0.682
-0.887 -0.44  -0.467
0.409  -0.649 0.249 
0.772  -0.962 0.443 
-0.594 0.776  -0.427
0.088  -0.971 0.938
    
por 18.05.2014 / 13:29
1

Há um recurso interessante do printf builtin

do shell bash
  The format is reused as necessary to consume all  of  the  argu‐
  ments.

que parece nos permitir obter um arquivo de valores separados por espaços em branco e distribuí-los de três em uma linha usando um simples printf

printf '%8.3f %8.3f %8.3f\n' $(<file)

(Eu usei o formato de ponto flutuante 8.3 apenas para purificar a saída, mas você pode usar %s para representar cada campo como uma string não processada).

Para contar os vetores resultantes, você poderia usar apenas wc - se você não se importa com a contagem chegando após os dados, então você poderia tee a saída

printf '%8.3f %8.3f %8.3f\n' $(<file) | tee >(wc -l)

Se você realmente insistir em colocar a contagem no topo, então uma possibilidade pode ser imprimir em uma variável, então contar e imprimir a variável (isso estará sujeito às mesmas considerações de memória que outros métodos no local)

printf -v vecs '%8.3f %8.3f %8.3f\n' $(<file)
wc -l < <(printf "$vecs") ; printf "$vecs"

Se você for um purista de shell real, poderá usar mapfile (ou seu sinônimo readarray ) para enviar os dados reformados para uma matriz em vez de uma variável de string - e, em seguida, usar ${#array[@]} da shell conta o operador para evitar uma chamada externa para wc

mapfile vecs < <(printf '%8.3f %8.3f %8.3f\n' $(<file))
printf '%d\n' ${#vecs[@]} ; printf '%s' "${vecs[@]}"

O printf final faz uso do recurso de reutilização de formato novamente para imprimir cada elemento da matriz terminada por nova linha por vez.

    
por 20.05.2014 / 13:59