Com GNU datamash :
datamash -H -W -g 1,2 min 3 max 4 <input
Eu tenho um arquivo assim:
Id Chr Start End
Prom_1 chr1 3978952 3978953
Prom_1 chr1 3979165 3979166
Prom_1 chr1 3979192 3979193
Prom_2 chr1 4379047 4379048
Prom_2 chr1 4379091 4379092
Prom_2 chr1 4379345 4379346
Prom_2 chr1 4379621 4379622
Prom_3 chr1 5184469 5184470
Prom_3 chr1 5184495 5184496
O que eu gostaria de extrair é o início e o final do mesmo Id
assim:
Id Chr Start End
Prom_1 chr1 3978952 3979193
Prom_2 chr1 4379047 4379622
Prom_3 chr1 5184469 5184496
como você notou, o número de Id
repetido não é constante entre o início e o fim. Qualquer ideia seria muito apreciada.
Com GNU datamash :
datamash -H -W -g 1,2 min 3 max 4 <input
Isso pode ser feito com um loop clássico para ler o arquivo ou com outras formas como o awk, mas eu não sou bom no awk para dar uma solução baseada no awk. A solução abaixo funciona bem no bash e usa awk, grep e arrays simples.
Com um ID conhecido (por parâmetro ou por entrada do usuário)
id="Prom_1" #Or for user input read -p "Give Id :" id
header=$(head -1 a.txt) #get the 1st line and store it as header.
data=($(grep $id a.txt)) #grep the file for given the id and fill an array
echo "$header"
echo -e "${data[0]}\t${data[1]}\t${data[2]}\t${data[-1]}" #data[-1] refers to the last element of the data array
#Output:
Id Chr Start End
Prom_1 chr1 3978952 3979193
O truque é que a matriz obtém todos os valores do grep separados pelo espaço em branco (padrão IFS) e, assim, a matriz se parece com isso:
root@debi64:# id="Prom_1";data=($(grep $id a.txt));declare -p data
declare -a data=([0]="Prom_1" [1]="chr1" [2]="3978952" [3]="3978953" [4]=$'\nProm_1' [5]="chr1" [6]="3979165" [7]="3979166" [8]=$'\nProm_1' [9]="chr1" [10]="3979192" [11]="3979193")
#declare -p command just prints out all the data of the array (keys and values)
Para verificar automaticamente o arquivo para IDs, você pode usar o uniq prog assim:
readarray -t ids< <(awk -F" " '{print $1}' a.txt |uniq |tail -n+2)
#For Field separator= " " print the first field (id), print them as unique fields and store them in an array.
#Here the use of readarray is better to handle data separated by new lines.
declare -p ids
#Output: declare -a ids=([0]="Prom_1" [1]="Prom_2" [2]="Prom_3")
Combinando todos juntos:
header=$(head -1 a.txt) #get the 1st line and store it as header.
readarray -t ids< <(awk -F" " '{print $1}' a.txt |uniq |tail -n+2)
echo "$header"
for id in ${ids[@]}
do
data=($(grep $id a.txt))
echo -e "${data[0]}\t${data[1]}\t${data[2]}\t${data[-1]}"
done
#Output
Id Chr Start End
Prom_1 chr1 3978952 3979193
Prom_2 chr1 4379047 4379622
Prom_3 chr1 5184469 5184496
você pode tentar este awk
$ awk 'NR==1{print; next}NR!=1{if(!($1 in Arr)){printf("\t%s\n%s\t%s\t%s",a,$1,$2,$3);Arr[$1]++}else{a=$NF}}END{printf("\t%s\n",a)}' input.txt
Id Chr Start End
Prom_1 chr1 3978952 3979193
Prom_2 chr1 4379047 4379622
Prom_3 chr1 5184469 5184496
awk '
NR==1{print; next}
NR!=1{
if(!($1 in Arr))
{
printf("\t%s\n%s\t%s\t%s",a,$1,$2,$3);Arr[$1]++;
}
else
{
a=$NF
}
}
END{
printf("\t%s\n",a)
}' input.txt
Supondo que as entradas de cada ID sejam classificadas numericamente na coordenada inicial:
#!/usr/bin/awk -f
NR == 1 {
# Deal with header (just print it and continue).
print;
next;
}
$1 != id {
# This is a new ID.
# Display the data for the ID we've been processing so far (if any).
if (id) {
print id, chr, start, stop;
}
# Store the data for the new ID.
id = $1;
chr = $2;
start = $3;
}
{
# The stop/end coordinate will be updated for each line.
stop = $4;
}
END {
# At the end, display the data for the last ID.
print id, chr, start, stop;
}
Teste (funciona com o GNU awk
, o BSD awk
e o mawk
):
$ ./script.awk data.in
Id Chr Start End
Prom_1 chr1 3978952 3979193
Prom_2 chr1 4379047 4379622
Prom_3 chr1 5184469 5184496
Se as entradas não forem ordenadas, classifique-as:
$ sort -k1,1 -k3,3n -o data.in data.in
Isso pode atrapalhar a linha de cabeçalho. A seguir, uma alternativa que funcionará:
$ cat <( head -n 1 data.in ) <( sed '1d' data.in | sort -k1,1 -k3,3n ) >data.new
$ mv data.new data.in
Requer bash
ou ksh
embora ...
Outra solução com o awk e o armazenamento em variáveis:
Pegue os cabeçalhos do arquivo e coloque-os no arquivo de saída:
row1=$(head -1 input_file)
echo $row1 | sed -e 's/ /\t/g' > output_file
Pegue os valores exclusivos da primeira coluna:
col1=$(for i in $(awk 'NR>1 {print $1}' input_file | uniq); do echo $i; done)
Pegue a primeira ocorrência do valor na segunda linha com base em cada valor da primeira coluna:
col2=$(for i in $(echo "$col1"); do grep -m1 $i input_file | awk '{print $2}'; done)
Pegue o primeiro valor da terceira coluna com base em cada valor da primeira coluna:
col3=$(for i in $(echo "$col1"); do grep -m1 $i input_file | tail -1 | awk '{print $3}'; done)
Pegue o último valor da quarta coluna com base em cada valor da primeira coluna:
col4=$(for i in $(echo "$col1"); do grep $i input_file | tail -1 | awk '{print $4}'; done)
Anexe todos esses valores ao arquivo de saída:
paste -d'\t' <(echo "$col1") <(echo "$col2") <(echo "$col3") <(echo "$col4") >> output_file