demande d'aide pour traitement de fichier en ligne de commande

demande d'aide pour traitement de fichier en ligne de commande - Codes et scripts - Linux et OS Alternatifs

Marsh Posté le 10-02-2004 à 15:21:40    

[edit : deplacé dans bash/shell]
 
Salut les kids,
 
J'ai des fichiers générés par un programme java qui sont du type suivant :  
#x f(x) H(x) E(x) phi(x)
0.0 62.0 -156.4565455321601 28322.65063985689 -1.1935042678449233
1.0 62.0 -147.1271142670323 25490.387752544382 -1.1719751993203753
2.0 63.0 -137.7261148483831 22937.48271123001 -1.141781157238644
3.0 64.0 -131.35014165628616 21348.85971312644 -1.1174028387957113
 
En gros 5 colonnes séparées par des tabs. Le nom des fichiers respecte le schéma suivant :  
 
blablabla.t=i.txt , avec i = 5*n , 0<n<500
 
Je souhaite effectuer le script suivant en shell :
 
1) prendre le fichier 1 et faire les op. suivantes
-virer la premiere ligne
-recuperer la 5e colonne (cut -f5)
 
2) faire la meme chose avec les autres fichiers
 
3) concatener toutes les colonnes dans un meme fichier
 
4) eventuellement : ajouter un increment a chaque colonne en fonction du nom, en gros colonne := colonne + 3*n par exemple
 
et comme je ne gere pas trop le shell je chiale et demande votre aide avisée =). Si vous vous demandez a quoi va servir ce truc, c'est pour analyser l'evolution de la phase ( graphiquement )dans un systeme dynamique. merki linux =)
 
Merci pour votre aide,
 
a+
joa


Message édité par enfoiro le 10-02-2004 à 15:27:23
Reply

Marsh Posté le 10-02-2004 à 15:21:40   

Reply

Marsh Posté le 10-02-2004 à 15:23:59    

cat et cut suffiront, man cat et man cut
 
edit : ce topic a plus sa place dans programmation [bash/shell]


Message édité par black_lord le 10-02-2004 à 15:24:48
Reply

Marsh Posté le 10-02-2004 à 15:32:47    

Oui j'ai compris que cela suffit mais je suis un n0Ob du shell donc je ne sais pas comment on fait par exemple pour (pseudo code)
 
for i=1 to 500 do
selectionne fichier.i.txt
vire la 1e ligne et cut -f5 > fichier.temp.i.txt
end
concatene tout
 
donc si tu veux pas m'expliquer cat et cut, ce que je comprends, explique moi juste comment on fait pour l'expression reguliere du fichier a selectionner. Puis je pense pas que cut suffit pour ajouter un nombre different a chaque colonne non ??
 
g déplacé mon msg
 
merci
joa

Reply

Marsh Posté le 10-02-2004 à 15:35:13    

heu pas testé mais ça doit être un truc du genre :
 


#!/bin/bash  
 
 
if [ -e toto ]; then rm toto; fi
 
for (( i = 1; i < 500; i++))
do
  for j in $(tail -n+2 blablabla.t=$((i*5)).txt | cut -f5)
  echo $(bc "$j + 3 * $i" ) >> toto
done


Message édité par minusplus le 10-02-2004 à 15:35:23
Reply

Marsh Posté le 10-02-2004 à 15:50:36    

Merci pour ces indications, je vous tiens au courant
 
++
joa

Reply

Marsh Posté le 10-02-2004 à 15:59:47    

