Ersetzen Sie ein Zeichen mit Ausnahme der letzten x Vorkommen

9

Ich habe eine Datei mit einer Reihe von Hostnamen, die mit IPs korreliert sind und folgendermaßen aussehen:

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

Ich möchte, dass es so aussieht:

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

Wie kann ich das ersetzen? (Punkte) aus der ersten Spalte mit - (Bindestrich), um eine Sortierung nach der zweiten Spalte zu erleichtern? Ich dachte daran, sed zu verwenden, um Punkte bis zum ersten Leerzeichen zu ersetzen, oder jeden Punkt außer den letzten drei zu ersetzen, aber ich habe Probleme, Regex und sed zu verstehen. Ich kann einfache Ersetzungen durchführen, aber das geht mir weit über den Kopf!

Dies ist Teil eines größeren Skripts, das ich in Bash geschrieben habe. Ich stecke in diesem Teil fest.

Gulden
quelle

Antworten:

7

Sie können AWK verwenden

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

Erläuterung

awkteilt standardmäßig eine Zeile in Leerzeichen. Daher ist die erste Spalte der Zeile ( $1in awk-ese) diejenige, für die Sie die Ersetzungen durchführen möchten. Zu diesem Zweck können Sie Folgendes verwenden:

 gsub(regex,replacement,string)

um die erforderliche Substitution durchzuführen.

Beachten Sie, dass gsubnur für gawkund nawkin vielen modernen Distributionen awkein Softlink zu unterstützt wird gawk.

Rahul Patil
quelle
1
+1 Schlage mich dazu. Ich denke, eine Erklärung würde dem Fragesteller und zukünftigen Lesern wirklich zugute kommen.
Joseph R.
1
@ JosephR. Entschuldigung, ich bin nicht gut in der Erklärung, aber ich habe versucht und aktualisiert ..
Rahul Patil
2
Die POSIX-Spezifikation für awkbasiert auf nawk, daher sollten alle modernen awkImplementierungen haben gsub. Unter Solaris benötigen Sie möglicherweise /usr/xpg4/bin/awkoder nawk.
Stéphane Chazelas
@ RahulPatil Wenn es Ihnen nichts ausmacht, habe ich ein paar Zeilen hinzugefügt, von denen ich denke, dass sie anderen helfen würden.
Joseph R.
@ JosephR danke .., es scheint jetzt perfekt .. :)
Rahul Patil
6

Wenn Sie die Ersetzungen für das erste Feld vornehmen müssen, verwenden Sie am besten Rahuls awk-Lösung. Beachten Sie jedoch, dass dies den Abstand beeinträchtigen kann (Felder werden mit einem einzelnen Leerzeichen dazwischen neu geschrieben).

Sie können es vermeiden, indem Sie es stattdessen schreiben:

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

Das -pFlag bedeutet "Zeile für Zeile die Eingabedatei lesen und jede Zeile nach dem Anwenden des von angegebenen Skriptes drucken -e". Ersetzen Sie dann ( s|pattern|replacement|) die erste Folge von Nicht-Leerzeichen ( \S+) durch das übereinstimmende Muster ( $&), nachdem Sie alle .durch ersetzt haben -. Der Trick besteht darin, zu verwenden, s|||ewo der eOperator einen Ausdruck als Ersatz auswertet. Sie können also einen Ersatz ( tr/./-/) auf das match ( $&) des vorherigen ( ) anwenden lassen s|||e.

Wenn Sie jeden .durch einen -außer den letzten 3 letzten ersetzen müssen , mit GNU sedund unter der Annahme, dass Sie einen revBefehl haben:

rev file | sed 's/\./-/4g' | rev
Stéphane Chazelas
quelle
1
Beachten Sie, dass die Perl-Lösung Version 5.14 oder höher voraussetzt ( /rdamit dies funktioniert).
Joseph R.
3

Sed ist nicht das einfachste Werkzeug für den Job - siehe andere Antworten für bessere Werkzeuge - aber es kann getan werden.

Verwenden Sie in einer Schleife , .um -nur bis zum ersten Leerzeichen zu ersetzen s.

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
'

(Beachten Sie, dass einige sed-Implementierungen keine Kommentare in derselben Zeile unterstützen. GNU sed tut dies.)

So führen Sie den Austausch stattdessen bis zum letzten Leerzeichen durch:

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
'

Eine andere Technik nutzt den Laderaum von sed. Speichern Sie das Bit, das Sie nicht ändern möchten, im Haltebereich, erledigen Sie Ihre Arbeit und rufen Sie den Haltebereich ab. Hier teile ich die Linie am letzten Leerzeichen und ersetze Punkte im ersten Teil durch Bindestriche.

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 - hör auf böse zu sein'
quelle
2

Da Rahul Ihnen die kanonische Antwort für Ihren Anwendungsfall gegeben hat, dachte ich, ich würde versuchen, das Titelproblem zu beantworten: Ersetzen Sie alle bis auf die letzten x Vorkommen eines regulären Ausdrucks:

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

Der obige Code (getestet) setzt nicht voraus, dass Sie durch Leerzeichen getrennte Felder haben. Es werden alle Punkte in einer Linie durch Bindestriche mit Ausnahme der letzten 3 Punkte ersetzt. Ersetzen Sie das 3im Code nach Ihren Wünschen.

Joseph R.
quelle
2

Sie können hierfür viele verschiedene Tools verwenden. Rahul Patil hat dir bereits gawkeine gegeben, also hier ein paar andere:

  • Perl

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

    Der -aSchalter bewirkt, dass Perl die Eingabezeilen im Leerzeichen automatisch aufteilt und die resultierenden Felder im Array speichert @F. Das erste Feld, deshalb wird sein , $F[0]so dass wir (ersetzen s///) alle Vorkommen .mit -im ersten Feld und dann das gesamte Array drucken.

  • Schale

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

    Hier liest die while-Schleife die Datei und teilt sie automatisch in Leerzeichen auf. Dadurch werden zwei Felder erstellt, $firstund $rest. Das Konstrukt ${first//pattern/replacement}ersetzt alle Vorkommen von patternmit replacement.

terdon
quelle
+1 Während perlrun(1)ich Ihnen sagen werde, dass dies der -a"Autosplit-Modus" ist, stelle ich ihn mir lieber als " awkModus" vor: D
Joseph R.
2

Ich glaube, das ist ein bisschen einfacher zu lesen als ein großer böser Regex. Grundsätzlich habe ich die Zeile im Leerzeichen einfach in zwei Felder aufgeteilt und im ersten Teil sed verwendet.

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

Abhängig von Ihrer Shell können Sie anstelle des Befehls sed auch $ {host //./-} verwenden.

Maedox
quelle
0
sed 's/\./-/' <file name>

Ohne gam Ende des Befehls zu verwenden, können Sie dies tun ... Dies ersetzt einfach das erste Vorkommen des Musters

Sunandan
quelle