Manipulowanie tekstem za pomocą sed

12

Obecnie mam wiele plików tekstowych o treści wyglądającej tak (z wieloma wierszami):

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

Chcę zmienić każdą linię, aby miała następujący format:

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

Czy jest jakiś sposób na wykonanie powyższego przy użyciu sed? Czy muszę uciekać się do Pythona?

Zanna
źródło

Odpowiedzi:

22

Możesz to zrobić za pomocą sed, tak, ale inne narzędzia są prostsze. Na przykład:

$ awk '{
        printf "%s ", $2; 
        for(i=3;i<=NF;i++){
            printf "%s:%s:1 ",$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 

Wyjaśnienie

awk będzie podzielić każdą linię wejścia na białych znaków (domyślnie), oszczędzając każdy pól jako $1, $2, $N. Więc:

  • printf "%s ", $2; wydrukuje drugie pole i końcowe miejsce.
  • for(i=3;i<=NF;i++){ printf "%s:%s:1 ",$1,$(i) }: przejdzie przez pola 3 do ostatniego pola ( NFjest liczbą pól) i dla każdego z nich wydrukuje 1. pole, a :następnie bieżące pole i a :1.
  • print "" : to po prostu drukuje ostatnią nową linię.

Lub 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 

Wyjaśnienie

Te -amarki perlzachowują się jak awki podzielić swoje wejście na spacji. Tutaj pola są przechowywane w tablicy @F, co oznacza, że ​​1. pole będzie $F[0]2., $F[1]itd. Tak więc:

  • print "$F[1] " : wydrukuj drugie pole.
  • print "$F[0]:$_:1 " for @F[2..$#F];: iteruj po polach 3 do ostatniego pola ( $#Fjest liczbą elementów w tablicy @F, więc @F[2..$#F]bierze wycinek tablicy zaczynając od trzeciego elementu do końca tablicy) i wypisz pierwsze pole, a :następnie bieżące pole i a :1.
  • print "\n" : to po prostu drukuje ostatnią nową linię.
terdon
źródło
12

Tutaj jest okropny sed sposób!

$ sed -r 's/^([0-9]+) ([0-9]+) ([0-9]+)/\2 \1:\3:1/; :a s/([0-9]+)(:[0-9]+:1) ([0-9]+)( |$)/\1\2 \1:\3: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

Bardziej czytelnie:

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

Notatki

  • -r użyj ERE
  • s/old/new/wymienić oldznew
  • ^([0-9]+) zapisz niektóre liczby na początku linii
  • \1 odniesienie do pierwszego zapisanego wzoru
  • :a oznacz tę sekcję skryptu a
  • ( |$) spacja lub koniec linii
  • t sprawdź, czy ostatnia wymiana zakończyła się powodzeniem - jeśli tak, wykonaj następną komendę
  • aznajdź etykietę :ai zrób to jeszcze raz
  • s/ $// usuń spację końcową

Po dodaniu struktury do pierwszej części wielokrotnie znajdujemy ostatnią instancję struktury i stosujemy ją do następnego numeru ...

Ale zgadzam się, że inne narzędzia ułatwiają ...

Zanna
źródło
Czekałem na twoje rozwiązanie sed: D
Ravexina
: D zajęło mi to trochę czasu @Ravexina - Myślę, że muru może zrobić czystsze
Zanna
5

Z awk:

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

lub z 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

Wynik:

0 565: 10: 1565: 12: 1565: 23: 1565: 18: 1565: 17: 1565: 25: 1 
1564: 7: 1564: 12: 1564: 13: 1564: 16: 1564: 18: 1564: 40: 1564: 29: 1564: 15: 1 
Cyrus
źródło
5

Cóż, możesz to zrobić w sed, ale python również działa.

$ ./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

Treści reformatfile.pysą następujące:

#!/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))

Jak to działa? Tak naprawdę nie dzieje się nic specjalnego. Otwieramy pierwszy argument wiersza poleceń jako plik do odczytu i przystępujemy do podziału każdego wiersza na „słowa” lub poszczególne elementy. Pierwsze słowa stają się prefzmienne, a my drukujemy na stdout drugim (słowa [1]) elemencie kończącym się spacją. Następnie konstruujemy nowy zestaw „słów” poprzez rozumienie listy i .join()funkcję na tymczasowej liście pref, każdego słowa i łańcucha "1". Ostatnim krokiem jest wydrukowanie ich

Sergiy Kolodyazhnyy
źródło
4

Z awk:

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

Chodzi o formatowanie pól oddzielonych spacją w pożądanym formacie:

  • printf("%s ", $2) drukuje drugie pole z końcową spacją

  • for(i=3; i<NF; i++) printf("%s:%s:1 ", $1, $i) iteruje od ostatniego do trzeciego pola i drukuje pola w pożądanym formacie (pierwsze pole, następnie dwukropek, następnie bieżące pole, następnie dwukropek, na końcu 1) z końcową spacją

  • printf("%s:%s:1\n", $1, $NF) wypisuje ostatnie pole z nową linią

Przykład:

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

% awk '{printf("%s ", $2); for(i=3; i<NF; i++) printf("%s:%s:1 ", $1, $i); printf("%s:%s:1\n", $1, $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
heemayl
źródło