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 / 414730
Accepted
WashichawbachaW
WashichawbachaW
Asked: 2018-01-05 01:12:48 +0800 CST2018-01-05 01:12:48 +0800 CST 2018-01-05 01:12:48 +0800 CST

Processamento de texto - Como obter vários padrões em ordem a partir de um arquivo

  • 772

Eu tenho isso file.txt.Zque contém isso:

AK2*856*1036~AK3*TD1*4**~AK4*2**1*~AK4*7**1*~AK3*TD5*5**~AK4*3**6*2~AK3*REF*6**~AK4*2**1*~AK3*REF*7**~AK4*2**1*~AK3*REF*8**~AK4*2**1*~AK3*DTM*9**~AK4*2**4*20~AK4*2**4*20~AK3*CTT*12**7~AK5*R
AK2*856*1037~AK3*HL*92**~AK4*3**7*O~AK5*R~AK9*R*2*2*0~SE*25*0001~GE*1*211582~IEA*1*000211582

Cada registro consiste em vários campos começando com um cabeçalho (geralmente AKcom um número), separados por ~. Se você substituir o ~por uma quebra de linha recuada, ele lerá:

AK2*856*1036
  AK3*TD1*4**
  AK4*2**1*
  AK4*7**1*
  AK3*TD5*5**
  AK4*3**6*2
  AK3*REF*6**
  AK4*2**1*
  AK3*REF*7**
  AK4*2**1*
  AK3*REF*8**
  AK4*2**1*
  AK3*DTM*9**
  AK4*2**4*20
  AK4*2**4*20
  AK3*CTT*12**7
  AK5*R
AK2*856*1037
  AK3*HL*92**
  AK4*3**7*O
  AK5*R
  AK9*R*2*2*0
  SE*25*0001
  GE*1*211582
  IEA*1*000211582

Cada campo possui subcampos separados por *. Por exemplo, o subcampo AK201é o primeiro campo após um AK2cabeçalho, portanto, é 856para as linhas de exemplo.

Como você pode ver, há 2 linhas com uma string inicial de AK2. Isso é como um cabeçalho de linha ou, como o chamamos, cabeçalho de segmento. Existem dois cabeçalhos de segmento em file.txt.Z. O que eu quero é obter esses dados de cada cabeçalho de segmento em ordem:

Dados Necessários:

  • AK202 (segundo campo após o AK2cabeçalho) - AK2*856*this_numeric_valueantes do asterisco ou ~.
  • AK301 (primeiro campo após o AK3cabeçalho) - ~AK3*this_string_valueantes do *ou ~.
  • AK502 (segundo campo após o AK5cabeçalho) - ~AK5*some_string_value*this_numeric_valueantes do *ou ~.
  • AK401 (primeiro campo após o AK4cabeçalho) - ~AK4*this_numeric_valueantes do *ou ~.
  • Cada valor numérico de AK4ou AK5campo deve ter sempre pelo menos 2 dígitos. por exemplo AK502 = 2; AK502 = 02 ou AK401 = 9; AK401 = 09.
  • Se não houver nenhum AK3campo, não imprima nada. (Já tenho um script para isso)
  • Se uma linha contiver mais de uma sequência AK3-AK5-AK4, elas devem ser concatenadas com um espaço em branco
  • Se o AK5campo faltar após o AK3campo, procure o AK4campo em seu lugar.
  • Se nem um AK4nem um AK5campo estiverem presentes após o AK3campo, imprima apenas o AK301 (primeiro campo após o cabeçalho AK3).
  • Se houver mais de um AK4campo após um AK3campo, concatene as sequências AK502-AK401 por vírgulas

Resultado:

GS: 1036 - TD102,07 TD503 REF02 DTM02,02 CTT
GS: 1037 - HL03

Como fazer isso? Apenas me pergunte se você está confuso com a minha pergunta.

Editar: Este é o meu código: isso está dentro de um loop while

while read FILE
do
    AK2=`zgrep -oP 'AK2.[\w\s\d]*.\K[\w\s\d]*' < $FILE`
    AK3=`zgrep -oP 'AK3.\K[\w\s\d]*' < $FILE`
    AK5=`zgrep -oP 'AK5.[\w\s\d]*.\K[\w\s\d]' < $FILE`
    AK5_ERROR=`if [[ $AK5 =~ ^[0-9]+$ ]]; then  printf "%02d" $AK5 2> /dev/null; else 2> /dev/null; fi`
    AK4=`zgrep -oP 'AK4.\K[\w\s\d]*' < $FILE`
    AK4_ERROR=`if [[ $AK4 =~ ^[0-9]+$ ]]; then  printf "%02d" $AK4 2> /dev/null; else 2> /dev/null; fi`

    if [[ $AK3 ]]
    then
        if $AK5 2> /dev/null
        then
            echo "GS: $AK2 - $AK3$AK4_ERROR"
        else
            echo "GS: $AK2 - $AK3$AK5_ERROR"
        fi
    else
        echo "Errors are not specified in the file."
    fi
done < file.txt.Z

O problema com meu código original é que ele não concatena $AK3e, $AK5ou $AK4.

text-processing
  • 2 2 respostas
  • 137 Views

2 respostas

  • Voted
  1. Best Answer
    cas
    2018-01-05T22:13:44+08:002018-01-05T22:13:44+08:00

    O script perl a seguir produz sua amostra de saída exatamente quando recebe sua amostra de entrada.

    Pode não funcionar exatamente como você deseja em seu arquivo de dados real, mas não está sendo apresentado como uma solução de trabalho completa. É apresentado como uma base para começar a trabalhar - brinque com o script, mexa com ele, quebre, conserte, mude para fazer o que quiser.

    Está, sem dúvida, longe de ser ideal, mas seria difícil melhorá-lo muito sem um conhecimento muito mais detalhado / melhor explicação de seus dados de entrada e saída necessária.

    Ele processa cada linha de entrada (também conhecido como "registro" ou "segmento" usando sua terminologia) e cria uma string a ser impressa após o processamento do registro. Cada linha de saída é construída de acordo com suas especificações na seção Dados necessários de sua pergunta.

    #!/usr/bin/perl
    
    use strict;
    
    while(<>) {
      next unless /AK3/;  # skip lines that don't contain AK3
    
      # process each "segment" aka "record".
      my @fields = split /~/;
    
      # get segment "header" and 2nd sub-field of that header.
      my @segment = split(/\*/,$fields[0]);
      my $segment_header = $segment[2];
      shift @fields;
    
      my $output = "GS: $segment_header -";
    
      my $groupoutput = ''; # output for a given AK3 "group"
      my $last_go = ''; # used to avoid duplicates like "REF02 REF02 REF02"
    
      foreach my $f (@fields) {
        my @subfields = split /\*/,$f;
    
        if ($f =~ m/^AK3/) {
    
            if (($groupoutput) && ($groupoutput ne $last_go)) {
              $output .= " $groupoutput";
              $last_go = $groupoutput;  # remember the most recent $groupoutput
            };
    
            $groupoutput = $subfields[1];
    
        } elsif ($f =~ m/^AK4/) {
            my $ak401 = $subfields[1];
            $groupoutput .= sprintf("%02i,",$ak401) if ($ak401 > 0);
        } elsif ($f =~ m/^AK5/) {
            my $ak502 = $subfields[2];
            $groupoutput .= sprintf("%02i",$ak502) if ($ak502 > 0);
        };
      };
    
      # append the group output generated since the last seen AK3 (if any)
      # i.e. don't forget to print the final group on the line.
      $output .= " $groupoutput" if (($groupoutput) && ($groupoutput ne $last_go));
    
      # clean up output string before printing.
      $output =~ s/, / /g;
      $output =~ s/\s*$|,$//;
    
      print $output, "\n";
    }
    

    Salvei este script mysteryprocess.plporque não consegui pensar em um nome mais apropriado. Em seguida, executei-o com seus dados de amostra (em um arquivo chamado input):

    saída de exemplo:

    $ ./mysteryprocess.pl input 
    GS: 1036 - TD102,07 TD503 REF02 DTM02,02 CTT
    GS: 1037 - HL03
    

    Essa coisa de "REF02 REF03 REF02" me incomodou, então aqui está outra versão. Este usa um array e um hash ( @groupse %groups) para construir a linha de saída, e outro hash ( %gseen) para evitar dupes dentro de um registro lembrando valores que já vimos e incluímos na saída.

    Os dados dos grupos são armazenados em %groups, mas os hashes não são ordenados em perl, portanto, a @groupsmatriz é usada para lembrar a ordem em que vimos um grupo específico pela primeira vez.

    BTW, %groupsprovavelmente deve ser um hash-of-arrays também conhecido como HoA (ou seja, um hash que contém um array em cada elemento), o que evitaria a necessidade de limpeza $outputantes de imprimi-lo (usando a join()função perl em vez de simplesmente acrescentar uma vírgula e o novo valor para as strings). Mas acho que esse script já é complicado o suficiente para um novato em perl entender.

    #!/usr/bin/perl
    
    use strict;
    
    while(<>) {
      next unless /AK3/;  # skip lines that don't contain AK3
    
      # process each "segment" aka "record".
      my @fields = split /~/;
    
      # get segment "header" from 1st field,  and then 2nd sub-field of that header.
      # NOTE: "shift" returns the first field of an array AND removes it from
      # the array.
      my @segment = split(/\*/, shift @fields);
      my $segment_header = $segment[2];
    
      my $output = "GS: $segment_header -";
    
      my @groups=(); # array to hold each group name (ak301) in the order that
                     # we see them
      my %groups=(); # hash to hold the ak401/ak502 values for each group
      my %gseen =(); # used to avoid dupes by holding specific values of ak301+ak401
                     # and ak301+ak502 that we've seen before.
    
      my $ak301='';
    
      foreach my $f (@fields) {
        my @subfields = split /\*/, $f;
    
        if ($f =~ m/^AK3/) {
    
            $ak301 = $subfields[1];
            if (!defined($groups{$ak301})) {
              push @groups, $ak301;
            };
    
        } elsif ($f =~ m/^AK4/) {
    
            my $ak401 = sprintf("%02i",$subfields[1]);
            $ak401 = '' if ($ak401 == 0);
            next if ($gseen{$ak301.'ak4'.$ak401});
    
            if (!defined($groups{$ak301})) {
              $groups{$ak301} = $ak401;
            } else {
              $groups{$ak301} .= ',' . $ak401;
            };
            $gseen{$ak301.'ak4'.$ak401}++;
    
        } elsif ($f =~ m/^AK5/) {
    
            my $ak502 = sprintf("%02i",$subfields[1]);
            $ak502 = '' if ($ak502 == 0);
            next if ($gseen{$ak301.'ak5'.$ak502});
    
            if (!defined($groups{$ak301})) {
              $groups{$ak301} = $ak502;
            } else {
              $groups{$ak301} .= ',' . $ak502;
            };
            $gseen{$ak301.'ak5'.$ak502}++;
    
        };
      };
    
      # construct the output string in the order we first saw each group
      foreach my $group (@groups) {
        $output .= " $group" . $groups{$group};
      };
    
      # clean up output string before printing.
      $output =~ s/, |  +/ /g;
      $output =~ s/\s*$|,$//;
    
      print $output, "\n";
    }
    

    Com a seguinte entrada

    AK2*856*1036~AK3*TD1*4**~AK4*2**1*~AK4*7**1*~AK3*TD5*5**~AK4*3**6*2~AK3*REF*6**~AK4*2**1*~AK3*REF*7**~AK4*2**1*~AK3*REF*8**~AK4*2**1*~AK3*DTM*9**~AK4*2**4*20~AK4*2**4*20~AK3*CTT*12**7~AK5*R
    AK2*856*1037~AK3*HL*92**~AK4*3**7*O~AK5*R~AK9*R*2*2*0~SE*25*0001~GE*1*211582~IEA*1*000211582
    AK2*856*1099~AK3*TD1*4**~AK4*2**1*~AK4*7**1*~AK3*TD5*5**~AK4*3**6*2~AK3*REF*6**~AK4*2**1*~AK3*REF*7**~AK4*2**1*~AK3*REF*8**~AK4*3**1*~AK3*REF*8**~AK4*2**1*~AK3*DTM*9**~AK4*2**4*20~AK4*2**4*20~AK3*CTT*12**7~AK5*R
    

    A saída agora é:

    $ ./mysteryprocess.pl input 
    GS: 1036 - TD102,07 TD503 REF02 DTM02 CTT
    GS: 1037 - HL03
    GS: 1099 - TD102,07 TD503 REF02,03 DTM02 CTT
    

    Notas:

    • DTM02,02também foi recolhido em apenas DTM02. A eliminação do dupe acontece para tudo agora.
    • A fusão de grupos (ou seja, elementos com o mesmo "nome" AK301) também ocorre, independentemente de onde um elemento apareça em um registro. A versão anterior apenas mesclava campos/subcampos adjacentes se fossem iguais.

    Não tenho certeza se alguma dessas alterações é o que você deseja.


    ps: se você não tiver perlinstalado, esse código seria traduzido facilmente para awk. É um algoritmo muito simples (até simplista) direto.

    • 2
  2. Guy
    2018-01-06T10:46:28+08:002018-01-06T10:46:28+08:00

    outra tentativa, para mostrar uma versão awk, como sugerido por cas. Provavelmente pode ser feito de maneira muito mais organizada, mas foi uma experiência de aprendizado de qualquer maneira.

    #!/usr/bin/awk -f
    
    function get_slice(elem, fc,       tmpArr) {
            split(elem, tmpArr, "*")
            return tmpArr[fc]
        }
    
    BEGIN { FS="~" }
    
    /AK2/ { 
        res = get_slice($1, 3) " - "
        tmpStr = ""
        # only continue with this line if there are any AK3 fields.
        # otherwise may as well skip whole thing.
        if (match($0, /AK3/)) {
            loc=2
            for (loc=2; loc<=NF; loc++)
                if ($loc ~ /AK3/) break
    
            for ( ; loc<=NF; loc++) {
                if ($loc ~ /AK3/) {
                    # check to see whether the previous loop generated a duplicate
                    # tmpStr will be "" the first time
                    if (index(res, tmpStr) == 0)
                        res = res " " tmpStr
                    tmpStr = get_slice($loc, 2)
                    # c is a count of how many fields have been added after AK3.
                    # once positive, "," will be added.
                    c = 0
                    }
                # add the other fields
                else if ($loc ~ /AK4/) { 
                    if ((s = get_slice($loc, 2)) != "")
                        tmpStr = tmpStr sprintf("%s%02d", c++ ? "," : "", s) 
                } else if ($loc ~ /AK5/) { 
                    if ((s = get_slice($loc, 3)) != "")
                        tmpStr = tmpStr sprintf("%s%02d", c++ ? "," : "", s) 
                }
            }
            # this is repeated at the end, to make sure the final set is printed.
            if (index(res, tmpStr) == 0)
                res = res " " tmpStr
            print res
            }
        }
    

    simplesmente dividindo inicialmente os campos em '~' e, em seguida, percorrendo todos os campos disponíveis por linha. Somente quando um campo é necessário, ele é dividido em subcampos em '*' para obter os elementos solicitados. 'get_slice' retorna "" se nada for encontrado, então isso deve ser verificado.

    Acho que entendi a pergunta..

    • 2

relate perguntas

  • Grep para um conjunto de linhas de $START a $END AND que contém uma correspondência em $MIDDLE

  • Reorganize as letras e compare duas palavras

  • Subtraindo a mesma coluna entre duas linhas no awk

  • Embaralhamento de arquivo de várias linhas

  • como posso alterar o caso do caractere (de baixo para cima e vice-versa)? ao mesmo tempo [duplicado]

Sidebar

Stats

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

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

    • 4 respostas
  • Marko Smith

    ssh Não é possível negociar: "nenhuma cifra correspondente encontrada", está rejeitando o cbc

    • 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

    Como descarregar o módulo do kernel 'nvidia-drm'?

    • 13 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
    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
    Wong Jia Hau ssh-add retorna com: "Erro ao conectar ao agente: nenhum arquivo ou diretório" 2018-08-24 23:28:13 +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
  • Martin Hope
    Bagas Sanjaya Por que o Linux usa LF como caractere de nova linha? 2017-12-20 05:48:21 +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