condicional substituindo linhas por um número

4

Eu tenho um diretório contendo quase 11 milhões de arquivos pequenos: como este

wa_filtering_DP15_good_pops_snps_file_1
wa_filtering_DP15_good_pops_snps_file_2
.
.
.
wa_filtering_DP15_good_pops_snps_file_11232111

e cada arquivo tem apenas 2 linhas e 315 colunas se parece com isso:

1   0   0   0   0   0   0   0   0   0   1   2   1   
0   0   0   0   0   0   0   0   0   0   0   0   0

Eu quero passar por cada arquivo e se em cada coluna ambas as linhas tiverem 0 valores, substitua-as por 9 e obtenha algo assim:

1   9   9   9   9   9   9   9   9   9   1   2   1   
0   9   9   9   9   9   9   9   9   9   0   0   0

Alguém pode me ajudar a descobrir como fazer isso? Obrigado

    
por Anna1364 20.09.2017 / 19:29

6 respostas

1

Aqui está a awk solution.

awk '{split($0,ary1,/[ ]+/); getline x; split(x,ary2,/[ ]+/); 
    for (i in ary1)if (!(ary1[i]+ary2[i])){ary1[i]=ary2[i]=9}} 
END{for (r=1;r<=NF;r++) printf ("%d ", ary1[r]); printf"\n"; 
    for (z=1;z<=NF;z++) printf ("%d ", ary2[z]); printf"\n"}' infile

Explicações:

  • split($0,ary1,/[ ]+/); : lê e divide a primeira linha em uma matriz ary1 com um ou mais delimitadores de espaços entre.

  • getline x; split(x,ary2,/[ ]+/); : lê a segunda linha na variável x e a divide no array ary2 .

  • for (i in ary1)if (!(ary1[i]+ary2[i])){ary1[i]=ary2[i]=9}} : loop na matriz ary1 para cada índice em i se a soma de ambos os campos fosse zero ( !(0) acionará if(1) como condição real ) em seguida, defina o valor de ambos os campos como 9 .

  • for (r=1;r<=NF;r++) printf ("%d ", ary1[r]); printf"\n"; : imprima agora os valores finais de cada matriz ary1 e na próxima linha ary2 .

Para aplicar em todos os 11 milhões de arquivos, salve as alterações no formato FILENAME.out , onde FILENAME indica a leitura atual do arquivo nomedado por awk .

awk '{split($0,ary1,/[ ]+/); getline x; split(x,ary2,/[ ]+/); 
    for (i in ary1)if (!(ary1[i]+ary2[i])){ary1[i]=ary2[i]=9}} 
END{for (r=1;r<=NF;r++) printf ("%d ", ary1[r])>FILENAME".out"; printf"\n">FILENAME".out"; 
    for (z=1;z<=NF;z++) printf ("%d ", ary2[z])>FILENAME".out"
}' wa_filtering_DP15_good_pops_snps_file_{1..11232111}
    
por 20.09.2017 / 20:11
1

Para diversão, aqui está o Ruby

ruby -e '
    data = File.readlines(ARGV.shift)
               .map {|line| line.split.map(&:to_i)}
               .transpose
               .map {|(a,b)| (a==0 && b==0) ? [9,9] : [a,b]}
               .transpose
               .each {|row| puts row.join(" ")}
' file
1 9 9 9 9 9 9 9 9 9 1 2 1
0 9 9 9 9 9 9 9 9 9 0 0 0

Para substituir todos os arquivos:

ruby -e '
    require "tempfile"
    require "pathname"
    Pathname.new("/path/to/your/files/").each_child do |pathname|
        next unless pathname.file?
        temp = Tempfile.new(pathname.basename.to_s)
        filename = pathname.to_s
        File.readlines(filename)
            .map {|line| line.split.map(&:to_i)}
            .transpose
            .map {|(a,b)| (a==0 && b==0) ? [9,9] : [a,b]}
            .transpose
            .each {|row| temp.puts row.join(" ")}
        temp.close
        File.link filename, filename+".bak"
        File.rename temp.path, filename
    end
