Como tratar uma string de um arquivo como um valor quando executar o awk?

3

Eu tenho um arquivo que possui algum valor de ponto de dados ausente e o valor ausente é mostrado como **** . Preciso selecionar linhas com 7 colunas consecutivas com valor menor que 10. Quando eu executo meu script também fornece as linhas que possuem **** em colunas consecutivas.

Eu posso resolvê-lo facilmente substituindo todo **** por um valor mais alto. Mas não quero alterar meu arquivo de entrada. Eu quero fazer algo para que meu script trate **** como um número (maior que 10 i.e. str=****=100 ). Como posso fazer isso?

entrada de amostra consecutive7pointDown10.input -

2     3    4    5    6    7    8   0  12   14   23
2     3    4    12   6    7    8   0  1     2   23
**** **** **** **** **** **** **** 8 ****  **** 12

O resultado do meu script consecutive7pointDown10.output -

2     3    4    5    6    7    8    0    12    14   23
**** **** **** **** **** **** ****  8   ****  ****  12

Mas a saída esperada

2     3    4    5    6    7    8    0    12  14   23

Meu script consecutive7pointDown10 é o seguinte-

#!/bin/bash
########################################################################################################################
# This script results rows having at most 10°C in consecutive at most 7 points.
# input = scriptname.input
# output = scriptname.output
########################################################################################################################
input='basename "$0"'.input
output='basename "$0"'.output
awk '{
    for(i=4;i<=34-6;i++)
        {   
            if($i<=10 && $(i+1)<=10 && $(i+2)<=10 && $(i+3)<=10 && $(i+4)<=10 && $(i+5)<=10 && $(i+6)<=10)
            {
                print
                next
            }
        }
}' $input > $output
    
por alhelal 23.10.2017 / 17:29

2 respostas

2

Você pode usar awk da seguinte maneira para evitar a repetição da verificação de 7 colunas consecutivas usando um sinalizador para incrementar quando todas atenderem à condição ou redefini-la quando contrário.

awk '{c=0; split($0,arr,/ +/);
    for(x in arr) if(arr[x]<10 && arr[x]>=0) {
        if(++c==7){ print $0; next } }else{c=0} }' infile

Aqui usamos função dividida do awk « split(string, array [, fieldsep [, seps ] ]) » para dividir as linhas (O $0 representa a linha inteira em awk ) na matriz denominada arr separada por um ou mais espaços.

Em seguida, dê um loop nos elementos da matriz e verifique se seu valor está entre 10 e 0, depois incremente um sinalizador chamado c e imprima a linha se ela for atingida para 7 (significa que 7 elementos consecutivos (colunas) atendem à condição); Caso contrário, descanse a bandeira com 0.

Ou fazendo o mesmo sem dividir a linha em array.

awk '{c=0; for(i=1;i<=NF;i++) if($i<10 && $i>=0) {
    if(++c==7){ print $0; next } }else{c=0} }' infile

No seu caso, como você vai filtrar a partir da coluna 4 até o final, você precisará de algo como. O NF representa o número de campos / colunas em cada linha e continua em awk .

$ time awk '{c=0; for(i=4;i<=NF;i++) if($i<10 && $i>=0) {
    if(++c==7) {print $0; next} }else{c=0} }' infile
real    0m0.317s
user    0m0.156s
sys     0m0.172s

Ou no modo regex, novamente aplicado no seu arquivo original , onde ele contém apenas ponto flutuante números, você pode usar abaixo o comando grep que é mais eficiente e ~ 6 vezes mais rápido que awk (Onde usado com -P flag, veja Grep -E, Sed -E - baixo desempenho quando '[x] {1,9999}" é usado, mas por quê? ), mas considerando a flexibilidade de awk solução, como você pode alterar os intervalos + funcionará se Integer / Float / mixed de ambos os números.

$ time grep -P '([^\d]\d\.\d[^\d]){7}' infile
real    0m0.060s
user    0m0.016s
sys     0m0.031s

Ou de outra forma:

$ time grep -P '(\s+\d\.\d\s+){7}' infile
real    0m0.057s
user    0m0.000s
sys     0m0.031s

Ou compatibilidade em grep , sed ou awk :

$ time grep -E '([^0-9][0-9]\.[0-9][^0-9]){7}' infile
real    0m0.419s
user    0m0.375s
sys     0m0.063s
$ time sed -En '/([^0-9][0-9]\.[0-9][^0-9]){7}/p' infile
real    0m0.367s
user    0m0.172s
sys     0m0.203s
$ time awk '/([^0-9][0-9]\.[0-9][^0-9]){7}/' infile
real    0m0.361s
user    0m0.219s
sys     0m0.172s
    
por 23.10.2017 / 19:43
1
awk '/(\<[0-9]\s+){7}/{print}' input.txt

ou

sed -rn '/(\b[0-9]\s{1,}){7}/p' input.txt

fará o trabalho.

Explicação para o awk (a mesma lógica para o sed):

  • /(\<[0-9]\s+){7}/{print} - imprime linhas contendo o padrão.

  • \< - Corresponde a um limite de palavra; isto é, combina se o caracter à direita for um caractere de “palavra” e o caractere à esquerda for um caractere de “não-palavra”.

  • [0-9]\s+ - um dígito de 0 a 9 e, em seguida, um ou mais espaços.
  • (\<[0-9]\s+){7} - corresponde, se o padrão \<[0-9]\s+ for repetido sete vezes.

Entrada

2     3    4    5    6    7    8   0  12   14   23
2     3    4    12   6    7    8   0  1     2   23
**** **** **** **** **** **** **** 8 ****  **** 12

Resultado

2     3    4    5    6    7    8   0  12   14   23

EDITAR:

Para números flutuantes com precisão de um dígito (9.2, 8.1, 7.5, etc.).

awk '/(\<[0-9]\.[0-9](\s+|$)){7}/{print}' input.txt
    
por 23.10.2017 / 20:41