Removendo valores numéricos em certas colunas mantendo os sinais de menos?

8

Eu tenho o seguinte quadro de dados que continua indefinidamente na horizontal e na vertical com números negativos apenas nas colunas ímpares:

-1  2  3  4 -5  9
 2  3 -4  5 -6  11

E eu quero as 2ª, 4ª e 6ª colunas completas (ou todas as colunas pares) e os sinais de menos apenas da 1ª, 3ª e 5ª (ou de todas as colunas ímpares), então eu recebo isto:

- 2   4 - 9
  3 - 5 - 11

E, eventualmente, acabar com isso:

-2  4 -9
 3 -5 -11

Então, eu preciso dos valores das colunas pares inalterados e das colunas ímpares, se houver um valor negativo, mantenha o - somente e se houver um valor positivo, descarte-o.

Existe uma maneira de fazer isso com o awk / sed?

Isso é o mais longe que eu chego:

awk '{ for (i=2;i<=NF;i+=2) $i="" }1' FILE.txt | sed 's/[0-9,.]*//g' 
    
por Asfound 21.06.2015 / 15:31

7 respostas

2

Aqui está uma maneira:

$ awk '{for(i=1;i<=NF;i+=2){if($i<0){$i="-"}else{$i="";} }};1' file |
     sed 's/- */-/g; s/  */ /g'
-2 4 -9
 3 -5 -11

O script awk passa por todas as colunas ímpares e define seu valor para - se elas forem negativas e vazias, se não. Em seguida, o sed remove todos os espaços após um - e, em seguida, substitui vários espaços consecutivos por um único. Observe que isso significa que o alinhamento será quebrado, pois alguns campos terão dois caracteres ou mais e outros terão um. Isso não será um problema se você estiver trabalhando com campos, eles simplesmente não parecerão bonitos.

    
por 21.06.2015 / 16:01
4

A sed maneira:

sed -E '
    s/^(([ \t]*-?[ \t]*[0-9.]+[ \t]+[0-9.]+)*)[ \t]+-?[ \t]*[0-9.]+$//;
    s/[0-9.]+[ \t]+([0-9.]+)//g'

Saída:

-2  4 -9
 3 -5 -11

A primeira expressão mata a coluna final se houver um número ímpar de colunas. Ele faz isso procurando 0 ou mais pares <number> <number> , onde o primeiro número pode ser negativo.

Editar: Uma solução sed mais curta, inspirada em @mikeserv:

sed -E '
    s/[0-9.]+[ \t]*([0-9.]*)//g;
    s/[- \t]*$//'

A mesma coisa com perl :

perl -lpe 's/^((\s*-?\s*[\d.]+\s*[\d.]+)*)\s+-?\s*[\d.]+$/$1/o; s/[\d.]+\s+([\d.]+)/$1/g'

Outra maneira com perl (provavelmente a mais limpa):

perl -lpe '$a = 1; s/([\d.]+\s*)/$a++ % 2 ? "" : $1/eg; s/[-\s]*$//o'
    
por 21.06.2015 / 16:18
3

Um perl um:

$ perl -anle 'BEGIN{$,=" "}
  print map{$_=$F[$_]=~/^-/?"-$F[$_+1]":" $F[$_+1]"}grep{!($_%2)}0..$#F' file
-2  4 -9
 3 -5 -11
  • -an dividiu a entrada para @F array
  • BEGIN{$,=" "} definiu o separador do campo de saída para um espaço
  • grep{!($_%2)}0..$#F obtém todos os índices pares em @F array, que são índices de elementos ímpares
  • map{$_=$F[$_]=~/^-/?"-$F[$_+1]":" $F[$_+1]"} verifica se o elemento ímpar começa com - , depois acrescenta - ao próximo elemento par, mais anexa um espaço
por 21.06.2015 / 17:00
3

Como a resposta do <@ terdon @ mas sem o sed:

awk '{ for(i=1;i<=NF;i+=2){
         if ($i<0) $(i+1)*=-1;
         $i = "";
       }
       print
     }'
    
por 21.06.2015 / 18:03
3

A python solution

python -c 'from __future__ import print_function; 
import sys, math;
for line in sys.stdin:
  x = [int(y) for y in line.split()]
  print(*[int(math.copysign(b, a)) for a, b in zip(x[::2], x[1::2])], sep=" ")
' <file
    
por 21.06.2015 / 18:58
2

Uma solução awk baseada em matemática simples:

$ cat <<M | awk '{for(i=2;i<=NF;i+=2){printf "%4s",($(i-1)<0?-1:1)*$i}print ""}'
-1  2  3  4 -5  9
2  3.2 -4  5 -6
M

  -2   4  -9
 3.2  -5
  • Faz o loop do segundo ( i=2 ) para o último campo ( i<=NF ).
  • Multiplique o campo anterior ( $(i-1) ) por -1 ou 1.
  • Formate bem a saída ( printf "%4s" ) e imprima uma nova linha à direita ( print "" ).

A única ressalva é que, se você tiver um número ímpar de colunas, o último campo não exibirá nada. Espero que isso seja o que você espera.

Aparentemente, isso é o que você espera. :)

(editado para trabalhar com valores decimais e para tornar as condições de loop mais alinhadas com a pergunta ao salvar 2 caracteres.)

    
por 22.06.2015 / 10:18
1

Você precisa esquecer completamente o negativo - deixe de fora. Você quer consolidar dois campos - da esquerda para a direita. Isso é muito fácil.

sed '   s/ *\(.*\)/ /
        s/\([0-9]*  *\)\{2\}//g
        s/[ -]*$//
' <<\IN
-1  2  3  4 -5  9
 2  3 -4  5 -6  11
IN
-2  4 -9
3 -5 -11

Observe como eu evito qualquer referência ao sinal - quando a entrada é processada, o autômato irá aceitar apenas espaços ou números porque não entende mais nada - todo o resto é completamente ignorado e permanecerá em lugar.

Quando você especifica um \{ de intervalo de repetição numérica \} para uma \( subexpressão \) , somente a última ocorrência dessa expressão é referenciada. Então você pode apenas apertar - ou truncar - um intervalo de repetição que facilmente. E porque apertamos a repetição atrás do sinal - se houver um - a segunda ocorrência desse padrão seguirá qualquer sinal que precedeu o primeiro.

O comportamento descrito acima é especificado por POSIX para aplicativos compatíveis com todos BRE, mas pouquíssimos sed s acertam. GNU sed faz.

Por último, os espaços são apenas para tornar a ocorrência padrão regular .

Claro, isso nunca funcionará para você. Ou, provavelmente, mais corretamente, o sempre funcionará para você, mas nunca retornará nenhum resultado. Como poderia se o padrão fosse indefinido ?

    
por 22.06.2015 / 00:46