Zamień znak z wyjątkiem ostatnich x wystąpień

9

Mam plik, który ma kilka nazw hostów skorelowanych z adresami IP, który wygląda następująco:

x-cluster-front-1 192.168.1.2
x-cluster-front-2 192.158.1.10
y-cluster-back-1 10.1.11.99
y-cluster-back-2 10.1.157.38
int.test.example.com 59.2.86.3
super.awesome.machine 123.234.15.6

Chcę, aby wyglądało to tak:

x-cluster-front-1 192.168.1.2
x-cluster-front-2 192.158.1.10
y-cluster-back-1 10.1.11.99
y-cluster-back-2 10.1.157.38
int-test-example-com 59.2.86.3
super-awesome-machine 123.234.15.6

Jak mogę wymienić. (kropki) z pierwszej kolumny z - (łącznikiem), aby ułatwić sortowanie według drugiej kolumny? Myślałem o użyciu seda do zamiany kropek do pierwszej spacji lub o zamianie każdej kropki oprócz trzech ostatnich, ale mam problem ze zrozumieniem wyrażeń regularnych i sed. Mogę wykonywać proste wymiany, ale to jest nad moją głową!

Jest to część większego skryptu, który pisałem bash. Utknąłem w tej części.

Florin
źródło

Odpowiedzi:

7

Możesz użyć AWK

awk '{gsub(/-/,".",$1);print}' infile

Wyjaśnienie

awkdomyślnie dzieli linię na białe znaki. Zatem pierwsza kolumna wiersza ( $1w awk-ese) będzie tą, w której chcesz wykonać podstawienia. W tym celu możesz użyć:

 gsub(regex,replacement,string)

w celu wykonania wymaganej zamiany.

Należy pamiętać, że gsubjest obsługiwana tylko dla gawka nawkjednak w wielu współczesnych dystrybucjach awkjest dowiązanie do gawk.

Rahul Patil
źródło
1
+1 Pokonaj mnie do tego. Myślę, że wyjaśnienie naprawdę przydałoby się pytającemu i przyszłym czytelnikom.
Joseph R.
1
@JosephR. Przepraszam, że nie jestem dobry w wyjaśnianiu, ale próbowałem i zaktualizowałem ...
Rahul Patil
2
Specyfikacja POSIX awkjest oparta na nawk, więc wszystkie nowoczesne awkimplementacje powinny ją mieć gsub. W systemie Solaris możesz potrzebować /usr/xpg4/bin/awklub nawk.
Stéphane Chazelas
@RahulPatil Jeśli nie masz nic przeciwko, dodałem kilka linijek, które moim zdaniem mogłyby pomóc innym.
Joseph R.
@JosephR dzięki .. wydaje się teraz idealny .. :)
Rahul Patil
6

Jeśli musisz dokonać podstawienia na pierwszym polu, najlepiej użyć rozwiązania awk Rahula, ale uważaj, może to wpłynąć na odstępy (pola są przepisywane z pojedynczym odstępem między nimi).

Możesz tego uniknąć, pisząc zamiast tego:

perl -pe 's|\S+|$&=~tr/./-/r|e' file

Te -pśrodki flag „czytać wiersz po wierszu pliku wejściowego i wydrukować każdą linię po zastosowaniu skryptu podane przez -e”. Następnie zamień ( s|pattern|replacement|) pierwszą sekwencję znaków spacji ( \S+) na dopasowany wzorzec ( $&) po zastąpieniu wszystkich .przez -. Sztuką jest użycie s|||emiejsca, w którym eoperator oceni wyrażenie jako zamiennik. Tak więc możesz zastosować jedną zamiennik ( tr/./-/) do dopasowania ( $&) poprzedniego ( s|||e).

Jeśli chcesz zastąpić każdą .z -wyjątkiem ostatnich 3 ostatnich, GNU sedi zakładając, że masz revkomendę:

rev file | sed 's/\./-/4g' | rev
Stéphane Chazelas
źródło
1
Zauważ, że rozwiązanie Perla zakłada wersję 5.14 lub wyższą ( /raby działała).
Joseph R.
3

