Заменить символ кроме последних x вхождений

9

У меня есть файл, который имеет несколько имен хостов, связанных с IP-адресами, который выглядит следующим образом:

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

Я хочу, чтобы это выглядело так:

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

Как я могу заменить. (точки) из первого столбца с - (дефис), чтобы упростить сортировку по второму столбцу? Я думал об использовании sed для замены точек до первого пробела или замены каждой точки, кроме последних трех, но у меня возникают проблемы с пониманием регулярных выражений и sed. Я могу выполнять простые замены, но это над моей головой!

Это часть более крупного скрипта, который я писал на bash. Я застрял в этой части.

Флорин
источник

Ответы:

7

Вы можете использовать AWK

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

объяснение

awkразбивает строку на пробел по умолчанию. Таким образом, первый столбец строки ( $1in awk-ese) будет тем, на котором вы хотите выполнить замены. Для этого вы можете использовать:

 gsub(regex,replacement,string)

выполнить необходимую замену.

Обратите внимание , что gsubподдерживается только gawkи , nawkно на многих современных дистрибутивах awkявляется мягкой ссылкой на gawk.

Рахул Патил
источник
1
+1 Ударь меня к этому. Я думаю, что объяснение действительно пойдет на пользу спрашивающему и будущим читателям.
Джозеф Р.
1
@JosephR. Извините, я не силен в объяснениях, но я попытался и обновил ..
Рахул Патил
2
Спецификация для POSIX awkоснована на nawk, так что все современные awkреализации должны иметь gsub. На Солярисе может понадобиться /usr/xpg4/bin/awkили nawk.
Стефан Шазелас
@RahulPatil Если вы не возражаете, я добавил несколько строк, которые, я думаю, помогут другим.
Джозеф Р.
@JosephR спасибо .. теперь это кажется идеальным .. :)
Рахул Патил
6

Если вам нужно выполнить замены в первом поле, лучше всего использовать решение Rahul's awk, но имейте в виду, что оно может повлиять на интервал (поля перезаписываются с одним пробелом между ними).

Вы можете избежать этого, написав вместо этого:

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

В -pозначает флаг «читать входной файл построчно и распечатать каждую строку после применения сценария дается -e». Затем замените ( s|pattern|replacement|) первую последовательность непробельных символов ( \S+) на соответствующий шаблон ( $&) после замены всех .на -. Хитрость заключается в использовании, s|||eгде eоператор будет оценивать выражение как замену. Таким образом, вы можете tr/./-/применить один replace ( ) к match ( $&) предыдущего ( s|||e).

Если вам нужно заменить каждое .на, -кроме последних 3 последних, на GNU sedи при условии, что у вас есть revкоманда:

rev file | sed 's/\./-/4g' | rev
Стефан Шазелас
источник
1
Обратите внимание, что решение Perl предполагает версию 5.14 или выше (для /rработы).
Джозеф Р.
3

Sed - не самый простой инструмент для работы - см. Другие ответы для лучших инструментов - но это можно сделать.

Для того, чтобы заменить .на -только до первого места, использование 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
'

(Обратите внимание, что некоторые реализации sed не поддерживают комментарии в одной строке. GNU sed поддерживает.)

Чтобы выполнить замену до последнего пробела:

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
'

Другая техника использует пространство для хранения sed. Сохраните бит, который вы не хотите изменять, в область удержания, выполните свою работу, а затем вызовите область удержания. Здесь я разбил строку на последнем месте и заменил точки на тире в первой части.

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
'
Жиль "ТАК - перестань быть злым"
источник
2

Так как Рахул дал вам канонический ответ для вашего варианта использования, я подумал, что я попытаюсь ответить на главную проблему: заменить все, кроме последнего, x вхождений регулярного выражения:

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

Приведенный выше код (проверено) не предполагает наличие полей, разделенных пробелами. Он заменит все точки на линии с помощью тире, кроме последних 3-х точек. Замените 3в коде по своему вкусу.

Джозеф Р.
источник
2

Вы можете использовать много разных инструментов для этого. Рахул Патил уже дал вам gawkодин, так что вот несколько других:

  • Perl

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

    Этот -aпереключатель заставляет perl автоматически разбивать входные строки на пробел и сохранять полученные поля в массив @F. Первое поле, таким образом, будет $F[0]так мы заменим ( s///) все вхождения .с -в первом поле , а затем напечатать весь массив.

  • ракушка

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

    Здесь цикл while читает файл и автоматически разделяется на пробелы. Это создает два поля $firstи $rest. Конструкция ${first//pattern/replacement}заменяет все вхождения patternс replacement.

Тердон
источник
+1 Пока perlrun(1)скажу, что -aэто «режим авторазделения», я предпочитаю думать о нем как о « awkрежиме»: D
Джозеф Р.
2

Я считаю, что это немного легче читать, чем большое противное регулярное выражение. По сути, я просто разбил строку на два поля в пустом месте и использовал sed в первой части.

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

В зависимости от вашей оболочки вы также можете использовать $ {host //./-} вместо команды sed.

maedox
источник
0
sed 's/\./-/' <file name>

Без использования gв конце команды вы можете сделать это ... Это просто заменит 1-е вхождение шаблона

sunandan
источник