AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / unix / Perguntas / 781710
Accepted
Mister Smith
Mister Smith
Asked: 2024-08-11 06:26:46 +0800 CST2024-08-11 06:26:46 +0800 CST 2024-08-11 06:26:46 +0800 CST

sed (ou awk): imprime grupo capturado ou espaço reservado se não existir

  • 772

Portanto, como o GitHub removeu a guia de insights para contas não premium, estou tentando listar localmente inserções e exclusões em meu repositório git por dia.

Eu descobri esta maneira de imprimir o que quero:

git log --pretty="@%ad" --date=short --shortstat |  tr "\n" " " | tr "@" "\n"

Isso produz este tipo de saída:

2024-06-13   7 files changed, 400 insertions(+), 406 deletions(-) 
2024-06-12   3 files changed, 145 insertions(+) 
2024-06-12   5 files changed, 638 deletions(-) 
2024-06-12   1 file changed, 1 insertion(+), 1 deletion(-) 

Observe os plurais em arquivo(s), inserção(ões) e exclusão(ões). Outro problema é que um commit pode não ter inserções ou exclusões (ou ambas, mas vamos ignorar este caso).

Então estou quase lá, só preciso extrair a data, inserções e exclusões e agrupar por data. Isso produzirá algum tipo de gráfico de “trabalho realizado por dia”.

Fiz esse regex para capturar os campos que tratam de todos os opcionais:

/^([0-9]{4}-[0-9]{2}-[0-9]{2})\s{3}[0-9]+\sfile(s)?\schanged,\s(([0-9]+)\sinsertion(s)?\(\+\))?(,\s)?(([0-9]+)\sdeletion(s)?\(\-\))?\s$/gm

Agora preciso obter o 1º, 4º e 8º grupos, por exemplo com sed:

echo "2024-06-13   7 files changed, 400 insertions(+), 406 deletions(-) " |
    sed -r 's/^([0-9]{4}-[0-9]{2}-[0-9]{2})\s{3}[0-9]+\sfile(s)?\schanged,\s(([0-9]+)\sinsertion(s)?\(\+\))?(,\s)?(([0-9]+)\sdeletion(s)?\(\-\))?\s$/\1 \4 \8/gm'

Isso produz a saída correta:

2024-06-13 400 406

Mas se a string de entrada não tiver inserções ou exclusões, sed simplesmente não imprimirá nada para esse grupo capturado. Ex.:

2024-06-13 400

E não tenho como saber se o número único são inserções ou exclusões.

Existe alguma maneira de extrair os grupos de cada linha, mas imprimir um "0" como espaço reservado se o grupo não existir? (não necessariamente apenas com sed e não necessariamente em um único comando).

sed
  • 7 7 respostas
  • 333 Views

