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.

Florim
fonte

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.

Rahul Patil
fonte
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
Stéphane Chazelas
fonte
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
'
Gilles 'SO- parar de ser mau'
fonte
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.

Joseph R.
fonte
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.

terdon
fonte
+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.

maedox
fonte
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

sunandan
fonte