Contenu | Rechercher | Menus

Annonce

Si vous avez des soucis pour rester connecté, déconnectez-vous puis reconnectez-vous depuis ce lien en cochant la case
Me connecter automatiquement lors de mes prochaines visites.

À propos de l'équipe du forum.

#1 Le 29/10/2018, à 07:35

csc

[Résolu] Script découpage fichier texte formaté

Bonjour,
voila notre contexte et tout à la fin le problème à résoudre.

On a une base de données (SQLite) sécurisée qui contient des informations sur des comptes Web externes, des fichiers cryptés Gpg, etc.
On a un fichier texte (export de table de cette base) qui a le format suivant:

Chaque enregistrement contient plusieurs lignes (séparées par \n) avec le nom du champ en début de première ligne,
en majuscules, suivi de ': ' (caractère 2 points avec 1 espace ensuite) puis le contenu du champ avec un \n à la fin de
chaque champ. Les champs peuvent être vides ou contenir une ou plusieurs lignes (séparées par '\n').
A la fin de l'enregistrement on a une ligne qui contient le séparateur '===\n'  (3 signes égal suivi de \n).
Pour éviter les erreurs, la saisie (faite dans le Sgbd) interdit de saisir des données identiques aux noms de champs
utilisés lors de l'export; cette vérification est aussi faite lors de l'export.
On a les éléments suivants pour chaque champ exporté suivi du contenu du champ:
'ID: '
'ADRESSE DE: '
'SITE: '
'USER: '
'PASSWORD: '
'REMARQUES: '
'==='

Exemple (espaces et texte entre parenthèses sont des explications):