'
    
por 20.09.2017 / 20:37
1

Esta é uma abordagem alternativa, que pode ser lenta para milhões de arquivos em comparação com soluções awk puras.

Usando algo assim, você pode transpor linhas para colunas:

$ cat file1
1   0   0   0   0   0   0   0   0   0   1   2   1   
0   0   0   0   0   0   0   0   0   0   0   0   0

$ paste -d'-' <(head -n1 file1 |tr -s ' ' '\n') <(tail -n1 file1 |tr -s ' ' '\n')
1-0
0-0
0-0
0-0
0-0
0-0
0-0
0-0
0-0
0-0
1-0
2-0
1-0

Você pode substituir todas as ocorrências de 0-0 por 9-9 por um sed simples e pode armazenar a saída em uma variável temporária:

$ f1=$(sed 's/0-0/9-9/g' <(paste -d'-' <(head -n1 file1|tr -s ' ' '\n') <(tail -n1 file1 |tr -s ' ' '\n')))
$ echo "$f1"
1-0
9-9
9-9
9-9
9-9
9-9
9-9
9-9
9-9
9-9
1-0
2-0
1-0

Agora você pode reverter de colunas para linhas como:

$ awk -F'-' 'NR==FNR{printf "%s ",$1;p=1;next}p{printf "\n";p=0}{printf "%s ",$2}END{printf "\n"}' <(echo "$f1") <(echo "$f1")
1 9 9 9 9 9 9 9 9 9 1 2 1  
0 9 9 9 9 9 9 9 9 9 0 0 0  

E você também pode acrescentar >file1 no final do último comando awk para sobrescrever file1 com o novo conteúdo.

A única coisa que resta é fazer um loop em todos os arquivos. Pode ser feito com uma espécie de loop bash:

for f in ./wa_filtering_DP15_good_pops_snps_file_*;do
  f1=$(sed 's/0-0/9-9/g' <(paste -d'-' <(head -n1 "$f"|tr -s ' ' '\n') <(tail -n1 "$f" |tr -s ' ' '\n')))
  awk -F'-' 'NR==FNR{printf "%s ",$1;p=1;next}p{printf "\n";p=0}{printf "%s ",$2}END{printf "\n"}' <(echo "$f1") <(echo "$f1") #>"$f" #uncomment >"$f" to overwrite the files...
done
    
por 20.09.2017 / 22:12
1

com awk :

NR == 1 {   # save the values from 1st line in array t
            split($0, t, FS);
        }

NR == 2 {   # compare values from second line with those stored in array t
            for ( i = 1; i <= NF; ++i ) {
                # build l1 and l2 (line 1 and line 2) based on comparison
                if ($i == 0 && t[i] == 0) {
                    l1 = (i == 1 ? 9    : l1 OFS 9    );
                    l2 = (i == 1 ? 9    : l2 OFS 9    );
                } else {
                    l1 = (i == 1 ? t[i] : l1 OFS t[i] );
                    l2 = (i == 1 ? $i   : l2 OFS $i   );
                }
            }
        }

END     {   # output the two constructed lines
            print l1;
            print l2;
        }

Executando no arquivo de exemplo:

$ awk -f script.awk file
1 9 9 9 9 9 9 9 9 9 1 2 1
0 9 9 9 9 9 9 9 9 9 0 0 0

Em execução em todos os arquivos correspondentes a wa_filtering_DP15_good_pops_snps_file_* no diretório atual:

mkdir modified

for name in wa_filtering_DP15_good_pops_snps_file_*; do
    awk -f script.awk "$name" >"modified/$name.new"
done