Non testé également (je travaille avec ksh et cette syntaxe de for n'est pas acceptée), avec en plus la concaténation des colonnes.
 


for ((for (( i = 1; i < 500; i++))  
do  
   file=blablabla.$i.txt
   temp=$(printf "blablabla.%03d.tmp" $i)
   awk 'NR>1 {print $5+3*Indice} ' Indice=$i $file > $temp
done
paste blablabla.*.tmp
rm blablabla.*.tmp


---------------
Jean Pierre.
Reply

Marsh Posté le 10-02-2004 à 16:01:01    

Désolé, il manque la redirection dans un fichier du résultat de la concaténation.


paste blablabla.*.tmp >blablabla.txt


---------------
Jean Pierre.
Reply

Marsh Posté le 11-02-2004 à 14:01:25    

Re
 
Comme j'ai envie de bien comprendre tout ce que je fais, j'ai pondu le script suivant. Merci pour vos idées ca m'a bien aidé. Pour ce qui est de l'utilisation de awk je pense qu'il va falloir que je m'y mette car ca a l'air puissant mais le choix que j'ai fait ici est celui du noobie qui se lance. Voici le script que j'ai pondu et pas encore testé. Je pense qu'il est truffé d'erreurs, si vous en voyez n'hésitez pas !
 
note : le format du nom des fichiers a traiter est le suivant
 
hilbert_1Hz-1.5cm-d-110-t-3440.txt
 
t est le temps de prise du fichier par exemple toutes les 5 secondes
t-3440
t-3445
etc...
 
et leur forme
 
#x  f(x)  H(x)  E(x)  phi(x)
0.0 62.0 -156.4565455321601 28322.65063985689 -1.1935042678449233
1.0 62.0 -147.1271142670323 25490.387752544382 -1.1719751993203753
2.0 63.0 -137.7261148483831 22937.48271123001 -1.141781157238644
3.0 64.0 -131.35014165628616 21348.85971312644 -1.1174028387957113
4.0 65.0 -126.39789649537009 20201.42823845429 -1.0958145549864549
5.0 65.0 -122.40878439575893 19208.910497247394 -1.0826514078742862
6.0 66.0 -119.05407390157093 18529.872512560712 -1.064604266389183
7.0 67.0 -115.94369917321349 17931.941377968626 -1.0468103329812561
8.0 68.0 -113.01822763984633 17397.119778852124 -1.0291476918256324
9.0 67.0 -110.55198153912566 16710.740622227182 -1.0259403562253213
10.0 68.0 -108.40197776680596 16374.988783755092 -1.0105484997877925
11.0 70.0 -106.06629242009235 16150.05838774454 -0.9874479673917881
12.0 70.0 -103.59423260071551 15631.765028131149 -0.9765567972147771
13.0 70.0 -101.27546866825138 15156.720553973966 -0.966011568716129
14.0 71.0 -98.99332036628844 14840.677477142619 -0.9486064912486759
 
mal paginé mais c tab-séparé
 
t-$temps
 
Extr_Phi nom_de_base tps_debut intervalle colonne_a_extraire decalage_graphique
 
#!/bin/bash
 
basename=$1
tps_debut=$2
time_int=$3
num_col=$4
graph_decal=$5
 
nb_fichiers=`ls | grep -o "^$1.*txt | wc -l`
 
t_max= $tps_debut + $nb_fichiers * $time_int # utile
 
dest_file=`resul_ ($basename) _t= ($start_time) _to_ ($t_max) _dec= ($graph_decal) # verbose file name : je sais pas si je suis bon sur la forme de l'expression
 
touch $dest_file
past -d'\t' $dest_file `cut -f1 "$basename $start_time '.txt' ` > $dest_file # on ajoute une premiere colonne recuperee ds le premier fichier
 
for i in `seq $nb_fichiers` /* seq genere une liste de 1 a nb_fichiers
do
file='$basename >> ($start_time) + $i * ($time_int) '.txt'
past -d'\t' $dest_file `cut -f($num_col) $file` > $dest_file # surement des pb syntaxe
done
sed '1/d' $dest_file > $dest_file # supprimer premiere ligne

Reply

Marsh Posté le 11-02-2004 à 16:15:56    

Quelques commentaires ...  
Je n'ai pas tout testé, à toi de jouer  :bounce: ...
 
 
 

  • nb_fichiers=`ls | grep -o "^${basename}*.txt | wc -l`  

 
Je ne connais pas l'option -o de grep, elle n'existe pas sur mon système.
Il n'est pas necessaire d'utiliser 'wc -l' pour avoir le nombre de lignes, l'option
-c de grep fait ça trés bien :
 
nb_fichiers=`ls | grep -cE "^$1.*.txt`
 
Avec bash (et ksh) je préfére plus clair d'utiliser la syntaxe $(commande) plutot que `commande`. C'est plus clair et en plus  
 
tu peux imbriquer les $( ), utiliser des guillemets à l'intérieur même si tu es déja entre guillements :
 
msg="Nous sommes le $(date +"%d %B" )"
 

  • t_max= $tps_debut + $nb_fichiers * $time_int # utile  


Il faut peut être déclarer les variables en integer (declare/typeset -i) au préalable pour pouvoir
effectuer ce calcul (je ne dispose pas de bash, je travaille avec ksh pour lequel la déclaration est obligatoira).
 
Il me semble que le calcul de t_max n'est pas bon, tu comptes un intervalle de trop.
t-3440  t-3445  t-34450  => nb_fichiers=3, tps_debut=3440 (je pense), time_int=5
t_max=3440 + 3 * 5 = 3455 => Erreur.
 
t_max=$tps_debut + ( $nb_fichiers - 1 ) * $time_int
 

  • dest_file=`resul_ ($basename) _t= ($start_time) _to_ ($t_max) _dec= ($graph_decal)  


Il faut plus simplement faire :
 
dest_file=resul_${basename}_t_${start_time}_to_${t_max}_dec_${graph_decal}
 
Il faut obligatoirement utiliser la syntaxe ${basename} car sinon le shell essaie de substituer $basename_t_
 

  • past -d'\t' $dest_file `cut -f1 "$basename $start_time '.txt' ` > $dest_file # on ajoute une premiere colonne recuperee ds  


le premier fichier  
 
La commande est 'paste' et non past.
La commande paste demande en arguments les noms de fichiers.  
La commande cut retourne des valeurs qui vont être prises comme les noms des fichiers à regrouper.  
 
cut -f${num_col} ${basename}${start_time}.txt | paste $dest_file -
 
'paste' regroupe le contenu des fichiers $destfile et - cad stdin qui est lu dans le pipe alimenté par le 'cut'.
 

  • for i in `seq $nb_fichiers` /* seq genere une liste de 1 a nb_fichiers  


Pourquoi ne pas effectuer la boucle sur la valeur temps (dans ce cas le paste précédent est à supprimer)
 
for (( temps=start_time ; temps<=t_max; temps=temps+time_int ))
do
   paste -d'\t' $dest_file ${basename}${temps}
done
 

  • sed '1/d' $dest_file > $dest_file # supprimer premiere ligne  


Tu ne peux pas rediriger dans le même fichier, en final tu te retrouve avec un fichier vide.
 
sed '1/d' $dest_file > work_file
mv work_file $dest_file


---------------
Jean Pierre.
Reply

Marsh Posté le 12-02-2004 à 11:13:06    

Merci beaucoup pour ta patience aigles.
 
J'ai lu tes commentaires avec attention et tes remarques sont très pertinentes.
 
Je poste le script final quand il marchera, je l'espère aujourd'hui :-)
 
joa

Reply

Marsh Posté le 12-02-2004 à 11:13:06   

Reply

Marsh Posté le 13-02-2004 à 14:01:53    

Bon alors voila le script que j'ai pondu. Bien sur je dois maintenant rajouter des fonctions que je vais vous expliquer de suite.
Le script : finalement voyant que c'était très compliqué et pas très modulaire d'utiliser des paste et des cut à gogo je me suis rabattu sur awk en essayant de comprendre ce que je fais. Merci encore aigles pour tes remarques.
Voici le script :
 
#!/bin/bash
# Extr_Phi nom_de_base tps_debut intervalle colonne_a_extraire decalage_graphique
 
basename="$1"
tps_debut="$2"
time_int="$3"
num_col="$4"
graph_decal="$5"
 
echo "Nom de base :  $basename"
echo "temps de début : $tps_debut"
echo "intervalle de temps TRAITEMENT: $time_int"
echo "numéro de colonne a extraire : $num_col"
echo "décalage graphique : $graph_decal"
 
nb_fichiers=$(ls | grep -cE "^${basename}.*txt" )
 
echo "Nombre de fichiers de résultats TOTAL : $nb_fichiers"
 
t_max=$(ls | grep -o "^${basename}.*txt" | cut -d'=' -f3 | cut -d'.' -f1 | sort -rn | awk '(NR = 1) {printf ("%.0f\n",$1) }' -)
# ne marche pas...
 
# t_max=$(expr $tps_debut + \( $nb_fichiers - 1 \) \* $time_int) # utile mais maintenant faux
 
echo "t_max : $t_max"
 
dest_file=resul\_${basename}${tps_debut}\_to\_${t_max}\_dec\=${graph_decal}\.txt # verbose file name
 
echo "Fichier de destination des resultats : $dest_file"
 
# Si le fichier de sortie existe dans le répertoire courant il est supprimé.
if [ -e ${dest_file} ]; then rm ${dest_file}; fi
 
# on utilise awk pour recuperer la premiere colonne avec la premiere ligne
 
touch $dest_file
touch temp
awk '( NF == 5 ) { printf ("%.2f\n", $1 ) }' ${basename}${tps_debut}\.txt > temp  
mv temp $dest_file
 
# Excellente suggestion de aigles pour la boucle : avec cette astuce on peut choisir les fichiers a traiter
i=0
for ((temps=${tps_debut}; $temps <= ${t_max} ; temps = $temps + ${time_int}))
do
file=${basename}${temps}\.txt
echo "fichier en cours de traitement : $file"
awk '( NR != 1 ) && ( NF == 5 ) {printf ("%.16f\n", $num_col+decal*i) }' i=$i num_col=$num_col decal=$graph_decal $file | paste ${dest_file} - > temp
mv temp $dest_file
i=$(expr $i + 1)
done
 
# probleme de complexité : l'algorithme se ralentit avec l'augmentation du nombre de fichiers
# paste semble en cause
# peut etre a cause de l'execution sous cygwin
 
Bon alors voila. J'ai décidé pour plus de flexibilité de pouvoir choisir un intervalle de temps plus grand entre deux fichiers, ce qui revient à ne pas utiliser tous les fichiers de données.
Pour cela, je dois revoir la méthode d'obtention de $t_max.
Je cherche donc a réaliser une fonction qui récupère le temps dans le nom de fichier et prenne le plus grand pour le mettre dans la variable t_max. EDIT : pour ca g réussi avec tail.
La je manque d'expérience. De plus je voudrais finalement utiliser la premiere ligne pour étiqueter chaque colonne avec le temps de l'expérience. Je pense donc revenir sur le choix de ne pas prendre la 1e ligne de chaque colonne de données. Il est preferable de l'utiliser puis de la remplacer ensuite, ou a l'interieur de la boucle, par le temps de l'expérience.
De plus je voudrais ensuite mouliner ce fichier avec gnuplot pour faire des regressions linéaires mais c'est une autre histoire.
 
joa


Message édité par enfoiro le 13-02-2004 à 14:41:19
Reply

Marsh Posté le 13-02-2004 à 19:18:28    

A nouveau quelques commentaires sur la nouvelle version de ton script.
 
Il ne faut oublier qu'avec les outils dont on dispose sous Unix, les choses peuvent être effectuées de plusieurs manières. Chaque programmeur à ses outils préférés et son style de programmation.
 
Il ne faut pas hésiter à utiliser 'man' ou 'info'.
 
 

  • t_max=$(ls | grep -o "^${basename}.*txt" | cut -d'=' -f3 | cut -d'.' -f1 | sort -rn | awk '(NR = 1) {printf ("%.0f\n",$1) }' -)  

# ne marche pas...  
 
cut -d'=' -f3
A moins que le format des noms de fichier ai changé, '=' n'est pas présent dans le nom =>
cut -d'-' -f6
 
awk '(NR = 1) {printf ("%.0f\n",$1) }' -
Il y a beaucoup plus simple, 'head' permet de récupérer les lignes en début de fichier =>
head -1
 
 
A partir du moment où le préfixe du nom du fichier est un paramètre (basename), tu ne peux pas récupérer le temps comme tu le fais sinon tu imposes une structure à ce préfixe.
Les fichiers on pour nom $[basename}temps.txt, pour récupérer le temps il suffit d'éliminer les préfixe et le suffixe (.txt) :
sed -e 's/^'/${basename}//' -e 's/.txt$//'


t_max=$(ls | grep -o "^${basename}.*\.txt" | sed -e 's/^'/${basename}//' -e 's/.txt$//' | sort -rn | head -1)  


 
Une autre façon de faire est d'utiliser 'awk' pour remplacer grep+sed+sort+head :


t_max=$(  
   ls | awk '   # Analyse de la liste des fichiers
   $1 ~ "^" BASE "[0-9]+\\.txt" { #   Sélectionne fichiers BASEn.txt ou est n est numérique  
      gsub("^" BASE,"",$1); #     Supprime le préfixe  BASE
      sub(/\.txt$/,"" );  #     Supprime l'extension '.txt' en fin
      if ($1 > max) max=$1; #     Mémorise le plus grand temps
   }
   END {   #   Lorsque tout le fichier a été traité
      print max;  #     Affiche le plus grand temps
   } BASE=$Basename '           #   La variable BASE est initialisée pour awk
   )


 

  • dest_file=resul\_${basename}${tps_debut}\_to\_${t_max}\_dec\=${graph_decal}\.txt # verbose file name  

 
Dans la mesure ou tu utilise la syntaxe ${var}, les '\' sont inutiles :
 
dest_file=resul_${basename}${tps_debut}_to_${t_max}_dec=${graph_decal}.txt
ou
dest_file=resul\_$basename$tps_debut\_to\_$t_max\_dec=$graph_decal\.txt  
 

  • if [ -e ${dest_file} ]; then rm ${dest_file}; fi  

 
Ok, mais en plus simple :
rm -f ${dest_file}  
 

  • # on utilise awk pour récupérer la première colonne avec la première ligne  

 
touch $dest_file  
touch temp  
awk '( NF == 5 ) { printf ("%.2f\n", $1 ) }' ${basename}${tps_debut}\.txt > temp  
mv temp $dest_file  
 
Il suffit de prendre le premier champ de toutes les lignes du fichier avec 'cut' pour qui le séparateur des champ par défaut est la tabulation (ce qui corresponds à tes fichiers).


cut -f1 ${basename}${tps_debut}.txt > ${dest_file}


 
Si tu veux effectivement vérifier le nombre de champs des lignes récupérées, ton 'awk' fonctionne mais est à simplifier :
 
awk 'NF==5 {print $1}' ${basename}${tps_debut}\.txt > ${dest_file}
 
Attention, si tu veux blinder un peu ton script, il te faudra vérifier au préalable l'existence de ce fichier.
De manière générale, tous les paramètres passés doivent être contrôlés (existence fichier, numéricité...).
 

  • awk '( NR != 1 ) && ( NF == 5 ) {printf ("%.16f\n", $num_col+decal*i) }' i=$i num_col=$num_col decal=$graph_decal $file  


Attention ici tu ne sélectionnes pas la première ligne d'en-tête, alors que tu la prise en compte pour initialiser ton fichier ${dest_file}.
 

  • i=$(expr $i + 1)  


En bash (et ksh) il est possible de faire de l'arithmétique sans passer par expr :
(( i = i + ))
(( i += 1 ))
(( i++ ))
 
L'incrémentation de i pourrait même se faire au niveau du 'awk'
awk 'NF==5 {printf ("%.16f\n", $num_col+decal*i) }' i=$((i++)) num_col=$num_col decal=$graph_decal $file  
 
$((i++)) : Substitution de la variable i PUIS incrément de la variable.
A ne pas confondre avec $((++i)) qui incrémente la variable i AVANT de la substituer.
C'est le même comportement qu'en C.
 

  • l'algorithme se ralentit avec l'augmentation du nombre de fichiers  


Pour n fichiers en entrée, tu fait n 'paste' qui au total lisent 2n fichiers pour créer en créer n.
 
Il faudrait modifier l'algorithme pour diminuer le nombre de 'paste'.
Par exemple, dans la boucle 'for', pour chaque fichier entrée créer un fichier résultat et faire en final un ou plusieurs 'paste' (par tranche de fichiers) pour regrouper les fichiers.
 
Cygwin n'est pas très rapide (tout du moins sur mon PC qui n'est qu'un antique Pentium 160).
 
 
Bon courage pour la suite.
Pour ma part c'est une semaine de congés  :sol:


---------------
Jean Pierre.
Reply

Marsh Posté le 27-02-2004 à 15:15:14    

Salut a ts,
 
Merci pour ta réponse complète. Cependant, après trop de galeres (le shell c'est bien mais c'est relou tant que t'es pas un dieu) j'ai décidé de réorienter mon projet pour finalement coder en perl l'algo dont j'ai besoin. De plus, mon prog crée les gabarits gnuplot pour générer les graphiques tous beaux tous propres d'un coup d'un seul. Il fait les calculs tout seul aussi, merki perl. En tout cas je vais de ce pas imprimer ce topic qui me servira de référence la prochaine fois que j'aurais a me coller au shell. Merci pour ton aide aigles, j'ai vraiment apprécié ta patience ;-)
 
joa

Reply

Marsh Posté le 27-02-2004 à 15:29:51    

Il va falloir que je me mettes moi aussi à perl.
Le problème c'est que je suis chez un client et que personne ne l'utilise ici, alors laisser des scripts que personne ne peut maintenir c'est pas vraiment pro ...


---------------
Jean Pierre.
Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed