Extraction d'enregistrements à largeur fixe sans délimiteur à partir d'une seule ligne


8

J'ai besoin d'extraire des chaînes de texte à partir d'un seul fichier contenant une très longue ligne de texte sans délimiteurs. En utilisant l'exemple de ligne ci-dessous, voici les faits connus suivants:

??????? A1XXXXXXXXXX ??????? B1XXXX ??????? A1XXXXXXXXXX ??????? C1XXXXXXX

1.  It contains 38 fixed width record types 
2.  The record marker is a 7 alphanumeric character followed by, for example, A1’.
3.  Each record type has varying widths, for example, A1 record type will have 10 characters following it, if B1 then 4, and if C1 then 7.
4.  The record types arent clumped together and can be in any order. As in the example, its A1,B1,A1,C1
5.  The example above has 4 records and each record type needs to go to separate files. In this case 38 of them.

??????? A1XXXXXXXXXX

??????? B1XXXX

??????? A1XXXXXXXXXX

??????? C1XXXXXXX

6.  The record identifier, e.g. ????????A1, can appear in the body of the record so cannot use grep. 
7.  With the last point in mind, I was proposing 3 solutions but not sure on how to script this and of course would greatly appreciate some help. 
a. Traverse through the file from the beginning and sequentially strip out the record to the appropriate output file. For example, strip out first record type A1 to A1file which I know is 10 characters long then re-interrogate the file which will then have B1 which I know is 4 chars long, strip this out to B1file etc.. <<< this seems painful >>
b. Traverse through the file and append some obscure character to each record marker within the same file. Much like above but not strip out. I understand it still will use the same logic but seems more elegant
c. I did think of simply using the proposed grep -oE solution but then re-interrogate the output files to see if any of the 38 record markers exist anywhere other than at the beginning. But this might not always work.

Code Perl refactorisé pour prendre en compte vos mises à jour. Veuillez voir si cela aide.
Joseph R.

Merci Joseph. Je ne connais pas Perl mais je voulais préciser que le fichier ne contient qu'une seule ligne de texte, c'est-à-dire aucun retour chariot ni saut de ligne. Je voulais juste que ce soit clair parce que je vois dans vos commentaires que vous sous-entendez que le fichier a plus de 1 lignes, sauf si comme je l'ai dit, j'ai mal lu cela. Merci beaucoup.
jags

Cela ne devrait pas faire de différence. Le code Perl fonctionnera de la même façon s'il est sur une seule ligne ou s'il y en a plusieurs, tant que chaque ligne contient un nombre entier d'enregistrements bien formés.
Joseph R.

Merci beaucoup Joseph. Ça a marché. Testé avec si un marqueur d'enregistrement est dans le corps du dossier et ce référencement arrière permet de surmonter cela. Quelqu'un peut-il offrir un équivalent Unix s'il vous plaît?
jags

Veuillez regarder ma réponse mise à jour.
Joseph R.

Réponses:


5

Que diriez-vous

grep -oE 'A1.{10}|B1.{4}|C1.{7}' input.txt

Cela imprime chaque enregistrement de chaque type d'enregistrement sur une ligne distincte. Pour rediriger la grepsortie 3 fichiers nommés A1, B1, C1respectivement,

grep -oE 'A1.{10}|B1.{4}|C1.{7}' input.txt| 
awk -v OFS= -v FS= '{f=$1$2; $1=$2=""; print>f}'

Merci beaucoup pour cela. Cela vous dérange-t-il d'expliquer ces différents composants de script et commutateurs utilisés afin que je puisse tester et étendre s'il vous plaît. Comment puis-je ajouter le motif de 9 avant (qui en réalité sera des caractères alphanumériques de 7 caractères). Merci beaucoup.
jags

J'ai parlé trop tôt ... J'aurais également dû ajouter une information vitale, à savoir que le pattern.recordmarker pourrait apparaître dans le reste de l'enregistrement, il a donc été conseillé de supprimer un enregistrement à la fois dans un fichier et de réinterroger le fichier qui probablement signifie que je ne peux pas utiliser grep.
jags

De plus, j'ai 2 solutions possibles. - parcourir le fichier, étiqueter avec un caractère obscur pour indiquer le début d'un enregistrement valide. Déplacez les caractères X en fonction du type d'enregistrement et utilisez le même caractère obscur pour désigner l'enregistrement suivant. Cependant méfiez-vous des problèmes de tampon. Par conséquent, attendez-vous à ce qu'une nouvelle sortie interroge ressemblant à ceci "? \\ 9999999A1XXXXXXXXXX? \\ 9999999B1XXXX? \\ 9999999A1XXXXXXXXXX? \\ 9999999C1XXXXXXX" - utilisez le courant actuel mais recherchez dans chaque fichier de sortie si les autres motifs apparaissent autrement qu'au début
jags

@jags, vous voudrez peut-être mettre à jour votre question d'origine avec des données d'échantillon vraiment représentatives, tout devient un peu déroutant
iruvar

Merci 1_CR, j'ai resoumis la question. Merci à tous pour votre aide. Le plus apprécié.
jags

4

Voici une solution possible en utilisant FPAT de Gawk

BEGIN { 
    FPAT="A1.{10}|B1.{4}|C1.{7}" #define field contents
} 
{
    for(i=1;i<=NF;i++) 
        print $i >> substr($i,0,2) #print the field to file A1,B1,etc
}

En une ligne:

gawk 'BEGIN{FPAT="A1.{10}|B1.{4}|C1.{7}"} {for(i=1;i<=NF;i++)print $i >> substr($i,0,2)}' < datafile

Notez que FPATnécessite la version 4 de gawk. Voir: linuxjournaldigital.com/linuxjournal/201109#pg98
Håkon Hægland

4

En Perl:

#!/usr/bin/env perl

use strict;
use warnings;
use re qw(eval);

my %field_widths = (
    A1 => 10,
    B1 =>  4,
    C1 =>  7,
    #...(fill this up with the widths of your 38 record types)
);

# Make a regex of record types; sort with longest first as appropriate for
# ... regex alternation:
my $record_type_regex = join '|', sort { length($b) <=> length($a) } keys %field_widths; 

my %records;
my $marker_length=7; #Assuming the marker is 7 characters long
while(<>){
    chomp;
    while( # Parse each line of input
      m!
        (.{$marker_length})          # Match the record marker (save in $1)
        ($record_type_regex)         # Match any record type (save in $2)
        (
         (??{'.'x$field_widths{$2})} # Match a field of correct width
        )                            # Save in $3
       !xg){
        $records{$2}.="$1$2$3\n";
      }
}
for my $file (sort keys %records){
    open my $OUT,'>',$file or die "Failed to open $file for writing: $!\n";
    print $OUT $records{$file};
    close $OUT
}

Appelez-le comme:

[user@host]$ ./myscript.pl file_of_data

Code testé et fonctionne avec votre entrée donnée.

Mise à jour

Dans vos commentaires, vous avez demandé un "équivalent Unix" de ce qui précède. Je doute fortement qu'il existe une telle chose, car l'expression Perl utilisée pour analyser votre ligne est une expression très irrégulière et je doute que les expressions régulières vanille puissent analyser votre format de données donné: il est trop similaire à un type d'expression célèbre que l'expression régulière peut 't parse (correspond à n'importe quel nombre de a' suivi du même nombre de b').

Dans tous les cas, l'approche "Unix" la plus proche que je puisse trouver est la généralisation de la réponse de 1_CR . Vous devez noter que cette approche est spécifique à l'implémentation GNU grepet ne fonctionnera donc pas sur la plupart des Unices. L'approche Perl, au contraire, devrait fonctionner de la même manière sur n'importe quelle plate-forme sur laquelle Perl fonctionne. Voici mon grepapproche GNU suggérée :

cat <<EOF \
| while read -r record width;do
    grep -oE ".{7}$record.{$width}" input_file\ #replace 7 with marker length
     >> "$record"
done
A1 10
B1 4
# enter your 38 record types
EOF

Mise à jour

Sur la base des demandes de l'OP dans les commentaires, au lieu de passer le nom de fichier comme argument de ligne de commande, il peut être ouvert dans le script comme suit:

open my $IN,'<',$input_file_name or die "Failed to open $input_file: $!\n";
while(<$IN>){ #instead of while(<>)
...

Cela suppose que vous avez déclaré que la variable $input_file_namecontient, eh bien, le nom du fichier d'entrée.

Quant à l'ajout d'un horodatage au nom du fichier de sortie, vous pouvez utiliser la qx{}syntaxe: entre les accolades, vous pouvez mettre n'importe quelle commande Unix que vous souhaitez et elle sera exécutée et sa sortie standard relue à la place de l' qx{}opérateur:

open my $OUT,'>',"$file_".qx{date +%Y-%m-%d--%I:%M:%S%P}

L' qxopérateur n'est pas limité aux accolades, utilisez votre caractère préféré comme délimiteur, assurez-vous simplement qu'il n'est pas dans la commande que vous devez exécuter:

qx<...>
qx(...)    
qx!...!    
qx@...@

etc...

Dans certains codes Perl, vous pouvez voir des backticks ( ` `) utilisés à la place pour cette fonction, de la même manière que le shell. qxConsidérez simplement l' opérateur comme la généralisation des backticks à n'importe quel délimiteur.

Soit dit en passant, cela donnera un horodatage légèrement différent à chaque fichier (si la différence de leurs temps de création se trouve être un nombre fini de secondes). Si vous ne le souhaitez pas, vous pouvez le faire en deux étapes:

my $tstamp = qx{...};
open my $OUT,'>',"$file_$tstamp" or die...;

Salut encore .... commence à vraiment aimer perl. Ayez juste quelques morceaux niggly. 1 . Comment lire dans le fichier au lieu de passer l'argument de ligne de commande. Essayer mais échouer d'utiliser la configuration d'exécution Eclipse. 2 . Comment ajouter du texte au fichier de sortie $ file. Le plus apprécié.
jags

@jags Bienvenue au club :). Réponse mise à jour. Voyez si cela aide.
Joseph R.

Merci Joseph. Cependant, pour la dernière demande, je voulais vraiment ajouter, par exemple, la date / l'horodatage au nom du fichier de sortie. Le code actuel génère les fichiers A1, B1 et C1. Merci encore.
jags

@jags que je vois. Veuillez voir si la mise à jour aide.
Joseph R.

Merci comme toujours Joseph. Cependant, je voulais ajouter au nom de fichier de sortie réel qui dans ce cas est actuellement A1, B1, C1, c'est-à-dire que je veux ajouter une date / horodatage, A1_ <todays_date>, B1_ <todays_date>, C1_ <todays_date>. Merci beaucoup.
jags
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.