Isso criará um novo arquivo para cada arquivo de entrada, com o nome do arquivo original e um sufixo .new extra. Os novos arquivos serão colocados na pasta modified no diretório atual.

  • optei por criar novos arquivos para que os originais não sejam modificados.
  • optei por colocar os novos arquivos em um novo diretório, já que ter 22 milhões de arquivos em um único diretório poderia tornar o sistema de arquivos um pouco complicado de se trabalhar.

Em geral, tente não criar milhões de arquivos em um único diretório. Em vez disso,

  1. crie muitos subdiretórios e distribua os arquivos neles, talvez com base em um algoritmo de categorização trabalhando no último inteiro do nome do arquivo, ou um hash, ou
  2. crie um único arquivo de saída que agregue todos os dados, possivelmente com linhas extras de texto identificando o que as duas linhas a seguir se referem.

A seguinte variante será executada com mais eficiência em milhões de arquivos:

FNR == 1    {   # save the values from 1st line in array t
                split($0, t, FS);
            }

FNR == 2    {   # compare values from second line with those stored in array t
                for ( i = 1; i <= NF; ++i ) {
                    # build l1 and l2 (line 1 and line 2) based on comparison
                    if ($i == 0 && t[i] == 0) {
                        l1 = (i == 1 ? 9    : l1 OFS 9    );
                        l2 = (i == 1 ? 9    : l2 OFS 9    );
                    } else {
                        l1 = (i == 1 ? t[i] : l1 OFS t[i] );
                        l2 = (i == 1 ? $i   : l2 OFS $i   );
                    }
                }

                # create output filename based on input filename
                # and output the two lines
                f = "modified/" FILENAME ".new";
                print l1 >f;
                print l2 >f;
            }

Para executá-lo:

mkdir modified

find . -maxdepth 1 -type f -name 'wa_filtering_DP15_good_pops_snps_file_*' \
    -exec awk -f script.awk {} +

Os novos arquivos serão gerados na pasta modified como antes, mas, desta vez, apenas uma fração dos processos awk será iniciada e a velocidade do processamento será bastante aumentada.

    
por 20.09.2017 / 20:23
0

Primeira variante:

Para arquivo único:

datamash -W transpose < input.txt | sed 's/0\t0/9\t9/' | datamash transpose

Para muitos arquivos, faça o mesmo no loop:

for i in *; do datamash -W transpose < "$i" |
sed 's/0\t0/9\t9/' |
datamash transpose > "new_$i"; done

Esse loop criará o novo arquivo alterado para cada arquivo, com o prefixo "novo_" adicionado. Então você pode remover todos os arquivos antigos e remover o prefixo "new_" dos nomes dos arquivos.

Segunda variante:

Esta é uma solução para o arquivo único, pois vários arquivos usam o loop, como na variante anterior.

tr '\n' '\t' < input.txt |
awk '{
    num = NF / 2;
    for(up = 1; up <= NF; up++) {
        if(up <= num) {
            low = num + up;
            if(!$up && !$low) {
                $up = 9;    
                $low = 9;
            }
        }

        printf "%s\t", $up;

        if(up % num == 0) 
            print "";
    }
}'

Explicação

  1. tr '\n' '\t' < input.txt - junte duas linhas juntas.
  2. %código%
    • verifica o elemento da primeira linha e o elemento adjacente da segunda linha simultaneamente, como: 1 e 316 , 2 e 317 , 3 e 318 , etc.
    • se os dois elementos forem 0 , eles serão alterados para 9 .
    • imprime campos pelo pedido - 1, 2, 3, 4 ... 628, 629, 630 .
    • Cada vez que o número do elemento é um múltiplo do número de elementos na linha, adiciona uma nova linha.

Entrada

1   0   0   0   0   0   0   0   0   0   1   2   1
0   0   0   0   0   0   0   0   0   0   0   0   0

Resultado

1   9   9   9   9   9   9   9   9   9   1   2   1
0   9   9   9   9   9   9   9   9   9   0   0   0
    
por 21.09.2017 / 02:02
0

