Implementando um regexp estendido para adicionar um número variável de zeros à esquerda com base na posição em uma sequência

10

Estou tendo problemas para reduzir minha sintaxe sed para adicionar um número variável de zeros à esquerda em um esquema organizacional numérico. As cordas nas quais estou operando aparecem como

1.1.1.1,Some Text Here

alavancando a sintaxe sed

sed -r ":r;s/\b[0-9]{1,$((1))}\b/0&/g;tr"

Eu sou capaz de obter a resposta

01.01.01.01,Some Text Here

No entanto, o que estou procurando é algo que preencha com zero até 2 dígitos nos campos 2 e 3 e 3 dígitos no campo 4, para que todos os itens tenham um comprimento padrão em [0-9]. [0-9] { 2}. [0-9] {2}. [0-9] {3}

1.01.01.001,Some Text Here

Durante toda a minha vida, não consigo imaginar nem mesmo como modificar o limite para incluir os parâmetros necessários para ajustar apenas os números após um período. Eu acho que tem algo a ver com o uso do \ b que eu entendo corresponde a zero caracteres em um limite de palavras, mas não entendo por que minhas tentativas de adicionar um ponto à correspondência falham da seguinte maneira:

sed -r ":r;s/\.\b[0-9]{1,$((1))}\b/0&/g;tr"
sed -r ":r;s/\b\.[0-9]{1,$((1))}\b/0&/g;tr"
Both cause the statement to hang

sed -r ":r;s/\b[0-9]\.{1,$((1))}\b/0&/g;tr"
sed -r ":r;s/\b[0-9]{1,$((1))}\.\b/0&/g;tr"
sed -r ":r;s/\b[0-9]{1,$((1))}\b\./0&/g;tr"
cause the statement to output:

1.01.01.1,Some Text Here

Além disso, espero ter problemas adicionais se a declaração contiver texto como:

1.1.1.1,Some Number 1 Here

É uma conclusão precipitada que eu realmente preciso aprender o sed e todas as suas complexidades. Estou trabalhando nisso, mas espero que essa declaração em particular continue a me causar problemas por um tempo. Qualquer ajuda seria muito apreciada.

Edição: Eu descobri uma maneira ... Esta declaração parece fazer o que estou procurando, mas deve haver uma maneira mais elegante de fazer isso.

sed -r ':r;s/\b[0-9]{1,1}\.\b/0&/;tr;:i;s/\b[0-9]{1,2},\b/0&/;ti;s/.//'

Além disso, sintaticamente, isso causará problemas se um formato de número semelhante aparecer no texto ... semelhante a:

1.1.1.1,Some Text Referring to Document XXX Heading 1.2.3

Nesse caso, resultará em:

1.01.01.001,Some Text Referring to Document XXX Heading 01.02.03

Resolvido Obrigado a todos pela ajuda aqui. Inicialmente resolvi o problema com a resposta que aceitei abaixo. Eu senti que mudou a solução para Python como parte de uma solução maior, aproveitando o tipo abaixo:

def getPaddedKey(line):
    keyparts = line[0].split(".")
    keyparts = map(lambda x: x.rjust(5, '0'), keyparts)
    return '.'.join(keyparts)

s=sorted(reader, key=getPaddedKey)
daijizai
fonte
Isso parece fazer o que estou procurando: sed -r ':r;s/\b[0-9]{1,1}\.\b/0&/;tr;:i;s/\b[0-9]{1,2},\b/0&/;ti;s/.//' No entanto, eu adoraria saber se existe uma abordagem mais elegante.
Daijizai
1
Estranhamente, reverter a sequência, aplicar zeros à direita e depois reverter o resultado podem atingir seu objetivo mais facilmente.
roaima 18/07
2
O uso printf(ou uma printfchamada no Awk) pode ser mais direto.
Curinga
1
isso é definitivamente algo que será mais fácil de implementar, ler, entender e modificar no futuro em uma linguagem como awk ou perl (ou qualquer outra coisa que tenha printf e fácil divisão de campo).
cas
1
@Wildcard - ponto bem tomado. Você pode me indicar algo sobre a depuração do sed? Geralmente, eu recorro a olhares prolongados pontuados por palavrões. ;) Exceto isso, às vezes eu divido uma declaração sed em pedaços menores e tento fazer com que cada uma funcione antes de combiná-las novamente. Recentemente, li um ótimo tutorial github.com/learnbyexample/Command-line-text-processing/blob/… e tinha certeza de que alguns dos exemplos estavam errados até que apliquei o olhar prolongado.
22417 Joe

Respostas:

4

Uso: leading_zero.sh input.txt

#!/bin/bash

sed -r '
    s/\.([0-9]{1,2})\.([0-9]{1,2})\.([0-9]{1,3},)/.0\1.0\2.00\3/
    s/\.0*([0-9]{2})\.0*([0-9]{2})\.0*([0-9]{3})/.\1.\2.\3/
' "$1"

Explicação:

  1. A primeira subtítulo adiciona certa quantidade de zeros a cada número. 1 zero a 2 e 3 números, 2 zero a 4 números. Não importa, quantos dígitos já existem.
  2. A segunda subestação remove todos os zeros extras, deixando apenas a quantidade necessária de números. Os números 2 e 3 devem conter apenas 2 dígitos. Deixa-os e remove os restos. O quarto número deve conter apenas 3 dígitos. Deixa-os e remove os restos.

input.txt

1.1.1.1,Some Text Here
1.1.1.1,Some Text Here
1.11.1.11,Some Text Referring to Document XXX Heading 1.2.3
1.1.1.1,Some Text Here
1.1.11.111,Some Text Referring to Document XXX Heading 1.2.3
1.11.1.1,Some Text Here

output.txt

1.01.01.001,Some Text Here
1.01.01.001,Some Text Here
1.11.01.011,Some Text Referring to Document XXX Heading 1.2.3
1.01.01.001,Some Text Here
1.01.11.111,Some Text Referring to Document XXX Heading 1.2.3
1.11.01.001,Some Text Here
MiniMax
fonte
Embora, no final, acabei escrevendo isso em Python por conveniência, essa é a melhor resposta para minha pergunta, como foi escrito, uma vez que o perl enviado anteriormente removeu barras invertidas (pelo menos) da saída. Esta 1. é uma solução sed e 2. produz a saída adequada sem molestar o texto. Marcando como resposta. Obrigado! :-)
daijizai
@daijizai como já demonstrei, a perlversão não remove as barras invertidas.
roaima
9

bash pode lidar com isso. No entanto, será muito mais lento que o perl:

echo "1.1.1.1,Some Text Here" | 
while IFS=., read -r a b c d text; do
    printf "%d.%02d.%02d.%03d,%s\n" "$a" "$b" "$c" "$d" "$text"
done
1.01.01.001,Some Text Here
Glenn Jackman
fonte
2
Ou Awk. Mas +1 para usar printf, a ferramenta sensata. (O Awk printftambém foi e foi melhor projetado do que bashpara o processamento de texto.) Veja também Por que o uso de um loop de shell para processar o texto é considerado uma má prática?
Curinga
5

Você não pediu especificamente uma perlsolução, mas aqui está uma de qualquer maneira. Pessoalmente, acho que isso é um pouco mais fácil de ler, especialmente quando dividido em várias linhas.

Primeiro, aqui está o one-liner:

(
    echo '1.2.3.4,Some Text Here'
    echo '1.01.01.1,Some Text Here'
    echo '1.1.1.1,Some Number 1 Here'
    echo '1.1.1.1,Some Text Referring to Document XXX Heading 1.2.3'
    echo '1.2.3.4,Some \n \s \text'
) |
perl -ne '($ip, $text) = split(/,/, $_, 2); $ip = sprintf("%1d.%02d.%03d.%03d", split(/\./, $ip)); print "$ip,$text"'

Seus resultados:

1.02.003.004,Some Text Here
1.01.001.001,Some Text Here
1.01.001.001,Some Number 1 Here
1.01.001.001,Some Text Referring to Document XXX Heading 1.2.3
1.02.003.004,Some \n \s \text

E aqui está o perlscript dividido e comentado (a -nflag coloca um while read; do ... doneloop implícito em torno do código):

($ip, $text) = split(/,/, $_, 2);                # Split line into two parts by comma
@octets = split(/\./, $ip)                       # Split IP address into octets by dots
$ip = sprintf("%1d.%02d.%03d.%03d", @octets);    # Apply the formatting
print "$ip,$text"                                # Output the two parts
roaima
fonte
Ironicamente, eu estava prestes a desistir do sed e mudar para o awk quando você postou isso. Parece se encaixar na conta. Vou verificar e voltar.
Daijizai
@daijizai awkiria trabalhar muito - mesmo princípio usandoprintf
roaima
A única coisa que isso falha eu não poderia ter previsto, mas é significativa. Parece remover a barra invertida da parte do texto.
Daijizai
@daijizai não aqui, não. Como você está alimentando o texto com uma barra invertida?
Adicionei
No meu uso com meu conjunto de dados interno, existem linhas com a coluna de texto contendo seqüências de caracteres como SOME \ Text \ Might \ Be \ Here \ 4Realz. Quando este conjunto de dados foi passado para a declaração perl que resultou em uma resposta como SOMETextMightBeHere4Realz
daijizai
3

Aqui está uma abordagem possível:
sed -E 's/([0-9]*\.)/0\1/g;s/.//;s/([0-9]*,)/00\1/'

Exemplos

echo "1.11.111.1111,Some Text Here" | sed -E 's/([0-9]*\.)/0\1/g;s/.//;s/([0-9]*,)/00\1/'
1.011.0111.001111,Some Text Here

Também trabalhe com esta sequência:

echo "1.1.1.1,Some Number 1 Here" | sed -E 's/([0-9]\.)/0\1/g;s/.//;s/([0-9],)/00\1/'
1.01.01.001,Some Number 1 Here

... e esta sequência:

echo "1.2.2101.7191,Some Text Here" | sed -E 's/([0-9]*\.)/0\1/g;s/.//;s/([0-9]*,)/00\1/'
1.02.02101.007191,Some Text Here
maulinglawns
fonte
Infelizmente, isso se decompõe à medida que os números sobem. Por exemplo: 1.1.11.111, algum texto aqui se tornou: 1.1.101.11001, algum texto aqui
daijizai
@daijizai Por favor, veja minha edição. Isso atenderia ao requisito?
maulinglawns
Infelizmente não, mas acho que isso pode ser minha culpa. O preenchimento zero precisa ter dois dígitos no campo 2 e 3 e 3 dígitos no campo 4. Essencialmente [0-9]. [0-9] {2}. [0-9] {2}. [0 -9] {3}, algum texto aqui
daijizai
2
perl -pe '/^\d/g && s/\G(?:(\.\K\d+(?=\.))|\.\K\d+(?=,))/sprintf "%0".($1?2:3)."d",$&/ge'

Explicação:

O método usado aqui é examinar as vizinhanças dos números e agir com base nisso. Assim, o 2º e o 3º números veem um ponto nos dois lados, enquanto o 4º numérico vê um ponto à esquerda e uma vírgula à direita.

O $ 1 é definido quando o regex segue o caminho de 2º ou 3º nums e, portanto, o preenchimento de precisão é 2. OTOH, para o 4º num, o preenchimento é 3.

% cat file.txt

1.00.3.4,Some Text Here
1.01.01.1,Some Text Here
1.0.01.1,Some Number 1 Here
1.1.1.1,Some Text Referring to Document XXX Heading 1.2.3.4
1.2.3.4,Some \n \s \text

Resultados:

1.00.03.004,Some Text Here
1.01.01.001,Some Text Here
1.00.01.001,Some Number 1 Here
1.01.01.001,Some Text Referring to Document XXX Heading 1.2.3.4
1.02.03.004,Some \n \s \text

fonte