Bash: filtra as linhas combinando com o intervalo numérico

4

Eu tenho um arquivo test com os campos: cato e pos .

1   7100
1   35000
1   49321
1   49759
2   44842
2   52794
2   53558
3   53859
3   54013
3   55172

Eu tenho um arquivo db com campos: cato , start e stop .

1   6408    8000
1   11822   16373
1   18716   23389
1   27690   34330
1   36552   39191
1   39313   44565
2   44839   50247
2   60987   65017
2   65705   71523

Meu objetivo é escolher as linhas no arquivo db , onde o campo pos do arquivo test está dentro do intervalo start e pare do arquivo db . Há a restrição de que a correspondência deve acontecer dentro de um grupo cato . Ambos os arquivos são classificados pelos campos 1 e 2. Como uma nota lateral, meus arquivos reais também têm muitos outros campos.

Com este conjunto de dados de exemplo, meu resultado esperado seria:

1   6408    8000
2   44839   50247

Eu tenho um script que eu tenho mashed. Desculpas se parece desastroso. Eu sou um iniciante.

k=1;
data_test=$(cat "test")
data_db=$(cat "db")
while read -r line
do
  # helps to keep count of test rows
  printf "$k \n"

  # get cato
  cato=$(echo $line | awk '{print $1}')
  # get pos
  pos=$(echo $line | awk '{print $2}')
  # get number of chars in pos (to reduce number of lines awk needs to look through later)
  pos_chr=$(echo -n $pos | wc -c)
  # get lines in db that start with cato and pos chars match start or stop
  matched=$(echo "$data_db" | grep -Ew "^$cato" | grep -Ew "[0-9]{$pos_chr}")
  #echo "$db_cat"

  # if matched is not empty
  if [ ! -z "$matched" ]; then
    # use awk to print lines in db where pos > start and pos < stop
    echo "$matched" | awk -v apos='$pos' 'BEGIN{OFS="\t"}{if(apos >= $2 && apos <= $3) print $0}'
    #check
    #echo "$matched" | awk -v apos=$pos 'BEGIN{OFS="\t"}{print apos,$0}'
  fi

  ((k=k+1))
done <<< "$data_test"

Parece que awk não faz a comparação na última etapa. As coisas parecem funcionar até o último passo e então não tenho certeza do que está errado. Talvez alguém veja o erro. Existe uma maneira melhor de fazer isso?

    
por rmf 21.01.2018 / 15:13

2 respostas

4

Com um único programa GNU awk (desde Gawk v4.0):

awk 'NR==FNR{ a[$1][$2]; next }
     $1 in a{ 
         for (i in a[$1]) 
             if (i >= $2 && i <= $3) { print $0; break }
     }' test db

A saída:

1   6408    8000
2   44839   50247
    
por 21.01.2018 / 15:36
0

Use join com um simples test :

$ cat is-between.sh
#!/bin/bash

while read cato start stop pos; do
    [ $start -le $pos -a $pos -le $stop ] && echo "$cato $start $stop"
done < <(join db test)

$ ./is-between.sh
1 6408 8000
2 44839 50247

Você tem dados relacionais: valores de chave em um arquivo correspondem a valores de chave em outro, linha por linha. join é a ferramenta para juntar essas coisas. Execute join db test e você perceberá que é o mesmo resultado que um SELECT * FROM db JOIN test on test.cato=db.cato se os dados estiverem em um banco de dados.

Com isso em mãos, agora você tem linhas que colocam start, stop e pos em uma linha. Fazer um loop sobre estes, usando um test para verificar o intervalo, é fácil.

    
por 21.01.2018 / 17:17