Sed nie jest najłatwiejszym narzędziem do pracy - zobacz inne odpowiedzi na lepsze narzędzia - ale można to zrobić.

Aby zastąpić .przez -tylko do pierwszego miejsca, wykorzystania sw pętli.

sed -e '
  : a                     # Label "a" for the branching command
  s/^\([^ .]*\)\./\1-/    # If there is a "." before the first space, replace it by "-"
  t a                     # If the s command matched, branch to a
'

(Zauważ, że niektóre implementacje sed nie obsługują komentarzy w tej samej linii. GNU sed tak.)

Aby zamiast tego wykonać zamianę do ostatniego miejsca:

sed -e '
  : a                     # Label "a" for the branching command
  s/\.\(.* \)/-\1/        # If there is a "." before the last space, replace it by "-"
  t a                     # If the s command matched, branch to a
'

Inna technika wykorzystuje przestrzeń ładowni sed. Zapisz fragment, którego nie chcesz modyfikować, w polu blokady, wykonaj swoją pracę, a następnie przywołaj przestrzeń blokady. Tutaj dzielę linię w ostatnim miejscu i zastępuję kropki myślnikami w pierwszej części.

sed -e '
  h           # Save the current line to the hold space
  s/.* / /    # Remove everything up to the last space
  x           # Swap the work space with the hold space
  s/[^ ]*$//  # Remove everything after the last space
  y/./-/      # Replace all "." by "-"
  G           # Append the content of the hold to the work space
  s/\n//      # Remove the newline introduced by G
'
Gilles „SO- przestań być zły”
źródło
2

Ponieważ Rahul udzielił ci kanonicznej odpowiedzi na twój przypadek użycia, pomyślałem, że postaram się odpowiedzieć na tytułowy problem: podstawiając wszystkie oprócz x wystąpień wyrażenia regularnego:

perl -pe '
    $count = tr{.}{.}; # Count '.' on the current line
    $x = 3;
    next LINE if $count <= $x;
    while(s{\.}{-}){   # Substitute one '.' with a '-'
        last if ++$i == $count - $x # Quit the loop before the last x substitutions
    }
$i = 0
' your_file

Powyższy kod (przetestowany) nie zakłada, że ​​masz pola rozdzielone spacjami. Zastąpi wszystkie kropki linii kreskami, z wyjątkiem ostatnich 3 kropek. Zastąp 3kod w swoich upodobaniach.

Joseph R.
źródło
2

Możesz użyć do tego wielu różnych narzędzi. Rahul Patil już ci go dał, gawkwięc oto kilka innych:

  • perl

    perl -lane  '$F[0]=~s/\./-/g; print "@F"' file
    

    Te -aprzyczyny przełączników Perl wejściowych automatycznie podzielone liniami na spacji i zapisać wynikających pola do tablicy @F. Pierwsze pole, zatem będzie $F[0]więc zastąpić ( s///) wszystkie wystąpienia .ze -w pierwszym polu, a następnie wydrukować całą tablicę.

  • muszla

     while read -r a b; do printf "%s %s\n" "${a//./-}" "$b"; done < file 
    

    Tutaj pętla while odczytuje plik i automatycznie dzieli się na białe znaki. Tworzy to dwa pola $firsti $rest. Konstrukt ${first//pattern/replacement}zastępuje wszystkie wystąpienia patternz replacement.

terdon
źródło
+1 Chociaż perlrun(1)powiem ci, że -ajest to „tryb automatycznego podziału”, wolę myśleć o nim jako o „ awktrybie”: D
Joseph R.
2

Uważam, że jest to nieco łatwiejsze do odczytania niż duże nieprzyjemne wyrażenie regularne. Zasadniczo po prostu podzieliłem linię na dwa pola w białych znakach i użyłem sed w pierwszej części.

while read -r host ip; do
    echo "$(sed 's/\./-/g' <<< "$host") $ip"
done < input_file

W zależności od powłoki możesz także użyć $ {host //./-} zamiast polecenia sed.

maedox
źródło
0
sed 's/\./-/' <file name>

Bez użycia gna końcu polecenia możesz to zrobić… To po prostu zastąpi pierwsze wystąpienie wzorca

sunandan
źródło