7 respostas

  • Voted
  1. Best Answer
    meuh
    2024-08-11T15:17:17+08:002024-08-11T15:17:17+08:00

    Sempre há muitas soluções possíveis. Aqui está um. Gosto de um único regexp que faça tudo, mas às vezes é melhor mantê-lo simples e passo a passo:

    sed -r '
    /^([0-9]{4}-[0-9]{2}-[0-9]{2}) /{
      s/$/XXX C0 I0 D0/
      s/([0-9]+)( files? changed.*)C0/\2C\1/
      s/([0-9]+)( insertion.*)I0/\2I\1/
      s/([0-9]+)( deletion.*)D0/\2D\1/
      s/ .*XXX//
      s/[CDI]//g
    }
    '
    

    Isso corresponde à data e adiciona ao final da linha a string de espaço reservado " XXX C0 I0 D0". Os próximos 3 comandos substituem o apropriado 0pelo número real de itens alterados, inseridos ou excluídos. Toda a linha desde o final da data até o espaço reservado é removida e as letras C, D e I são removidas, deixando apenas os números atualizados anexados a elas.

    • 3
  2. Chris Davies
    2024-08-11T16:25:58+08:002024-08-11T16:25:58+08:00

    Se o espaço reservado intermediário estiver vazio, você terá um espaço duplo, que poderá preencher com um zero. Da mesma forma, se você não tiver o último item, sua linha terminará com um espaço.

    echo "2024-06-13   7 files changed, 400 insertions(+), 406 deletions(-) " |
        sed -r \
            -e 's/^([0-9]{4}-[0-9]{2}-[0-9]{2})\s{3}[0-9]+\sfile(s)?\schanged,\s(([0-9]+)\sinsertion(s)?\(\+\))?(,\s)?(([0-9]+)\sdeletion(s)?\(\-\))?\s$/\1 \4 \8/gm' \
            -e 's/  / 0 /' -e 's/ $/ 0/'
    
    • 3
  3. muru
    2024-08-11T23:22:22+08:002024-08-11T23:22:22+08:00

    Pode ser mais confiável se você --numstatmesmo usar e calcular as estatísticas necessárias. Usar --numstat(por exemplo, in git log --numstat --pretty="@%ad" --date=short) resulta em resultados como:

    @2024-06-07
    
    1       1       plugin/supertab.vim
    @2024-06-07
    
    2       34      plugin/supertab.vim
    @2024-06-06
    
    7       1       plugin/supertab.vim
    @2021-04-22
    
    4       0       .gitignore
    17      0       Makefile
    210     0       README.rst
    473     0       doc/supertab.txt
    61      0       ftplugin/html.vim
    45      0       ftplugin/xml.vim
    1144    0       plugin/supertab.vim
    

    Então você pode usar algo como o seguinte código awk:

    /^@[0-9]+-[0-9]+-[0-9]+/ { # new commit info starting with `@<date>`
      if (date) {  # print previous commit's stats
        print date, inserts, deletes
      };
      date = $0;
      getline;
      inserts = deletes = 0;  # reset counts
      next
    }
    {
      inserts += $1;
      deletes += $2;
    }
    END {
      if (date) {  # Final (initial?) commit
        print date, inserts, deletes
      }
    }
    

    Exemplo de saída:

    % git log --numstat --pretty="@%ad" --date=short | awk '/^@[0-9]+-[0-9]+-[0-9]+/ {if (date) {print date, i, d}; date=$0; getline; i=d=0; next} {i+=$1; d+=$2; } END {if (date) {print date, i, d}}' | head
    @2024-06-07 1 1
    @2024-06-07 2 34
    @2024-06-06 7 1
    @2021-04-22 1954 0
    

    Se quiser combinar as estatísticas de todos os commits no mesmo dia, você pode usar algo como:

    if (date == $0) {
      getline; next
    }
    if (date) {
      # print previous commit's stats
      #...
    }
    

    Então:

    % git log --numstat --pretty="@%ad" --date=short | awk '/^@[0-9]+-[0-9]+-[0-9]+/ {if (date == $0) { getline; next } if (date) {print date, i, d}; date=$0; getline; i=d=0; next} {i+=$1; d+=$2; } END {if (date) {print date, i, d}}' | head
    @2024-06-07 3 35
    @2024-06-06 7 1
    @2021-04-22 1954 0
    
    • 2
  4. Toby Speight
    2024-08-11T17:15:24+08:002024-08-11T17:15:24+08:00

    Podemos inserir 0 insertionsdepois de "alterado" se estiver faltando:

    /insertion/!s/changed,/& 0 insertions(+),/
    

    Da mesma forma, podemos acrescentar, 0 deletionsse necessário:

    /deletion/!s/$/, deletions(-)/
    

    Roteiro completo

    Canalize a saída git log --pretty=%as --shortstatpara este pequeno programa (sem necessidade de @formato ou trcomandos):

    #!/usr/bin/sed -Ef
    
    # Store date line in hold space
    /^[0-9]{4}-[0-9]{2}/h
    
    # Ignore everything except shortstat lines
    /changed/!d
    
    # Add insertions if absent
    /insertion/!s/changed,/& 0,/
    # Add deletions if absent
    /deletion/!s/$/, 0/
    
    # Reformat
    s/.* changed,//
    s/[^0-9,]+//g
    
    # Prepend the saved date
    H
    x
    y/,\n/  /
    
    • 1
  5. Ed Morton
    2024-08-12T01:09:29+08:002024-08-12T01:09:29+08:00

    Usando uma única chamada para qualquer awk em qualquer shell em cada caixa Unix, isso

    git log --pretty="@%ad" --date=short --shortstat |
    awk '
        sub(/^@/,"") { date = $0 }
        gsub(/s?\([+-]),?/,"") {
            delete val
            val[$5] = $4
            val[$7] = $6
            print date, val["insertion"]+0, val["deletion"]+0
        }
    '
    

    produzirá esta saída:

    2024-06-13 400 406
    2024-06-12 145 0
    2024-06-12 0 638
    2024-06-12 1 1
    

    Aqui está uma versão comentada do script acima para ajudar a explicar o que ele faz:

    sub(/^@/,"") {          # Try to remove a leading `@` from the current line.
                            # If that succeeded then a leading `@` was present so
                            # this must be a date line so enter this subsequent
                            # action block.
    
        date = $0           # Save the date, i.e. whats left on the line once
                            # the above `sub()` removed the leading `@`.
    }
    
    gsub(/s?\([+-]),?/,"") {#  Try to remove a string that contains `(+)` or `(-)`
                            # optionally preceded by `s` or followed by `,` so
                            # `insertion(+)`, `insertions(+),` or similar become
                            # `insertion` and `deletions(-),` etc. become `deletion`.
                            # If that succeeded then this is a line containing
                            # insertion and deletion values to process them.
    
        delete val          # Remove the `val` array to ensure no it cannot contain
                            # leftover valus from processing the previous line.
    
        val[$5] = $4        # $5 contains `insertion` or `deletion` or is empty
                            # so save the mapping from that to the value thats
                            # in $4 so we have e.g. `val["insertion"] = 400`.
    
        val[$7] = $6        # Ditto for $7 and $6.
    
        print date, val["insertion"]+0, val["deletion"]+0
                            # Print the saved values in the order we want them
                            # to be output. Adding `0` means if `insertion` and/or
                            # deletion were missing from the input and so `val[]`
                            # wasnt populated for them on this line, wed still
                            # print `0` instead of a null string.
    }
    

    Vejo pela sua resposta que você realmente deseja somar os valores por data - se sim, então isso, novamente usando any awk:

    git log --pretty="@%ad" --date=short --shortstat |
    awk '
        sub(/^@/,"") {
            date = $0
            if ( (date != prev) && (prev != "") ) {
                print prev, val["insertion"]+0, val["deletion"]+0
                delete val
            }
            prev = date
        }
        gsub(/s?\([+-]),?/,"") {
            val[$5] += $4
            val[$7] += $6
        }
        END {
            print prev, val["insertion"]+0, val["deletion"]+0
        }
    '
    

    produzirá esta saída:

    2024-06-13 400 406
    2024-06-12 146 639
    

    sem ter que armazenar todos os valores na memória de uma só vez.

    O acima pressupõe que isso:

    git log --pretty="@%ad" --date=short --shortstat
    

    produz uma saída como esta:

    @2024-06-13
    
     7 files changed, 400 insertions(+), 406 deletions(-)
    
    @2024-06-12
    
     3 files changed, 145 insertions(+)
    
    @2024-06-12
    
     5 files changed, 638 deletions(-)
    
    @2024-06-12
    
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    • 1
  6. Mister Smith
    2024-08-11T22:25:32+08:002024-08-11T22:25:32+08:00

    Solução completa baseada na regex do @meuh , mas ignorando a quantidade de arquivos alterados:

    git log --pretty="@%ad" --date=short --shortstat |  tr "\n" " " | tr "@" "\n" |
     tail -n +2 |
     sed -r '
    /^([0-9]{4}-[0-9]{2}-[0-9]{2}) /{
      s/$/XXX I0 D0/
      s/([0-9]+)( insertion.*)I0/\2I\1/
      s/([0-9]+)( deletion.*)D0/\2D\1/
      s/ .*XXX//
      s/[DI]//g
    }' |
     awk '{a[$1]+=$2; b[$1]+=$3} END { for (i in a) print i,a[i],b[i] }' |
     sort -k 1
    

    O tailcomando da segunda linha é necessário para remover uma primeira linha vazia que as trsubstituições introduzem na primeira linha.

    Depois disso, o sedcomando remove tudo exceto data, linhas inseridas e linhas excluídas, contabilizando commits sem inserções ou exclusões.

    Em seguida, o awkcomando soma a 2ª e a 3ª colunas agrupadas pela primeira coluna (data). Mas como awkas chaves do array não estão ordenadas, precisamos classificar o resultado com um final sortpela primeira coluna, o que produz a lista de estatísticas diárias exclusivas em ordem decrescente por data, por exemplo:

    2024-05-29 1234 2
    2024-05-30 350 76
    2024-06-03 405 68
    2024-06-06 483 36
    2024-06-11 447 59
    2024-06-12 795 12
    2024-06-13 522 436
    ...
    
    • 0
  7. ilkkachu
    2024-08-12T17:18:18+08:002024-08-12T17:18:18+08:00

    Em Perl, isso seria relativamente simples, já que você pode usar a formatação inteira %dcom printf, que imprimirá valores vazios como zeros, e/ou você pode usar o //operador para definir um valor "padrão":

    % git ... | perl -0777 -ne '
        while (/^([0-9]{4}-[0-9]{2}-[0-9]{2})\s{3}[0-9]+\sfile(s)?\schanged,\s(([0-9]+)\sinsertion(s)?\(\+\))?(,\s)?(([0-9]+)\sdeletion(s)?\(\-\))?\s$/gm) {
            printf "%s %d %d\n", $1, $4//0, $8//0;
        }'  
    2024-06-13 400 406
    2024-06-12 145 0
    2024-06-12 0 638
    2024-06-12 1 1
    

    ou com s///e, que permite código Perl na peça de substituição:

    % git ... | perl -0777 -pe 's#^([0-9]{4}-[0-9]{2}-[0-9]{2})\s{3}[0-9]+\sfile(s)?\schanged,\s(([0-9]+)\sinsertion(s)?\(\+\))?(,\s)?(([0-9]+)\sdeletion(s)?\(\-\))?\s$# sprintf("%s %d %d", $1, $4//0, $8//0) #gme'
    2024-06-13 400 406
    2024-06-12 145 0
    2024-06-12 0 638
    2024-06-12 1 1
    

    Embora, como muru já mencionou, a saída de --numstatpossa ser mais fácil de analisar.


    Por outro lado, pode ser mais fácil escolher cada valor separadamente, o que ajudaria a evitar o sangrento regex. Por exemplo, algo assim, que faz a soma diária ao mesmo tempo:

    % git ... | perl -ne '
        next unless /^([-0-9]+)\s+(\d+) files? changed/;
        $date = $1;
        $ins{$date} += $1 if /(\d+) insert/;
        $del{$date} += $1 if /(\d+) delet/;
    
        END {
            printf "date: %s total insertions: %d deletions: %d\n", $_, $ins{$_}, $del{$_} for sort keys %ins;
        }
    '
    
    date: 2024-06-12 total insertions: 146 deletions: 639
    date: 2024-06-13 total insertions: 400 deletions: 406
    

    Aliás, isso é bastante fácil de ajustar para que trnão seja necessário, basta definir @como separador de registros:

    git log --pretty="@%ad" --date=short --shortstat | perl -ne '
        BEGIN { $/ = "@" }
        next unless /^([-0-9]+)\s+\d+ files? changed/;
        $date = $1;
        $ins{$date} += $1 if /(\d+) insert/;
        $del{$date} += $1 if /(\d+) delet/;
        
        END {
            printf "date: %s total insertions: %d deletions: %d\n", $_, $ins{$_}, $del{$_} for sort keys %ins;
        }
    '
    

    (Observe que o \sseu regex é Perl-ismo. Embora o GNU o suporte por algum motivo, ele não é padrão e não é suportado pelas ferramentas padrão em outros sistemas.)

    • 0