ID: 54   (numérique)
ADRESSE DE: Compte Gmail du secrétariat (du texte qui permet d'identifier ou de rechercher cet enregistrement)
SITE: https://accounts.google.com/     (texte qui peut contenir ou pas une URI)
USER: unnomdutilisateur   (texte qui peut contenir ou pas un nom ou une adresse email)
PASSWORD: motdepasseintrouvable   (texte qui peut contenir ou pas un mot de passe)
REMARQUES: changer le mot de passe tous les 3 mois, svp   (texte contenant ou pas des remarques pour cet enregistrement)
===        (le séparateur d'enregistrements)

Puis viennent les autres enregistrements avec la même syntaxe que précédemment.
Tous les champs (sauf ID: ) peuvent ëtre vides, ou contenir une ou plusieurs lignes mais pas tous vides en même temps !

Le problème (enfin !):
Ce fichier texte fait actuellement environ 50 Ko.
Il est utilisé (crypté) sur les ordinateurs n'ayant pas de Viewer SQL.
Par contre certaines machines (systèmes embarqués) ont une limite de taille pour les fichiers texte de 5 Ko.
On voudrait pouvoir découper ce fichier texte avec les contraintes:
- 4,9 Ko taille maxi,
- coupure en fin de chaque fichier sur le séparateur et pas en plein milieu d'un enregistrement comme c'est le cas parfois
  actuellement en faisant un simple découpage avec la commande linux  'split'.

On sait évidemment le faire en programme langage 'C' ou 'Python', etc.
Mais on veut utiliser un shell script (bash) avec les seules commandes du shell ou du système (ni perl, ni php, etc.).
Seulement split, sed, awk, cut, découpage de chaine, etc.

Avez-vous une solution à proposer ?

Bonne journée,
Csc

Dernière modification par csc (Le 05/11/2018, à 08:26)

Hors ligne

#2 Le 29/10/2018, à 13:03

Hizoka

Re : [Résolu] Script découpage fichier texte formaté

Salut, finalement le problème n'est que celui de la découpe du fichier ?
Si c'est ça, perso je ferais ça :

x=1
while read Ligne
do
  # Envoi de la ligne dans le nouveau fichier txt
  echo "${Ligne}" >> "fichier_export_${x}.txt"

  # Si on traite une ligne de fin
  if [[ ${Ligne} == "===" ]]
  then
    # SI le fichier atteint un certain poids, on incrémente x pour changer de fichier
    [[ $(wc -c < "fichier_export_${x}.txt") -gt 4500 ]] && ((x++))
  fi
done < fichier_export

Que du bash sauf pour la taille du fichier ou j'utilise du.

EDIT : Simplifié après conseille de pingouinux

Dernière modification par Hizoka (Le 29/10/2018, à 14:16)


KDE Neon 64bits
Tous mes softs (MKVExtractorQt, HizoSelect, HizoProgress, Qtesseract, Keneric, Services menus...) sont sur github

Hors ligne

#3 Le 29/10/2018, à 13:15

pingouinux

Re : [Résolu] Script découpage fichier texte formaté

Bonjour,
@Hizoka :
Pour obtenir directement la taille d'un fichier, tu as aussi cette méthode

Taille=$(wc -c fichier|cut -d\  -f1)

Édité :
Ou mieux

Taille=$(wc -c <fichier)

Dernière modification par pingouinux (Le 29/10/2018, à 13:34)

Hors ligne

#4 Le 29/10/2018, à 14:14

Hizoka

Re : [Résolu] Script découpage fichier texte formaté

Taille=$(wc -c fichier|cut -d\  -f1)

Oui je sais mais j'essaie de limiter les pipe et les commandes externes smile

Taille=$(wc -c <fichier)

Par contre, je ne savais pas que wc permettait ça et le coup du < est pas mal smile
Merci wink


KDE Neon 64bits
Tous mes softs (MKVExtractorQt, HizoSelect, HizoProgress, Qtesseract, Keneric, Services menus...) sont sur github

Hors ligne

#5 Le 30/10/2018, à 15:51

Postmortem

Re : [Résolu] Script découpage fichier texte formaté

Salut,
En version full gawk :

LC_ALL=C gawk '
   BEGIN {
      RS="\n===\n"
      taille_RS=length(RS)
   }
   {
      taille+=length + taille_RS
      if(taille > 4900) {
         close("fic_sortie_" i ".txt")
         i++
         taille=length + taille_RS
      }
      print $0 RT >"fic_sortie_" i ".txt"
   }
' 'i=1' 'ORS=' fic_entree.txt

Edit :
Remplacement de print $0 (RT?RT:"") par print $0 RT (c'était n'importe quoi !)

Re-édit : petite modif suite à la remarque Watael

Dernière modification par Postmortem (Le 31/10/2018, à 01:22)


Mot' a dit : « Un Hellfest sans Slayer, c'est comme une galette-saucisse sans saucisse ! »

Hors ligne

#6 Le 30/10/2018, à 17:37

Watael

Re : [Résolu] Script découpage fichier texte formaté

pourquoi length +5 ?

Dernière modification par Watael (Le 30/10/2018, à 17:39)


Connected \o/
Welcome to sHell. · eval is evil.

Hors ligne

#7 Le 30/10/2018, à 17:40

Postmortem

Re : [Résolu] Script découpage fichier texte formaté

Watael a écrit :

pourquoi length +5 ?

length, c'est length($0) ; et dans $0, il n'y a pas le séparateur d'enregistrement (qui a une longueur de 5)


Mot' a dit : « Un Hellfest sans Slayer, c'est comme une galette-saucisse sans saucisse ! »

Hors ligne

#8 Le 30/10/2018, à 23:42

Watael

Re : [Résolu] Script découpage fichier texte formaté

ma question portait sur le +5.
par souci de clarté et de maintenabilité, sans me soucier du coût, je préférerais lire length(RS), plutôt que de me poser la même question dans quelques mois.


Connected \o/
Welcome to sHell. · eval is evil.

Hors ligne

#9 Le 31/10/2018, à 01:23

Postmortem

Re : [Résolu] Script découpage fichier texte formaté

Watael a écrit :

ma question portait sur le +5.
par souci de clarté et de maintenabilité, sans me soucier du coût, je préférerais lire length(RS), plutôt que de me poser la même question dans quelques mois.

T'as raison, j'ai modifié.


Mot' a dit : « Un Hellfest sans Slayer, c'est comme une galette-saucisse sans saucisse ! »

Hors ligne

#10 Le 31/10/2018, à 07:14

csc

Re : [Résolu] Script découpage fichier texte formaté

Bonjour Isoka (et Postmortem),
merci d'avoir pris le temps de me répondre,
malheureusement j'ai identifié 2 bugs dans ta solution:

Premier bug:
Le fichier d'entrée est lu ligne par ligne et écrit ligne par ligne AVANT de tester si la taille limite est atteinte.
Le test de dépassement devrait se faire sur les enregistrements complet avant d'écrire.
Exemple:
imagine une taille limite de 4500 octets et un fichier d'entrée de 4800 octets et n enregistrements (séparateur "===").
Si les n-2 premiers enregistrements font 4400 octets ton script continue d'écrire dans le même fichier (4400<4500).
Imagine que l'enregistrement suivant fasse 600 octets, ton script écrit cet enregistrement puis teste si maintenant les n-1 enregistrements font plus de 4500 octets.
Le test est "OUI", ton script passe à l'écriture dans le fichier suivant pour les enregistrements n et suivants.
MAIS ton premier fichier fera 4400+600=5000 octets ce qui est plus que le max de 4500 !

Deuxième bug:
quand le contenu d'un champs est vide, on a donc par exemple
"USER: "
l'espace à la fin fait partie, pour nous, de l'identifiant d'un nom de champ (tests dans d'autres programmes).
Ton script enlève l'espace en fin de ligne.
Je suppose que c'est le 'read' du fichier d'entrée ou le "write" dans le fichier de sortie mais le contenu est donc modifié ce qui n'est pas possible.

Désolé,
Bonne journée

Hors ligne

#11 Le 31/10/2018, à 08:45

Postmortem

Re : [Résolu] Script découpage fichier texte formaté

Pour pas perdre l'espace, remplacer :

while read Ligne

Par :

while IFS= read Ligne

Pour le contrôle, peut-être qu'en cherchant un peu, tu devrais t'en sortir, non ?

Dernière modification par Postmortem (Le 31/10/2018, à 08:46)


Mot' a dit : « Un Hellfest sans Slayer, c'est comme une galette-saucisse sans saucisse ! »

Hors ligne

#12 Le 31/10/2018, à 09:51

Hizoka

Re : [Résolu] Script découpage fichier texte formaté

Premier bug:
Le fichier d'entrée est lu ligne par ligne et écrit ligne par ligne AVANT de tester si la taille limite est atteinte.
Le test de dépassement devrait se faire sur les enregistrements complet avant d'écrire.

En effet, pour ça que j'ai mis 4500 pour laisser de la marge.
Mais il est possible de passer par une variable temporaire :

x=1
texte=()
while IFS= read Ligne
do
  # Envoi de la ligne dans le nouveau fichier txt
  texte+=("${Ligne}")

  # Si on traite une ligne de fin
  if [[ ${Ligne} == "===" ]]
  then
    # Calcul la taille que représente la liste, le fichier puis le total
    taille1=$(wc -c <<< "${texte[@]}")
    taille2=$(wc -c 2>/dev/null < "fichier_export_${x}.txt")
    tailletotale=$((${taille1} + ${taille2:-0}))

    # Si la taille totale dépasse, on incrémente x et donc le nom du fichier
    [[ ${tailletotale} -gt 4900 ]] && ((x++))

    # Envoi des lignes dans le fichier
    for SubLigne in "${texte[@]}"
    do
       echo "${SubLigne}" >> "fichier_export_${x}.txt"
    done

    # Réinitialisation de la liste
    texte=()
  fi
done < fichier_export

Ça te va ?

Deuxième bug:
quand le contenu d'un champs est vide, on a donc par exemple
"USER: "
l'espace à la fin fait partie, pour nous, de l'identifiant d'un nom de champ (tests dans d'autres programmes)

