[Résolu] sed pour inverser les colonnes d'un dictionnaire

Bonsoir,

Je suis en train de mettre en forme un dictionnaire destiné à être utilisé avec stardict. J’ai déjà un fichier tabulé (fichier texte avec extension tab) dans le sens langue1–>langue2, composé d’un certain nombre de lignes sous la forme :
mot (ou expression) --tabulation–> traduction

Dans la deuxième partie de la ligne (zone traduction), il y a parfois plusieurs mots ou expressions, séparés pas une virgule… un mot dans une langue pouvant avoir plusieurs équivalents dans une autre.

Je voudrais réaliser un script bash permettant d’inverser intelligemment les deux colonnes. L’objectif est de pouvoir automatiquement créer l’autre sens du dictionnaire (langue2 --> langue 1)… sans avoir à tout reprendre manuellement… et de pouvoir renouveler cette création automatique de “langue2 --> langue1” chaque fois que je fais des corrections ou compléments sur “langue 1 --> langue 2.”

Il ne s’agit pas d’une simple inversion (ce serait trop facile :wink: )

Pour m’expliquer, je préfère donner un exemple.

N.B. : dans les exemples ci-dessous, j’ai visualisé une tabulation par “–>”

Dans le sens langue 1 --> langue 2, j’ai ça :

[quote]
viotu–> vide, creux
viotoru --> creux
virdura --> verdure
virticale --> vertical
visa --> viser
visà --> visa (de la police, du consulat)
visatu --> visa (de la police, du consulat)
visionà --> visionner
visita --> visite, inspection
visità --> visiter, inspecter, explorer
visiunà --> visionner [/quote]

Je voudrais obtenir à la fin ceci :

[quote]creux --> viotoru, viotu
explorer --> visità
inspecter --> visità
inspection --> visita
verdure --> virdura
vertical --> virticale
vide --> viotu
visa (de la police, du consulat) --> visà, visatu
viser --> visa
visionner --> visionà, visiunà
visite --> visita
visiter --> visità[/quote]

C’est à dire que dans la première colonne, il ne doit y avoir qu’un mot (ou expression) par ligne… et dans la seconde colonne, les diverses traductions de ce mot dans l’autre langue, séparées par une virgule.

Pour l’instant, en cherchant sur la toile, je suis arrivé à inverser les colonnes, et à reclasser par ordre alphabétique la première colonne, puis à séparer en plusieurs lignes lorsqu’il y a plusieurs mots séparés par une virgule dans la 1er champ…

Le script utilisé est le suivant :

[code]#!/bin/bash
TEMP=mktemp

$2
cat $1 | sed -e ‘s/(.)\t(.)/\2\t\1/’ > $TEMP
sort -t \n $TEMP > $2
rm $TEMP
cat $2 | sed ‘s/, /\t*\n/g’ > $TEMP
mv $TEMP $2[/code]

Et j’aboutis à ceci :

[quote]creux --> viotoru
verdure --> virdura
vertical --> virticale
vide --> *
creux --> viotu
visa (de la police ou du consulat) --> visà
visa (de la police ou du consulat) --> visatu
viser --> visa
visionner --> visionà
visionner --> visiunà
visite --> *
inspection --> visita
visiter --> *
inspecter --> *
explorer --> visità[/quote]

Lorsque le script place une astérisque dans le champ 2 (2ème colonne), c’est qu’il faut ensuite pouvoir remplacer l’astérisque par le mot (ou l’expression) du champ 2 de la ligne suivante.

Et c’est la que je bloque… Comment faire pour que, chaque fois qu’il y a une astérisque dans le champ 2 d’une ligne, le script fasse un “copier” de ce qui est dans le champ 2 de la ligne suivante et vienne le coller à la place de l’astérisque ? C’est à dire pour obtenir ceci dans l’exemple des lignes visite/inspection :

[quote]visite --> visita
inspection --> visita[/quote]

