Manipulação de texto com sed

12

Atualmente, tenho vários arquivos de texto com conteúdo parecido com este (com muitas linhas):

565 0 10 12 23 18 17 25
564 1 7 12 13 16 18 40 29 15

Desejo alterar cada linha para ter o seguinte formato:

0 565:10:1 565:12:1 565:23:1 565:18:1 565:17:1 565:25:1
1 564:7:1 564:12:1 564:13:1 564:16:1 564:18:1 564:40:1 564:29:1 564:15:1

Existe alguma maneira de fazer o acima usando sed? Ou preciso recorrer ao Python?

    
por yi416 05.06.2017 / 10:13

5 respostas

22

Você poderia fazer isso com sed, sim, mas outras ferramentas são mais simples. Por exemplo:

$ awk '{
        printf "%s ", ; 
        for(i=3;i<=NF;i++){
            printf "%s:%s:1 ",,$(i) 
        }
        print ""
       }' file 
0 565:10:1 565:12:1 565:23:1 565:18:1 565:17:1 565:25:1 
1 564:7:1 564:12:1 564:13:1 564:16:1 564:18:1 564:40:1 564:29:1 564:15:1 

Explicação

o awk dividirá cada linha de entrada no espaço em branco (por padrão), salvando cada campo como , , $N . Então:

  • printf "%s ", ; imprimirá o segundo campo e um espaço à direita.
  • for(i=3;i<=NF;i++){ printf "%s:%s:1 ",,$(i) } : irá iterar nos campos 3 para o último campo ( NF é o número de campos) e para cada um deles será impresso o primeiro campo, um : , depois o campo atual e um :1 .
  • print "" : isso apenas imprime uma nova linha final.

Ou Perl:

$ perl -ane 'print "$F[1] "; print "$F[0]:$_:1 " for @F[2..$#F]; print "\n"' file 
0 565:10:1 565:12:1 565:23:1 565:18:1 565:17:1 565:25:1 
1 564:7:1 564:12:1 564:13:1 564:16:1 564:18:1 564:40:1 564:29:1 564:15:1 

Explicação

O -a faz com que perl se comporte como awk e divida sua entrada no espaço em branco. Aqui, os campos são armazenados na matriz @F , significando que o primeiro campo será $F[0] , o segundo $F[1] etc. Então:

  • print "$F[1] " : imprime o 2º campo.
  • print "$F[0]:$_:1 " for @F[2..$#F]; : iterar nos campos 3 para o último campo ( $#F é o número de elementos na matriz @F , portanto @F[2..$#F] usa uma divisão de matriz começando no terceiro elemento até o final da matriz) e imprime o primeiro campo, um : , depois o campo atual e um :1 .
  • print "\n" : isso apenas imprime uma nova linha final.
por terdon 05.06.2017 / 10:23
12

Aqui está um horrível sed way!

$ sed -r 's/^([0-9]+) ([0-9]+) ([0-9]+)/ ::1/; :a s/([0-9]+)(:[0-9]+:1) ([0-9]+)( |$)/ ::1 /; t a; s/ $//' file
0 565:10:1 565:12:1 565:23:1 565:18:1 565:17:1 565:25:1 
1 564:7:1 564:12:1 564:13:1 564:16:1 564:18:1 564:40:1 564:29:1 564:15:1

Mais claramente:

sed -r '
s/^([0-9]+) ([0-9]+) ([0-9]+)/ ::1/
:a 
s/([0-9]+)(:[0-9]+:1) ([0-9]+)( |$)/ ::1 /
t a
s/ $//'

Notas

  • -r use ERE
  • s/old/new/ replace old com new
  • ^([0-9]+) salva alguns números no início da linha
  • backreference para o primeiro padrão salvo
  • :a rotular esta seção do script a
  • ( |$) ou um espaço ou o fim da linha
  • t testa se a última substituição foi bem sucedida - se foi, então faça o próximo comando
  • a encontre o rótulo :a e faça novamente
  • s/ $// remove o espaço à direita

Então, depois de adicionar a estrutura à primeira parte, encontramos repetidamente a última instância da estrutura e a aplicamos ao próximo número ...

Mas concordo que outras ferramentas facilitam ...

    
por Zanna 05.06.2017 / 10:56
5

com o awk:

awk '{printf "%s ",; for (i=3; i<=NF; i++) printf ":"$i":1 "; printf "\n"}' file

ou com bash:

while read -r -a a; do                  # read line to array a
  printf "%s " ${a[1]}                  # print column #1
  for ((i=2;i<${#a[@]};i++)); do        # loop from column #2 to number of columns
    printf "%s " "${a[0]}:${a[$i]}:1"   # print content/values
  done
  echo                                  # print line break
done < file                             # read file from stdin

Saída:

0 565:10:1 565:12:1 565:23:1 565:18:1 565:17:1 565:25:1 
1 564:7:1 564:12:1 564:13:1 564:16:1 564:18:1 564:40:1 564:29:1 564:15:1 
    
por Cyrus 05.06.2017 / 10:27
5

Bem, você pode fazer isso no sed, mas o python também funciona.

$ ./reformatfile.py  input.txt                                                                        
0 565:10:1 565:12:1 565:23:1 565:18:1 565:17:1 565:25:1
1 564:7:1 564:12:1 564:13:1 564:16:1 564:18:1 564:40:1 564:29:1 564:15:1

O conteúdo do reformatfile.py é o seguinte:

#!/usr/bin/env python3
import sys

with open(sys.argv[1]) as fd:
    for line in fd:
        words = line.strip().split()
        pref = words[0]
        print(words[1],end=" ")
        new_words = [ ":".join([pref,i,"1"]) for i in words[2:] ]
        print(" ".join(new_words))

Como isso funciona? Não há nada particularmente especial acontecendo. Abrimos o primeiro argumento de linha de comando como arquivo para leitura e continuamos com a divisão de cada linha em "palavras" ou itens individuais. As primeiras palavras se tornam pref variable, e nós imprimimos no segundo stdout (words [1]) item terminando com espaço. Em seguida, construímos um novo conjunto de "palavras" por meio de compreensões de lista e .join() function em uma lista temporária de pref, cada palavra e string "1" . O passo final é imprimi-los

    
por Sergiy Kolodyazhnyy 05.06.2017 / 12:49
4

com awk :

awk '{printf("%s ", ); for(i=3; i<NF; i++) printf("%s:%s:1 ", , $i);\
          printf("%s:%s:1\n", , $NF)}' file.txt

Tudo se resume à formatação de campos separados por espaço no formato desejado:

  • printf("%s ", ) imprime o segundo campo com um espaço à direita

  • for(i=3; i<NF; i++) printf("%s:%s:1 ", , $i) itera o terceiro ao segundo último campo e imprime os campos no formato desejado (primeiro campo, dois pontos, depois o campo atual, dois pontos, finalmente 1) com um espaço à direita

  • printf("%s:%s:1\n", , $NF) imprime o último campo com nova linha

Exemplo:

% cat file.txt
565 0 10 12 23 18 17 25
564 1 7 12 13 16 18 40 29 15

% awk '{printf("%s ", ); for(i=3; i<NF; i++) printf("%s:%s:1 ", , $i); printf("%s:%s:1\n", , $NF)}' file.txt
0 565:10:1 565:12:1 565:23:1 565:18:1 565:17:1 565:25:1
1 564:7:1 564:12:1 564:13:1 564:16:1 564:18:1 564:40:1 564:29:1 564:15:1
    
por heemayl 05.06.2017 / 10:24