Slick one-liner para converter uma lista como “1: 2, 3, 4, 5” para “1.2, 1.3, 1.4, 1.5”

7

Digamos que eu tenha um arquivo parecido com este:

23: a, b, c, d
24: b, d, f
25: c, g

e quero obter uma saída assim:

23.a
23.b
23.c
23.d
24.b
24.d
24.f
25.c
25.g

É claro que não é muito difícil simplesmente lançar algo fora, mas eu queria saber se havia um verso simples usando algo como o awk.

    
por Daniel McLaury 24.03.2013 / 21:39

7 respostas

19

Talvez algo como:

sed 's/: /./;s/\(\([^.]*\.\)[^,]*\), /\
/;P;D'

São duas linhas ( \<LF> pode ser substituído por \n com algumas implementações sed ).

O comando D é uma maneira de implementar while loops em sed . Ele remove a primeira linha do espaço padrão e, desde que haja algo remanescente no espaço padrão, tudo começa novamente com o que resta. Então, o acima pode ser lido como:

do {
  - change ": " to "." so we start with "23.a, b, c"
  - change "23.x, y, z" to "23.x\n23.y, z"
  - print the first line ("23.x"): P
  - remove it
} while (pattern space is not empty)

Não precisamos que o primeiro comando s faça parte do loop, mas para evitar isso, precisaríamos usar um tipo de loop mais detalhado como o uso de rótulos ( : ) e de comandos de ramificação ( b , t ).

    
por 24.03.2013 / 21:53
10

Não importa, eu acabei de me lembrar da função split do awk, o que torna isso bastante simples.

awk -F ":" '{
  split($2, ps, ",");
  for (i in ps) {
    gsub(" ", "",ps[i]);
    print $1 "." ps[i];
  }
}'

(o gsub está removendo espaços em branco externos.)

Obrigado pelas outras respostas, no entanto.

    
por 24.03.2013 / 22:05
10

Aqui está um Perl:

 perl -nle '/(.+?):\s*(.+)/; print "$1.$_" for split(/[,\s]+/,$2);' foo.txt

EXPLICAÇÃO:

  • perl -nle : isso diz ao Perl para analisar o arquivo de entrada uma linha por vez ( -n ), executar o script fornecido como um argumento para -e e adicionar uma nova linha ( \n ) para cada string impressa ( -l ).

  • /(.+?):\s*(.+)/ : corresponde os primeiros caracteres até os primeiros dois pontos seguidos por 0 ou mais espaços ( :\s* ) e, em seguida, o restante da linha. Os parênteses são a sintaxe Perl para capturar padrões, as duas correspondências são salvas como $1 e $2 .

  • split(/[,\s]*/,$2); : isso dividirá $2 (o segundo padrão correspondente da operação de correspondência acima) em , e / ou espaços, criando uma matriz anônima.

  • print "$1.$_" for split() : percorra a matriz anônima criada pela divisão acima, salvando cada membro da matriz como $_ e imprima-o junto com $1 (o primeiro padrão capturado na primeira etapa) e um ponto . .

por 24.03.2013 / 22:01
5

Aqui está um Ruby:

ruby -ane '$F.drop(1).each{|f| puts $F.first.gsub(":",".")+f.chomp(",")}' <file.txt

Explicação

  • ruby -ane: diz ao Ruby para a ua dividir as linhas, uma li n e no momento e e xecuta o argumento como um script.

  • Em um arquivo de divisão automática $F é uma matriz do resultado da divisão.

  • drop(1) ignora o primeiro campo (o número da linha) e .each faz um loop nos seguintes campos.

  • gsub substitui o : e chomp remove um separador à direita da string.

por 25.03.2013 / 11:38
4

Um one-liner awk que eu acho que é um pouco mais elegante do que a outra solução awk:

awk -F'[:, ]+' '{for(i=2;i<=NF;i++)printf $1"."$i"\n"}' file.in

Ele tira proveito do fato de que o separador de campo awk é um regex.

    
por 30.03.2013 / 21:07
2

Perl:

perl -nE '($first,$rest)=split ": "; say "$first.$_" for split ", ", $rest'

Divide a linha no primeiro número e no restante e, em seguida, imprime "$first.$_" para cada uma das letras.

    
por 25.03.2013 / 13:18
2

Que tal um script de shell bourne simples (principalmente):

tr -d ':,' file.txt | while read p r; do for i in $r; do echo "$p.$i"; done; done

O comando "tr" apenas limpa os dois-pontos (:) e vírgulas (,) - esta resposta depende de haver espaço em branco nos dados (que os dados de amostra possuem - caso contrário, você precisa usar sed para converter: e em espaços em vez de tr).

A saída de "tr" é canalizada para o loop externo "while read ...; do ...; done", que lê as linhas e as divide em duas, na primeira ocorrência de espaço em branco (ou melhor, o conteúdo de "$ IFS" - o separador de campo de entrada do shell, cujo padrão é o espaço em branco), deixando o prefixo em "$ p" e o restante da linha em "$ r".

O loop interno "para i in ...; do ...; done", em seguida, quebra o conteúdo de "$ r" no espaço em branco ("$ IFS") e coloca cada item em "$ i" antes de executar o comando echo.

EDIT: veja comentários - você não precisa de "tr" ... os dois pontos e vírgulas podem ser limpos incluindo-os na variável IFS da seguinte forma:

OIFS="$IFS"; IFS=":,       "; while read p r; do 
 for i in $r; do echo "$p.$i"; done; done <file.txt; IFS="$OIFS"

tudo feito dentro do shell - sem chamadas para programas externos ... (a menos que o echo não esteja embutido). Observe que o IFS = acima tem um espaço e um caractere de tabulação. Observe também que o $ r no segundo loop for não tem aspas em torno dele - isso é deliberado, então o shell irá dividi-lo no espaço em branco.

    
por 30.03.2013 / 15:08