au lieu de

[quote]visite --> *
inspection --> visita[/quote]

Une fois ceci fait, un “sort -t \n” remettrait le tout par ordre alphabétique (pour la langue2, placée dans la 1ère colonne), et on approcherais du but.

Il resterait encore à réduire en une seule ligne les lignes successives dont le 1er champ est identique, comme par exemple :

[quote]visionner --> visionà
visionner --> visiunà [/quote]

… à transformer en

(et je bloque aussi sur cette dernière opération.)

Je suis un apprenti en bash… et ma connaissance des outils et options possibles est limitée… Il y a peut-être plus simple pour obtenir ce que je souhaite.

Voilà… Mais si on arrive à mettre au point ce script, ça rendra service pour les compléments et mises à jour de plein de dictionnaires disponibles pour stardict… gratuits, et libres…

bonjour,
j’ai essayé d’obtenir le résultat que tu veux avoir, mais je n’ai pas spécialement choisi sed pour manipuler le fichier,
parceque je ne le maitrise pas, de même que les expressions régulières.

Voilà une base de travail qui pourrait fonctionner, je n’ai que sommairement géré les bugs prévisibles sans parler de ceux que je n’ai même pas tenté d’imaginer (on espère que le fichier source est correctement formaté).
C’est donc à tester et à améliorer :

[code]
#!/bin/bash

------------------------------------------------------------------------------

les variables :

declare carsep=’–>’ # caractère séparateur clé/valeurs
declare workspace=/tmp # repertoire de travail
declare source=$workspace/example # le fichier à traiter
declare tmpnew=$workspace/newfic # fichier tmp
declare tmptri=$workspace/fictri # fichier trié
declare resultfic=$workspace/ficresult # le fichier de résultat
declare verbose=0

------------------------------------------------------------------------------

if [ -f $resultfic ] && [ $(cat $resultfic | wc -l) -ne 0 ]; then
echo ""
echo "Abandon, le fichier résultat $resultfic contient déjà des données."
echo "
"
exit 1
fi

[ $verbose == ‘0’ ] && {
echo "********************* fichier *********************"
cat $source
echo “***************************************************”
}

------------------------------------------------------------------------------

les fonctions:

# spérare clé et valeurs, envoie à setDefs

function splitLine(){
local def=$(echo ${1%$carsep*})
local defs=$(echo ${1#*$carsep})
setDefs “$def” “$defs”
}

# écrit dans un nouveau fichier tmp les définitions inversées

function setDefs(){
local tmp="$2"
local val="$1"
while [ ! -z “$tmp” ]; do
if [ “$(expr index “$tmp” ‘(’)” != “0” ] &&
[ “$(expr index “$tmp” ‘)’)” != “0” ]; then
v=$(echo “${tmp%)}")
tmp=$(echo "${tmp#
)}”)
echo “$v) --> $val” >> $tmpnew
else
if [ “$(expr index “$tmp” ‘,’)” != “0” ]; then
v=$(echo ${tmp%,})
tmp=$(echo ${tmp#
,})
echo “$v --> $val” >> $tmpnew
else
echo “$tmp --> $val” >> $tmpnew
tmp=""
fi
fi
done
}

# modifie les fichiers tmp et écrit dans le fichier de résultat