utilisation de IFS= comme proposé par Postmortem.


KDE Neon 64bits
Tous mes softs (MKVExtractorQt, HizoSelect, HizoProgress, Qtesseract, Keneric, Services menus...) sont sur github

Hors ligne

#13 Le 31/10/2018, à 11:34

Postmortem

Re : [Résolu] Script découpage fichier texte formaté

En version "awk classique" (mais c'est moins classe !) :

LC_ALL=C awk '
    BEGIN { i=1 }
    {
        enreg=enreg sep $0
        sep="\n"
    }
    /^===$/ {
        taille_enreg=length(enreg) + 1 # + 1 pour le \n ajouté par print
        taille_fic=taille_fic + taille_enreg
        if (taille_fic > 4900) {
            close("fic_sortie_" i ".txt")
            i++
            taille_fic=taille_enreg
        }
        print enreg >"fic_sortie_" i ".txt"
        enreg=""
        sep=""
    }
' fic_entree.txt

Mais niveau temps d'exécution, comme gawk, ça explose le bash ! tongue

Dernière modification par Postmortem (Le 31/10/2018, à 11:35)


Mot' a dit : « Un Hellfest sans Slayer, c'est comme une galette-saucisse sans saucisse ! »

Hors ligne

#14 Le 05/11/2018, à 08:24

csc

Re : [Résolu] Script découpage fichier texte formaté

Bonjour et merci Hizoka et Postmortem,
vos solutions semblent Ok.
Après la recommandation de Postmortem j'ai essayé d'écrire à nouveau mon propre script en partant de zéro.
Pourquoi j'avais fait cette demande d'aide ?
Le dernier script Unix (traduire de nos jours par Linux si vous voulez) que j'ai écrit c'était en 1983 sur Unix Berkley et Bourne shell. Quand j'ai vu à quel point la syntaxe avait changé, ça m'a un peu g...é
Accolades ou pas crochets simples ou double ou pas, espace avant, après ou pas, # ou pas, double quote ou pas, $ ou pas, etc.
Cela ressemble diablement à de la sédimentation ?
Ce script que j'ai écrit marche mais est malpropre. Chaque fois que j'ai eu un problème (comparaisons, affectation, etc.) je suis allé sur internet chercher une solution. Il est donc parfaitement possible
que pour faire 2 fois la même chose, j'ai utilisé 2 syntaxes différentes, Horreur !
Je vous le livre quand même mais pitié, lisez le en fermant les yeux.

#!/bin/bash
x=1
Taillemax=4900
Enreg=""
Tailleenreg=0
Enregtmp=""
Tailleenregtmp=0
Taillepossible=0
Separateur="==="$'\n'

while IFS= read Ligne
do
   Ligne="$Ligne"$'\n'
   Enregtmp="${Enregtmp}${Ligne}"
   let Tailleenregtmp=${#Enregtmp}
   let Tailleenreg=${#Enreg}
   let Taillepossible=Tailleenreg+Tailleenregtmp    # taille des enreg en cours + enreg actuel lu
   if [ "${Ligne}" = "$Separateur" ]
        then
        if [ $Taillepossible -eq $Taillemax ]  # si enreg en cours+tmpenreg == taillemax on écrit Enreg+Enregtmp
            then
            echo "${Enreg}${Enregtmp}" >> "fic_sortie_${x}.txt"
            let x++
            unset Enreg
            unset Enregtmp
         fi
        if [ $Taillepossible -gt $Taillemax ]  # si enregs en cours+tmpenreg>Max alors on écrit Enreg seul
            then
            echo "${Enreg}" >> "fic_sortie_${x}.txt"
            let x++
            Enreg="${Enregtmp}"
            unset Enregtmp
         fi
        if [ $Taillepossible -lt $Taillemax ]   # si taille max non atteinte on continue de lire
            then
            Enreg="${Enreg}${Enregtmp}"
            unset Enregtmp
         fi
        fi
done < fic_entree.txt
#
# fin du fichier d'entrée atteinte
if test -n "$Enreg"           # mais Enreg était trop petit pour être écrit
then
       echo "${Enreg}" >> "fic_sortie_${x}.txt"
fi
# s'il y a des données dans Enregtmp c'est que Enreg était déjà suffisant seul pour être juste sous ou égal à Taillemax
# il faut donc créer un nouveau fichier (d'où le x++) pour ce qui peut traîner dans Enregtmp...
if test -n "$Enregtmp"
then
       let x++
       echo "${Enregtmp}" >> "fic_sortie_${x}.txt"
fi

J'espère que l'utilisation du Bbcode est correcte.
Avancée dans l'inconnu one more time.
Sujet résolu donc,
Bonne journée

Hors ligne

#15 Le 05/11/2018, à 23:53

Postmortem

Re : [Résolu] Script découpage fichier texte formaté

Salut,
Petite remarque ; ${#Enreg} ne donne pas la taille d'un enregistrement mais le nombre de caractères d'un enregistrement.
C'est pour cela que j'avais préfixé les commandes (g)awk de « LC_ALL=C ».

$ var=é
$ echo "${#var}"
1
$ LC_ALL=C echo "${#var}"
1
$ awk -v "var=$var" 'BEGIN {print length(var)}'
1
$ LC_ALL=C awk -v "var=$var" 'BEGIN {print length(var)}'
2

Mot' a dit : « Un Hellfest sans Slayer, c'est comme une galette-saucisse sans saucisse ! »

Hors ligne