relate perguntas

  • Linux grep o que no arquivo 1 está no arquivo 2 [duplicado]

  • como grep linhas após a terceira vírgula com condição

  • remova o número de linhas duplicadas com base na correspondência antes da primeira vírgula

  • Como posso melhorar este script de conversão de personagens?

  • Como remover uma única linha entre duas linhas

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Possível firmware ausente /lib/firmware/i915/* para o módulo i915

    • 3 respostas
  • Marko Smith

    Falha ao buscar o repositório de backports jessie

    • 4 respostas
  • Marko Smith

    Como exportar uma chave privada GPG e uma chave pública para um arquivo

    • 4 respostas
  • Marko Smith

    Como podemos executar um comando armazenado em uma variável?

    • 5 respostas
  • Marko Smith

    Como configurar o systemd-resolved e o systemd-networkd para usar o servidor DNS local para resolver domínios locais e o servidor DNS remoto para domínios remotos?

    • 3 respostas
  • Marko Smith

    apt-get update error no Kali Linux após a atualização do dist [duplicado]

    • 2 respostas
  • Marko Smith

    Como ver as últimas linhas x do log de serviço systemctl

    • 5 respostas
  • Marko Smith

    Nano - pule para o final do arquivo

    • 8 respostas
  • Marko Smith

    erro grub: você precisa carregar o kernel primeiro

    • 4 respostas
  • Marko Smith

    Como baixar o pacote não instalá-lo com o comando apt-get?

    • 7 respostas
  • Martin Hope
    user12345 Falha ao buscar o repositório de backports jessie 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl Por que a maioria dos exemplos do systemd contém WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky Como exportar uma chave privada GPG e uma chave pública para um arquivo 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll status systemctl mostra: "Estado: degradado" 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim Como podemos executar um comando armazenado em uma variável? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S Por que /dev/null é um arquivo? Por que sua função não é implementada como um programa simples? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 Como ver as últimas linhas x do log de serviço systemctl 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - pule para o final do arquivo 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla Por que verdadeiro e falso são tão grandes? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis Substitua a string em um arquivo de texto enorme (70 GB), uma linha 2017-12-30 06:58:33 +0800 CST

Hot tag

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve