Substituir um caractere, exceto as últimas x ocorrências


9

Eu tenho um arquivo que tem um monte de nomes de host correlacionados com IPs que se parece com isso:

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

Eu quero que fique assim:

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

Como posso substituir o. (pontos) da primeira coluna com - (hífen) para facilitar a classificação pela segunda coluna? Eu estava pensando em usar o sed para substituir pontos até o primeiro espaço ou substituir todos os pontos, exceto os três últimos, mas estou tendo problemas para entender regex e sed. Eu posso executar substituições simples, mas isso está passando da minha cabeça!

Isso faz parte de um script maior que eu tenho escrito no bash. Estou preso nessa parte.

Respostas:


7

Você pode usar o AWK

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

Explicação

awkdivide uma linha no espaço em branco por padrão. Assim, a primeira coluna da linha ( $1em awk-ese) será aquela em que você deseja executar as substituições. Para esse fim, você pode usar:

 gsub(regex,replacement,string)

para executar a substituição necessária.

Observe que isso gsubé suportado apenas por gawke, nawkem muitas distribuições modernas, awké um link para gawk.


1
+1 Bata-me para isso. Eu acho que uma explicação realmente beneficiaria os leitores e futuros leitores também.
Joseph R.

1
@JosephR. Desculpe, eu não sou bom em explicação, mas eu tentei e atualizado ..
Rahul Patil

2
A especificação POSIX awké baseada em nawk, portanto, todas as awkimplementações modernas devem ter gsub. No Solaris, você pode precisar /usr/xpg4/bin/awkou nawk.
Stéphane Chazelas

@RahulPatil Se você não se importa, adicionei algumas linhas que acho que ajudariam outras pessoas.
Joseph R.

@JosephR obrigado .., parece perfeito agora .. :)
Rahul Patil

6

Se você precisar fazer as substituições no primeiro campo, o melhor é usar a solução awk de Rahul, mas cuidado, isso pode afetar o espaçamento (os campos são reescritos com um único espaço entre eles).

Você pode evitá-lo escrevendo-o:

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

O -psinalizador significa "leia o arquivo de entrada linha por linha e imprima cada linha após aplicar o script fornecido por -e". Em seguida, substitua ( s|pattern|replacement|) a primeira sequência de caracteres não espaciais ( \S+) pelo padrão correspondente ( $&) depois de substituir todos .por -. O truque é usar s|||eonde o eoperador avaliará uma expressão como uma substituição. Portanto, você pode ter uma substituição ( tr/./-/) aplicada à partida ( $&) da anterior ( s|||e).

Se você precisar substituir todos .por um, -exceto os três últimos, pelo GNU sede supondo que você tenha um revcomando:

rev file | sed 's/\./-/4g' | rev

1
Observe que a solução Perl assume a versão 5.14 ou superior (para /rque funcione).
Joseph R.

3

O Sed não é a ferramenta mais fácil para o trabalho - veja outras respostas para obter melhores ferramentas - mas pode ser feito.

Para substituir .por -apenas até o primeiro espaço, o uso sem um loop.

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
'

(Observe que algumas implementações sed não suportam comentários na mesma linha. GNU sed sim.)

Para executar a substituição até o último espaço:

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
'

Outra técnica utiliza o espaço de espera do sed. Salve o bit que não deseja modificar no espaço de espera, faça seu trabalho e depois recupere o espaço de espera. Aqui, divido a linha no último espaço e substituo pontos por traços na primeira parte.

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

Como Rahul deu a resposta canônica para seu caso de uso, pensei em responder ao problema titular: substituindo todas, exceto as últimas x ocorrências de uma regex:

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

O código acima (testado) não pressupõe que você tenha campos separados por espaço. Ele substituirá todos os pontos em uma linha por traços, exceto os últimos 3 pontos. Substitua o 3código ao seu gosto.


2

Você pode usar muitas ferramentas diferentes para isso. Rahul Patil já deu uma, gawkentão aqui estão algumas outras:

  • perl

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

    A -aopção faz com que o perl divida automaticamente as linhas de entrada no espaço em branco e salve os campos resultantes na matriz @F. O primeiro campo, portanto, será, portanto, $F[0]substituiremos ( s///) todas as ocorrências de .por -no primeiro campo e, em seguida, imprimiremos toda a matriz.

  • Concha

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

    Aqui, o loop while lê o arquivo e divide automaticamente em espaço em branco. Isso cria dois campos $firste $rest. A construção ${first//pattern/replacement}substitui todas as ocorrências de patterncom replacement.


+1 Enquanto perlrun(1)lhe dirá que -aé "modo autosplit", prefiro pensar nisso como " awkmodo": D
Joseph R.

2

Eu acredito que isso é um pouco mais fácil de ler do que um grande regex desagradável. Basicamente, eu apenas divido a linha em dois campos no espaço em branco e usei sed na primeira parte.

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

Dependendo do seu shell, você também pode usar $ {host //./-} em vez do comando sed.


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

Sem usar gno final do comando, você pode fazer isso ... Isso substituirá simplesmente a 1ª ocorrência do padrão

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.