function triDefs(){
local def=$(echo ${1%$carsep*})
def="$(echo “$def” | tr -s ’ ')“
def=”$def $carsep"

filtre le fichier trié en récupérant les définitions pour une clé identique

local tmp="$(sed -e “s/$def//g” $tmptri | grep -v – “$carsep” | tr ‘\n’ ’ ')“
if [ “$tmp” != “” ]; then
tmp=”$(echo “$tmp” | tr -s ’ ')"
echo “$def$tmp” >> $resultfic
sed -i “/$def/d” $tmptri # supprime toutes les lignes contenant le motif traité
fi
}

------------------------------------------------------------------------------

script: boucle sur les fichiers, les taite.

------------------------------------------------------------------------------

while read line; do splitLine “$line” ; done<$source
sort -o $tmptri $tmpnew # tri et écrit dans $tmptri
while read line; do triDefs “$line” ; done<$tmptri
rm -f $tmpnew $tmptri # nettoyage

------------------------------------------------------------------------------

[ $verbose == ‘0’ ] && {
echo “********************* result "
cat $resultfic
echo "
*****************************”
}

exit 0[/code]

Qui nous donne ceci :

[code]
********************* fichier *********************
viotu–> vide, creux
viotoru --> creux
virdura --> verdure
virticale --> vertical
visa --> viser
visà --> visa (de la police, du consulat)
visatu --> visa (de la police, du consulat)
visionà --> visionner
visita --> visite, inspection
visità --> visiter, inspecter, explorer
visiunà --> visionner


********************* result *********************
creux --> viotoru viotu
explorer --> visità
inspecter --> visità visiter, visità
inspection --> visita
verdure --> virdura
vertical --> virticale
vide --> viotu
visa (de la police, du consulat) --> visà visatu
viser --> visa
visionner --> visionà visiunà
visite --> visita
**************************************************[/code]

je remarque qu’il y a une erreur à la troisième ligne de “result”, ça doit pas être grand chose, si j’ai le temps demain j’y jette un coup d’oei (si tu trouve ce que c’est tu le mettras ici sans doute).

En règle générale, je trouve que les caractères de type ‘-’ sont à banir en tant que caractère séparateur, ça pose plein de problème, car les commandes risquent de l’interpréter comme une option. De même, le fait de choisir ‘,’ comme car séparateurs des valeurs possibles dans une définition est pas terrible, dés lors que tu autorises ce caractère sans signification spécifique au même endroit , ex:
‘visa (de la police, du consulat)’=une valeur et ‘visiter, inspecter, explorer’=multiples valeurs.

Bonjour usinagaz, et merci pour ta réponse.

Une petite précision : le séparateur, dans ce tableau, est une vraie tabulation (\t) et non pas les caractères “–>”, que j’ai utilisé uniquement pour rendre visible cette tabulation sur le forum…

Mais je pense que si je remplace
declare carsep=’–>’ # caractère séparateur clé/valeurs
par
declare carsep=’\t’ # caractère séparateur clé/valeurs

ça doit marcher.
[Edit : ben non… il y a d’autres choses à modifier dans ton script pour que ça marche avec \t à la place de -->… j’ai essayé de remplacé simplement les occurences de “–>” par “\t”… mais ça ne suffit pas.]

Pour le reste, ok sur la remarque concernant les virgules. C’est pour ça qu’après un premier test, j’avais remplacé “visa (de la police, du consulat)” par “visa (de la police ou du consulat)”…
Les fichiers sources de ces dictionnaires sont ainsi faits que je ne vois pas quoi utiliser d’autre que la virgule pour distinguer les mots… Il faudrait dans ce cas pouvoir exclure les virgules situées dans une parenthèse…

Bon… je teste et je donne des nouvelles.
@+

ah okay …
non ça va surement poser problme ici le '\t’
alors le mieux c’est l’espace comme caractère séparateur ex:
toto tata titi tutu
titi tutu toto tata
où le premier mot est la clé, le reste les valeurs. Du coup avec awk, c’est bien.
ça autorise ça aussi :
toto tata (titi, tutu) si on retire la séquence parenthèses (incluse et son contenu) avant traitement par awk, et qu’on la récupère pour la traiter ensuite.
je regarderai demain peut-être …

Remarque si tu n’a que des mots simples, ça va vite:

[code]Fichier /tmp/l:
#!/bin/sh
MOT=$1
FICHIER=$2
echo $MOT “–>” grep -E "^.*-->.*$MOT" $FICHIER | sed -e 's/-->.*//' | awk 'BEGIN {chaine=""} {chaine=chaine", "$1} END {print chaine}' | sed -e '1,$s/^, //'

Fichier /tmp/t

#!/bin/sh
cp $1 /tmp/_FICHIER
sed -e ‘1,$s/.–>//’ /tmp/_FICHIER | sed -e ‘1,$s/,/\n/g’ | cat -s | sort -u | sed -e '1,$s|(^.$)|sh /tmp/l \1 /tmp/_FICHIER|’ | sh | sort -u

[/code]

Exemple sur

[quote]viotu–> vide, creux
viotoru --> creux
virdura --> verdure
virticale --> vertical
visa --> viser
vis --> visa
visatu --> visa
vision --> visionner
visita --> visite, inspection
visit --> visiter, inspecter, explorer
visiun --> visionner [/quote]

[quote]$ sh /tmp/t test
creux --> viotu, viotoru
explorer --> visità
inspecter --> visità
inspection --> visita
verdure --> virdura
vertical --> virticale
vide --> viotu
visa --> visà, visatu
viser --> visa
visionner --> visionà, visiunà
visiter --> visità
visite --> visita, visità
[/quote]

mais les parenthèses et autres , posent pbms…

Mais il n’y a pas que des mots simples dans ces dictionnaires. Il y a aussi des expressions. Autre exemple, en plus de “visa (de la police, du consulat)” :

“a vi discurrite? \t vous discutez?”

Comment fais tu la différence entre

A -> B, C

et

A -> visa (prefecture , police)

où tu peux poser A = visa (prefecture et B = police). Il te faut préciser tes séparateurs ou bien définir une syntaxe minimale sinon tu auras des pbms de ce genre. Précise bien cette syntaxe avant de faire ton script. En tout cas «,» ne convient pas comme séparateur. C’est juste l’aspect court du script qui me l’a fait poster, je vois bien que ça ne correspond pas à ton pbm…

@usinagaz
J’ai testé ta proposition, juste pour voir, en transformant au préalable mon fichier source par :

#!/bin/bash cat $1 | sed 's/\t/ --> /g' > $2
pour remplacer les tabulations par " --> "

J’obtiens bien le résultat que tu indiques… mais je n’ai pas trouvé la cause de l’erreur de la ligne 3.

@fran.b
Je ne sais pas comment faire la différence. C’est un de mes problèmes… J’ai vu que usinagaz parvenait à garder comme une seule valeur “visa (de la police, du consulat)” dans son script, mais je n’ai pas trop compris comment.

Pour la syntaxe, on peut en définir une pour la création de nouvelles listes de mots… mais certaines listes sont déjà faites par divers auteurs… on ne peut que les vérifier.

Une bête solution serait une vérification manuelle du second champ du fichier tabulé pour remplacer les trucs du style “visa (de la police, du consulat)” par “visa (de la police ou du consulat)”… c’est à dire pour faire en sorte que la virgule ne serve qu’à séparer des valeurs… mais quand il y a 10000 ou 40000 lignes dans un dico… :confused:
Ceci dit, dans le fichier que je suis en train de traiter… ce serait faisable; j’ai l’impression que ce type de lignes est très rare.

@usinagaz, fran.b (et les autres)

Une solution (partielle car elle ne résoud pas la question de la virgule) a été proposée sur le forum Ubuntu; elle utilise awk, comme le suggérait usinagaz :

[code]#!/bin/bash
TEMP=mktemp

$2

la ligne qui suit permet d’éliminer l’espace qui précède une tabulation (espace inutile à la fin du 1er champ, et gênant pour la suite du script).

cat $1 | sed ‘N;s/ \n/\n/g;P;D;’ > $TEMP
mv $TEMP $2

puis pour opérer la conversion :

cat $2 | awk -F"\t" ‘{split($2,tab,", “);for (i in tab) print tab[i]”\t"$1}’ |
sort -t \n |
awk -F"\t" ‘{if ($1 != key) {if (paspremier == “oui”) printf “\n”; printf $1"\t"$2;} else {printf “, “$2;} key = $1; paspremier=“oui”;} END {printf(”\n”);}’ > $TEMP
mv $TEMP $2
[/code]

… ça fonctionne pas mal…
Il ne reste semble-t-il que deux difficultés :

  • l’utilisation de “,” comme séparateur qui pose problème pour les trucs comme “visa (de la police, du consulat)”

  • le fait que le second champ soit pris en compte pour le classement par ordre alphabétique… ce qui place la ligne
    "visiter --> visità"
    avant la ligne :
    "visite --> visita, visità"
    comme dans le test fait par fran.b en utilisant ‘sort -u’

Si vous avez des idées pour solutionner ça… ou d’autres pistes.

Mon idée initiale avec sed n’était pas la meilleure… (et je prenais aussi la virgule comme séparateur)…

Tu peux transformer les , entre parenthèses par des @: Rapidement si tu as un maximum de 4 «,» entre des parenthèses:

[code]$ echo “a , b , (c , d , e) , e” | sed -e ‘1,$s/(([^)]),/\1@/g’ | sed -e '1,$s/(([^)]),/\1@/g’ | sed -e ‘1,$s/(([^)]),/\1@/g’ | sed -e '1,$s/(([^)]),/\1@/g’

a , b , (c @ d @ e) , e
[/code]
mais c’est une rustine (qui devrait marcher)

bonsoir,
je vais re-réfléchir à ce sur quoi on a mis le doigt …; mais, est-ce que bash est un langage fait pour gérer un dico ? ne serait ce pas mieux un accès à une base de donnée, et donc à php-cli ou en java surtout et carrément ?

Pour servir à la réflexion :

  • pour la création de nouveaux dicos, une base de donnée ou tout autre système imposant à l’auteur une syntaxe serait une bonne chose pour faciliter le traitement des données par la suite. Des applications open source existent peut-être pour ça, je ne sais pas.

  • pour des fichiers déjà existant, on n’a pas d’autre solution que de “faire au mieux”. Le script bash rend un grand service, mais il suppose une vérification manuelle préalable de la syntaxe. Pour exemple, dans le fichier que je viens de traiter, j’ai du opérer par une recherche sur les virgules dans le 2ème champ, et sur plus de 2000 entrées, j’en ai trouvé cinq “foireuses” pour l’usage de la virgule comme séparateur… ce ne sont pas toutes des virgules entre parenthèses, comme dans le cas déjà repéré… ce qui exclu me semble-t-il tout automatisme…

Voici ces cinq entrées

[quote]funa --\t–> corde en poil de chèvre, tressée et plate
nórchja --\t–> bûche noueuse, tordue, difficile à fendre
pannu --\t–> tissu de lin, de coton ou de laine
visà --\t–> visa (de la police, du consulat)
visatu --\t–> visa (de la police, du consulat) [/quote]

que j’ai modifiées comme suit :

[quote]funa --\t–> corde en poil de chèvre (tressée et plate)
nórchja --\t–> bûche noueuse (tordue et difficile à fendre)
pannu --\t–> tissu de lin (ou de coton ou de laine)
visà --\t–> visa (de la police ou du consulat)
visatu --\t–> visa (de la police ou du consulat)[/quote]

… avant application du script.

J’ai complété ce dernier par :

  • un remplacement des tabulations par “00000” avec sed
  • un classement alphabétique avec sort
  • un remplacement des “00000” par des tabulations avec sed

… ce qui permet d’obtenir un classement alphabétique correct au final (“visite” avant “visiter”)…

Le script a sorti le fichier dictionnaire dans l’autre sens de la langue de façon presque instantanée.

Voilà… mais tout ça peut sans doute être encore amélioré…
… et la réflexion se poursuivre !