Provavelmente não é eficiente o suficiente para 11 milhões de arquivos, mas é uma abordagem diferente na substituição. Leva um argumento na linha de comando; o nome do diretório onde todos os arquivos estão armazenados. O nome para o diretório pode ser codificado (consulte as notas no código). O nome base do arquivo já está codificado sem o número no final (não obrigatório). Espero que ajude alguém.

#!/bin/bash

# compare two rows in a file
# when both are 0, change both to 9
# otherwise keep original value

ProgName=${0##*/}
Pid=$$
DBG_FNAME=""
scriptUsage() {
cat <<ENDUSE

  $ProgName </path/to/directory> [ [-d|--debug] || [-f|--filename] ]

  path/to/directory:    Path to directory (NO trailing '/')
  -f|--filename:        Print the each file name to stdout after complete
  -d|--debug:           Run in debug mode (Implies filename option - SEE NOTE*)
  -h|--help:            Print this help message

  NOTE:  USING [-d|--debug] AUTOMATICALLY SETS [-f|--filename]
         You DO NOT need both together!

ENDUSE
}

# check args
#!# NOTE: you can delete from here to #!!# above 'WorkDir="$1"'
[[ -z $1 ]] && { >&2 echo "MISSING file source directory!"; scriptUsage; exit 1; }
[[ $1 == "-h" || $1 == "--help" ]] && { scriptUsage; exit 0; }
[[ -d $1 ]] || { >&2 echo "Unable to locate directory [$1]"; exit 1; }
if (( $# > 2 ))
  then
    DBG_FNAME=1
    >&2 echo "Running in debug mode from using ${2} & ${3} together!"
    echo "PID is: $Pid"
    sleep 2
    set -x
  else
    [[ $2 == "-f" || $2 == "--filename" ]] && DBG_FNAME=1
    [[ $2 == "-d" || $2 == "--debug" ]] && { echo "PID is: $Pid"; set -x; }
fi
#!!# to here #!!#
# directory as arg[1] or change to hardcoded
  WorkDir="$1"

# check for/remove trailing slash
[[ ${WorkDir:(-1)} == / ]] && WorkDir=${WorkDir:0:((${#WorkDir}-1))}

# given file root withOUT number ending
  WorkFile="${WorkDir}/wa_filtering_DP15_good_pops_snps_file_"


##== MAIN LOOP
for file in ${WorkFile}*
  do
    # reset these after each file
    TopRow=""
    BotRow=""
    NewTop=""
    NewBot=""
    SKIPME=""

    # get top row of file
    TopRow=$(sed -n '1{p;q}' $file)
    # get bottom row of file
    BotRow=$(sed -n '2{p;q}' $file)

    ##-- EACH FILE LOOP
    for (( f=0; f<${#TopRow}; f++ ))
      do
        if [[ -n $SKIPME ]]
          then
            # SKIPME is -z by default so
            # this runs every other time through
            NewTop="${NewTop} "
            NewBot="${NewBot} "
            SKIPME=""
        elif (( $((${TopRow:${f}:1}+${BotRow:${f}:1})) == 0 ))
          then
            # 0+0=0 so change to 9
            NewTop="${NewTop}9"
            NewBot="${NewBot}9"
            SKIPME=1
        else
            # (1+0 or 0+1)!=0 so keep originals
            NewTop="${NewTop}${TopRow:${f}:1}"
            NewBot="${NewBot}${BotRow:${f}:1}"
            SKIPME=1
        fi
    done
    ##--

    # overwrite original file
    printf "%s\n%s" "$NewTop" "$NewBot" > $file

    # if -f|--filename given print file name
    [[ -n $DBG_FNAME ]] && echo "$file is complete"
done
##==

FAZER EDITAR ARQUIVOS NO LUGAR. Não seria difícil fazer backups durante a execução. Retorna arquivos exatamente da maneira solicitada acima.

    
por 01.10.2017 / 04:47