1683 lines
		
	
	
		
			56 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
			
		
		
	
	
			1683 lines
		
	
	
		
			56 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
| ##@cooked comments # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
 | |
| ## fonctions pour awk
 | |
| ##@cooked nocomments
 | |
| ##@require base
 | |
| uprovide awk
 | |
| urequire base
 | |
| 
 | |
| __AWKDEF_HELP="\
 | |
| Les variables données en arguments sont définies dans une section BEGIN{}. Si
 | |
| une valeur ne ressemble pas à une définition de variable, l'analyse des
 | |
| variables s'arrête et le reste des arguments est inséré tel quel.
 | |
| 
 | |
| Normalement, les variables définies sont scalaires, avec une syntaxe de la forme
 | |
| name=value pour une chaine, ou name:int=value pour une valeur entière.
 | |
| Dans la forme name=value, si la valeur ne contient que des chiffres, alors elle
 | |
| est considérée comme entière.
 | |
| 
 | |
| Il est aussi possible d'utiliser la syntaxe awk_array[@]=bash_array pour
 | |
| initialiser le tableau awk_array, qui contiendra toute les valeurs du tableau
 | |
| nommé bash_array, avec les indices de 1 à N, N étant le nombre d'éléments du
 | |
| tableau bash_array. La variable awk_array_count est aussi initialisée, et
 | |
| contient le nombre d'éléments du tableau.
 | |
| La syntaxe simplifiée array[@] est équivalente à array[@]=array
 | |
| Il existe une autre syntaxe 'awk_array[@]=<' qui permet de spécifier les valeurs
 | |
| du tableau, une par ligne, e.g:
 | |
|     $'values[@]=<\nvalue1\nvalue2'
 | |
| pour un tableau values qui contiendra deux valeurs: value1 et value2
 | |
| 
 | |
| Les fonctions suivantes sont définies:
 | |
| 
 | |
| quote_value(s)
 | |
|     quoter une valeur pour le shell. la valeur est entourée de quotes, e.g:
 | |
|         quote_value(\"here, \\\"there\\\" and 'everywhere'.\")
 | |
|         --> 'here, \"there\" and '\\''everywhere'\\''.'
 | |
| 
 | |
| quoted_values()
 | |
|     quoter les valeurs \$1..\$NF pour les passer comme argument sur la ligne de
 | |
|     commande avec eval. e.g.:
 | |
|         print \"mycmd \" quoted_values()
 | |
|     La ligne qui est affichée pourra être évaluée avec eval dans le shell.
 | |
| 
 | |
| quote_subrepl(s)
 | |
|     quoter une valeur pour l'argument r des fonctions sub() et gsub(). Les
 | |
|     caractères suivants sont mis en échappement: \\ &
 | |
| 
 | |
| quote_grep(s)
 | |
|     quoter une valeur pour un pattern *simple* de grep. Les caractères suivants
 | |
|     sont mis en échappement: \\ . [ ^ \$ *
 | |
| 
 | |
| quote_egrep(s)
 | |
|     quoter une valeur pour un pattern *étendu* de grep. Les caractères suivants
 | |
|     sont mis en échappement: \\ . [ ^ \$ ? + * ( ) | {
 | |
| 
 | |
| quote_sql(s)
 | |
|     quoter une valeur pour un script sql. la valeur est entourée de quotes, e.g:
 | |
|         quote_sql(\"hello'there\")
 | |
|         --> 'hello''there'
 | |
| 
 | |
| unquote_mysqlcsv(s)
 | |
|     Analyser une valeur exportée de MySQL avec mysqlcsv. Les transformations
 | |
|     suivantes sont effectuées:
 | |
|         \\n   --> <newline>
 | |
|         \\t   --> <tab>
 | |
|         \\0   --> <caractère NUL>
 | |
|         \\\\  --> \\
 | |
| 
 | |
| mkindices(values, indices)
 | |
|     créer le tableau indices qui contient les indices du tableau values, de 1 à N,
 | |
|     et retourner la valeur N. Il faudra utiliser les valeurs de cette manière:
 | |
|         count = mkindices(values, indices)
 | |
|         for (i = 1; i <= count; i++) {
 | |
|             value = values[indices[i]]
 | |
|             ...
 | |
|         }
 | |
|     cette fonction nécessite gnuawk
 | |
| 
 | |
| array_new(dest)
 | |
|     créer un nouveau tableau vide dest
 | |
| 
 | |
| array_newsize(dest, size)
 | |
|     créer un nouveau tableau de taille size, rempli de chaines vides
 | |
| 
 | |
| array_copy(dest, src)
 | |
|     faire une copie d'un tableau. Cette fonction nécessite gnuawk, puisqu'elle
 | |
|     utilise mkindices().
 | |
| 
 | |
| array_getlastindex(src)
 | |
|     Retourner l'index du dernier élément du tableau src
 | |
| 
 | |
| array_add(dest, value)
 | |
|     Ajouter un élément dans dest, avec l'index array_getlastindex(dest)+1
 | |
| 
 | |
| array_deli(dest, i)
 | |
|     Supprimer l'élément d'index i dans le tableau dest.
 | |
|     Les index des éléments après i sont corrigés en conséquence.
 | |
|     Cette fonction assume que les indices du tableau commencent à 1 et n'ont pas
 | |
|     de \"trous\". Si i==0, cette fonction est un NOP.
 | |
| 
 | |
| array_del(dest, value[, ignoreCase])
 | |
|     Supprimer *tous* les éléments du tableau dest dont la valeur est value. Les
 | |
|     indexes des valeurs sont trouvées avec key_index(), puis les valeurs sont
 | |
|     supprimées avec array_deli()
 | |
|     ignoreCase permet de spécifier que la recherche de la valeur se fait en
 | |
|     ignorant la casse.
 | |
| 
 | |
| array_extend(dest, src)
 | |
|     Ajouter les éléments de src dans dest, en commençant avec l'index
 | |
|     array_getlastindex(dest)+1
 | |
| 
 | |
| array_fill(dest)
 | |
|     remplir le tableau avec \$1..\$NF
 | |
| 
 | |
| array_getline(src)
 | |
|     avec le tableau array contenant N éléments, initialise \$1..\$N avec les valeurs
 | |
|     du tableau
 | |
| 
 | |
| array_appendline(src)
 | |
|     avec le tableau array contenant N éléments, initialise \$(NF+1)..\$(NF+N) avec
 | |
|     les valeurs du tableau
 | |
| 
 | |
| in_array(value, values[, ignoreCase])
 | |
|     tester si le tableau values contient la valeur value, en ne tenant
 | |
|     éventuellement pas compte de la casse.
 | |
| 
 | |
| key_index(value, values[, ignoreCase])
 | |
|     trouver l'index de value dans le tableau values, en ne tenant éventuellement
 | |
|     pas compte de la casse. Retourner 0 si la valeur n'a pas été trouvée
 | |
| 
 | |
| array2s(values, prefix, sep, suffix, noindices)
 | |
|     convertir un tableau en chaine pour affichage. attention! les valeurs sont
 | |
|     affichés dans un ordre arbitraire. noindices, s'il vaut 1, supprime
 | |
|     l'affichage des indices du tableau. prefix (qui vaut par défaut \"[\") est
 | |
|     ajouté avant la chaine, suffix (qui vaut par défaut \"]\") après, et sep (qui
 | |
|     vaut par défaut \",\") est utilisé pour séparer chaque valeur.
 | |
| 
 | |
| array2so(values, prefix, sep, suffix, noindices)
 | |
|     convertir un tableau en chaine pour affichage. Les valeurs sont affichés dans
 | |
|     l'ordre du tableau. Cette fonction nécessite gnuawk, puisqu'elle utilise
 | |
|     mkindices(). noindices, s'il vaut 1, supprime l'affichage des indices du
 | |
|     tableau. prefix est ajouté avant la chaine, suffix après, et sep (qui vaut par
 | |
|     défaut \",\") est utilisé pour séparer chaque valeur.
 | |
| 
 | |
| array_join(values, sep, prefix, suffix)
 | |
|     convertir un tableau en chaine pour affichage. Les valeurs sont affichés dans
 | |
|     l'ordre du tableau. Cette fonction nécessite gnuawk, puisqu'elle utilise
 | |
|     mkindices().
 | |
|     Il n'y a pas de valeur par défaut pour sep, prefix et suffix.
 | |
| 
 | |
| printto(s, output)
 | |
|     en fonction de la valeur de output, afficher la chaine s sur la destination
 | |
|     spécifiée.
 | |
|     output est de la forme...            faire....
 | |
|         \"\"                               print s
 | |
|         \"dest\"                           print s >\"dest\"
 | |
|         \">dest\"                          print s >\"dest\"
 | |
|         \">>dest\"                         print s >>\"dest\"
 | |
|     XXX les formes suivantes sont désactivées pour le moment:
 | |
|         \"|dest\"                          print s |\"dest\"
 | |
|         \"|&dest\"                         print s |&\"dest\"
 | |
| 
 | |
| find_line(input, field, value)
 | |
|     retourner la première ligne du fichier input dont le champ \$field vaut
 | |
|     value. Retourner une chaine vide si la ligne n'a pas été trouvée.
 | |
| 
 | |
| merge_line(input, field, key)
 | |
|     équivaut à:
 | |
|         \$0 = \$0 FS find_line(input, field, \$key)
 | |
|     La ligne courante n'est pas modifiée si la ligne correspondante dans input
 | |
|     n'est pas trouvée.
 | |
| 
 | |
| array_parsecsv2(fields, line, nbfields, colsep, qchar, echar)
 | |
| array_parsecsv(fields, line, nbfields[, colsep, qchar, echar])
 | |
|     analyser une ligne au format csv, et initialiser le tableau fields aux valeurs
 | |
|     des champs trouvés. Si nbfields est spécifié, c'est le nombre de champs
 | |
|     minimum que doit avoir le tableau résultat. Le tableau est complété avec des
 | |
|     chaines vides au besoin. Le tableau commence à l'index 1.
 | |
|     Consulter array_formatcsv2 pour la signification de colsep, qchar et echar.
 | |
|     array_parsecsv(fields, line, nbfields) est équivalent à
 | |
|         array_parsecsv2(fields, line, nbfields, ",", "\"", "")
 | |
| 
 | |
| parsecsv(line)
 | |
|     équivaut à:
 | |
|         array_parsecsv(fields, line)
 | |
|         array_getline(fields)
 | |
| 
 | |
| getlinecsv(file)
 | |
|     obtient une ligne par getline, l'analyse, puis initialise \$1..\$N avec les
 | |
|     valeurs trouvées. equivaut à:
 | |
|         getline
 | |
|         parsecsv(\$0)
 | |
|     Si file est spécifié, utiliser 'getline <file' au lieu de 'getline'
 | |
| 
 | |
| array_formatcsv2(fields, colsep, mvsep, qchar, echar)
 | |
|     construit une ligne au format csv avec les colonnes du tableau fields.
 | |
|     - colsep est le séparateur des colonnes, e.g \",\"
 | |
|     - mvsep est le séparateur des valeurs des colonnes (si une colonne contient
 | |
|       plusieurs valeurs, par exemple si elle transporte la valeur d'un attribut
 | |
|       multivalué). Cette information ne sert que pour décider s'il faut encadrer
 | |
|       la colonne avec qchar.
 | |
|     - qchar est le caractère à employer pour encadrer la valeur d'une colonne
 | |
|       si elle contient l'un des caractères colsep ou mvsep
 | |
|     - echar est le caractère à employer pour mettre en echappement le caractère
 | |
|       qchar dans la valeur d'une colonne. Si echar est vide, le caractère qchar
 | |
|       est doublé.
 | |
| 
 | |
| array_formatcsv(fields, output)
 | |
|     équivaut à
 | |
|         array_formatcsv2(fields, \",\", \";\", \"\\\"\", \"\")
 | |
| 
 | |
| array_printcsv(fields, output)
 | |
|     équivaut à
 | |
|         printto(array_formatcsv(fields), output)
 | |
| 
 | |
| get_formatcsv()
 | |
|     équivaut à
 | |
|         array_fill(fields)
 | |
|         return array_formatcsv(fields)
 | |
| 
 | |
| formatcsv()
 | |
|     équivaut à
 | |
|         \$0 = get_formatcsv()
 | |
| 
 | |
| printcsv(output)
 | |
|     équivaut à
 | |
|         array_fill(fields)
 | |
|         array_printcsv(fields, output)
 | |
| 
 | |
| array_findcsv(fields, input, field, value, nbfields)
 | |
|     initialiser le tableau fields avec la première ligne au format csv du fichier
 | |
|     input dont le champ \$field vaut value. Le tableau est toujours initialisé,
 | |
|     même si la ligne correspondante n'a pas été trouvée.
 | |
|     Retourner 1 si la ligne correspondante a été trouvée
 | |
|     Si nbfields est spécifié, c'est le nombre de champs minimum que doit avoir le
 | |
|     tableau résultat. Le tableau est complété avec des chaines vides au besoin. Le
 | |
|     tableau commence à l'index 1."
 | |
| 
 | |
| ################################################################################
 | |
| 
 | |
| __AWKCSV_HELP="\
 | |
| Lancer un script awk pour traiter un flux csv
 | |
| Typiquement, l'option -e sera utilisée pour faire un traitement sur les
 | |
| données, e.g:
 | |
|     awkcsv -e '{ \$2 = toupper(\$2) }'
 | |
| pour mettre en majuscule le deuxième champ.
 | |
| 
 | |
| Analyse du flux en entrée:
 | |
| -s, --skip-lines nblines
 | |
|     Sauter nblines au début du flux
 | |
| -h, --parse-headers
 | |
|     Lire la liste des champs à partir de la première ligne non ignorée du flux.
 | |
|     Si la liste des champs est vide, cette option est implicitement activée.
 | |
|     Par contre, si une liste de champs est spécifiée et que le flux en entrée
 | |
|     contient une ligne d'en-têtes, il *faut* spécifier explicitement cette
 | |
|     option.
 | |
| --sepconf 'CQE'
 | |
|     Spécifier en une seule option les caractères de séparation des colonnes (C),
 | |
|     le caractère pour encadrer les chaines (Q) et le caractère d'échappement
 | |
|     (E). Pour chacun des caractères, s'il n'est pas spécifié, la valeur par
 | |
|     défaut est prise. CQE vaut par défaut ',\"'
 | |
| --colsep C
 | |
| --qchar Q
 | |
| --echar E
 | |
|     Spécifier les caractères pour l'analyse du flux csv. Seul le premier
 | |
|     caractère de respectivement C, Q et E est considéré.
 | |
|     - C est le séparateur des colonnes, e.g \",\"
 | |
|     - Q est le caractère employé pour encadrer la valeur d'une colonne si elle
 | |
|       contient le caractère C
 | |
|     - E est le caractère employé pour mettre en echappement le caractère Q dans
 | |
|       la valeur d'une colonne. Si E est vide, le caractère Q doit être doublé.
 | |
| 
 | |
| Flux en sortie:
 | |
| -n, --no-headers
 | |
|     Ne pas afficher les en-têtes. Par défaut, les en-têtes sont affichés.
 | |
| -z, --reset-fields
 | |
|     Commencer le script -e avec les champs vides. Il faut les copier
 | |
|     individuellement avec copyfield().
 | |
| 
 | |
| Scripts:
 | |
| -v var=value
 | |
|     Définir une variable pour le script awk
 | |
| -b before
 | |
|     Instructions à exécuter avant le début de l'analyse. Utiliser la variante
 | |
|     --rb pour remplacer les instructions par défaut, qui sont exécutées avant
 | |
|     les instructions de -b
 | |
|     Par défaut, il n'y a pas d'instructions -b et les instructions de --rb sont
 | |
|     d'analyser les en-têtes si -h est spécifié. Le tableau ORIGHEADERS contient
 | |
|     les en-têtes analysées en entrée. Le tableau HEADERS contient les en-têtes
 | |
|     spécifiées par l'utilisateur (copie de ORIGHEADERS si l'utilisateur de
 | |
|     spécifie pas d'en-têtes). Il est recommandé de ne pas modifier --rb
 | |
| 
 | |
| -e script
 | |
|     Instructions à exécuter pour chaque ligne en entrée. Utiliser la variante
 | |
|     --re pour remplacer les instructions par défaut qui sont exécutées avant les
 | |
|     instructions de -e
 | |
|     Par défaut, il n'y a pas d'instructions -e et les instructions de --re sont
 | |
|     d'analyser la ligne courante. Les champs sont disponibles dans \$1..\$NF et
 | |
|     dans le tableau ORIGFIELDS. Il est recommandé de ne pas modifier --re
 | |
| -m, --map FIELDMAP
 | |
|     Renommer les champs selon la liste FIELDMAP, qui contient des éléments de la
 | |
|     forme dest:src et séparés par des virgules. Le champ src doit exister dans
 | |
|     HEADERS, et dest ne doit pas exister dans HEADERS, sinon l'élément est
 | |
|     ignoré. Un script équivalent au suivant est ajouté aux instructions par
 | |
|     défaut --re:
 | |
|         '{ if (do_once(\"mapfields\")) mapfields(FIELDMAP) }'
 | |
|     Cette option annule l'option -z
 | |
| -c, --checkfields FIELDS
 | |
|     S'assurer que toutes les en-têtes spécifiées dans la liste FIELDS sont
 | |
|     présentes. Sinon, sortir du script avec le code de retour 1. Un script
 | |
|     équivalent au suivant est rajouté aux instructions par défaut --re:
 | |
|         '{ if (do_once(\"checkfields\") && !checkfields(FIELDS)) exit 1 }'
 | |
|     Ce traitement est effectué le cas échéant après le traitement --map
 | |
|     Cette option annule l'option -z
 | |
| --checkvalues FIELDS
 | |
|     S'assurer que toutes les valeurs des en-têtes spécifiées dans la liste
 | |
|     FIELDS sont non vides. Sinon, sortir du script avec le code de retour 1. Un
 | |
|     script équivalent au suivant est rajouté aux instructions par défaut --re:
 | |
|         '{ if (!checkvalues(FIELDS)) exit 1 }'
 | |
|     Ce traitement est effectué le cas échéant après le traitement --checkfields
 | |
|     Cette option annule l'option -z
 | |
| -k, --keep KEEPFIELDS
 | |
|     Garder les champs spécifiés. Les autres sont supprimés de la sortie.
 | |
|     KEEPFIELDS est une liste de champs séparés par des virgules, ou '*' pour
 | |
|     spécifier de garder tous les champs, ou '' pour ne garder aucun champ.
 | |
|     Si un champ de KEEPFIELDS n'existe pas, il est créé.
 | |
|     Cette option annule l'option -z
 | |
| --skip SKIPFIELDS
 | |
|     Exclure les champs spécifiés. SKIPFIELDS est une liste de champs séparés par
 | |
|     des virgules.
 | |
|     Pour les options --keep et --skip, un script équivalent au suivant est
 | |
|     rajouté aux instructions par défaut --re:
 | |
|         if (do_once(\"filterfields\")) {
 | |
|           build_skipfs(KEEPFIELDS, SKIPFIELDS, SKIPFS, ADDFS)
 | |
|           filterheaders(SKIPFS, ADDFS)
 | |
|         }
 | |
|         filterfields(SKIPFS, ADDFS)
 | |
|     Ce traitement est effectué le cas échéant après le traitement --checkvalues
 | |
| -a after
 | |
|     Instructions à exécuter après avoir traité la ligne en entrée. Utiliser la
 | |
|     variante --ra pour remplacer les instructions par défaut qui sont exécutées
 | |
|     avant les instructions de -a
 | |
|     Par défaut, il n'y a pas d'instructions --ra et les instructions de -a sont
 | |
|     d'afficher les en-têtes si -n n'est pas spécifié. Puis afficher les champs
 | |
|     \$1..\$NF
 | |
| 
 | |
| En fonctions des arguments, les variables skip_lines, parse_headers et
 | |
| show_headers sont définis. user_headers est un tableau contenant les champs
 | |
| spécifiés par l'utilisateur.
 | |
| 
 | |
| Les fonctions suivantes sont disponibles:
 | |
| do_once(key)
 | |
|     Retourner vrai s'il faut faire l'opération identifiées par key (qui vaut
 | |
|     par défaut \"default\"), qui ne devrait être faite qu'une seule fois.
 | |
|     Utiliser de cette manière:
 | |
|         if (do_once(key)) { ... }
 | |
| ogeth(field)
 | |
| ogeti(num)
 | |
| oget(field)
 | |
|     Gestion des valeurs originales.
 | |
| geth(field)
 | |
| sethi(num, value)
 | |
| seth(field, newfield)
 | |
| addh(field)
 | |
| delhi(num)
 | |
| delh(field)
 | |
| geti(num)
 | |
| get(field)
 | |
| seti(num, value)
 | |
| set(field, value)
 | |
| add(field, value)
 | |
| deli(num)
 | |
| del(field)
 | |
|     Gestion des valeurs courantes.
 | |
| comparevic(field1, value1, field2, value2, icfields)
 | |
| ocompareic(field1, field2, icfields)
 | |
| compareic(field1, field2, icfields)
 | |
| infields(field, fields)
 | |
| parseheaders()
 | |
| printheaders()
 | |
| resetfields()
 | |
| copyfield(field)
 | |
| copyfields(fields)
 | |
| copyall()
 | |
| mapfields(fieldmap)
 | |
| checkfields(fields[, missings])
 | |
| checkvalues(fields[, missings])
 | |
| build_skipfs(keepfields, skipfields, skipfs, addfs)
 | |
| filterheaders(skipfs, addfs)
 | |
| filterfields(skipfs, addfs)
 | |
|     Gestion avancée des valeurs. Pour les fonctions comparevic(), ocompareic(),
 | |
|     compareic(), infields(), copyfields(), checkfields(), checkvalues(),
 | |
|     mapfields(), build_skipfs(), les arguments sont une suite d'éléments séparés
 | |
|     par des virgules."
 | |
| 
 | |
| __AWKCSV_FUNCTIONS='
 | |
| BEGIN {
 | |
|   # Forcer ici le type tableau pour les variables ci-dessous. Sinon, leur
 | |
|   # utilisation dans une section BEGIN{} provoque une erreur fatale, parce
 | |
|   # qu"ils ne sont déclarés que dans des fonctions
 | |
|   array_new(HEADERS)
 | |
|   array_new(ORIGHEADERS)
 | |
|   array_new(ORIGFIELDS)
 | |
|   array_new(__DONE_ONCE)
 | |
| }
 | |
| function do_once(key) {
 | |
|   if (!key) key = "default"
 | |
|   if (__DONE_ONCE[key] != "") return 0
 | |
|   __DONE_ONCE[key] = 1
 | |
|   return 1
 | |
| }
 | |
| 
 | |
| function __geth(field, HEADERS,               nbfields, i) {
 | |
|   nbfields = array_len(HEADERS)
 | |
|   if (int(field) == field) {
 | |
|     field = int(field)
 | |
|     if (field >= 1 && field <= nbfields) return field
 | |
|     else return 0
 | |
|   }
 | |
|   field = tolower(field)
 | |
|   for (i = 1; i <= nbfields; i++) {
 | |
|     if (field == tolower(HEADERS[i])) {
 | |
|       return i
 | |
|     }
 | |
|   }
 | |
|   return 0
 | |
| }
 | |
| function __geti(num, HEADERS) { if (num != 0) return HEADERS[num] }
 | |
| function __get(field, HEADERS) { return __geti(__geth(field, HEADERS), HEADERS) }
 | |
| 
 | |
| 
 | |
| function ogeth(field,                nbfields, i) {
 | |
|   nbfields = array_len(ORIGHEADERS)
 | |
|   if (int(field) == field) {
 | |
|     field = int(field)
 | |
|     if (field >= 1 && field <= nbfields) return field
 | |
|     else return 0
 | |
|   }
 | |
|   field = tolower(field)
 | |
|   for (i = 1; i <= nbfields; i++) {
 | |
|     if (field == tolower(ORIGHEADERS[i])) {
 | |
|       return i
 | |
|     }
 | |
|   }
 | |
|   return 0
 | |
| }
 | |
| function ogeti(num) { if (num != 0) return ORIGFIELDS[num] }
 | |
| function oget(field) { return ogeti(ogeth(field)) }
 | |
| 
 | |
| function geth(field,                nbfields, i) {
 | |
|   nbfields = array_len(HEADERS)
 | |
|   if (int(field) == field) {
 | |
|     field = int(field)
 | |
|     if (field >= 1 && field <= nbfields) return field
 | |
|     else return 0
 | |
|   }
 | |
|   field = tolower(field)
 | |
|   for (i = 1; i <= nbfields; i++) {
 | |
|     if (field == tolower(HEADERS[i])) {
 | |
|       return i
 | |
|     }
 | |
|   }
 | |
|   return 0
 | |
| }
 | |
| function sethi(num, value) { if (num != 0) HEADERS[num] = value }
 | |
| function seth(field, value) { sethi(geth(field), value) }
 | |
| function addh(field,                 num) {
 | |
|   num = geth(field)
 | |
|   if (num == 0) {
 | |
|     num = array_len(HEADERS) + 1
 | |
|     HEADERS[num] = field
 | |
|   }
 | |
|   return num
 | |
| }
 | |
| function delhi(num,         i, l) {
 | |
|   if (num == 0) return
 | |
|   array_deli(HEADERS, num)
 | |
| }
 | |
| function delh(field) { delhi(geth(field)) }
 | |
| function geti(num) { if (num != 0) return $num }
 | |
| function get(field) { return geti(geth(field)) }
 | |
| function seti(num, value) { if (num != 0) $num = value }
 | |
| function set(field, value) { seti(geth(field), value) }
 | |
| function add(field, value,           num, i, max) {
 | |
|   num = addh(field)
 | |
|   i = NF
 | |
|   max = array_len(HEADERS)
 | |
|   if (i < max) {
 | |
|     i = i + 1
 | |
|     while(i <= max) {
 | |
|       $i = ""
 | |
|       i = i + 1
 | |
|     }
 | |
|   }
 | |
|   if ($num == "") {
 | |
|     $num = value
 | |
|   } else {
 | |
|     $num = $num ";" value
 | |
|   }
 | |
| }
 | |
| function deli(num,         i, j) {
 | |
|   if (num == 0) return
 | |
|   i = num
 | |
|   while (i < NF) {
 | |
|     j = i + 1
 | |
|     $i = $j
 | |
|     i = i + 1
 | |
|   }
 | |
|   NF = NF - 1
 | |
| }
 | |
| function del(field) { deli(geth(field)) }
 | |
| 
 | |
| function comparevic(field1, value1, field2, value2, icfields,           array) {
 | |
|   split(icfields, array, /,/)
 | |
|   if (in_array(field1, array, 1) || in_array(field2, array, 1)) {
 | |
|     return tolower(value1) == tolower(value2)
 | |
|   } else {
 | |
|     return value1 == value2
 | |
|   }
 | |
| }
 | |
| function ocompareic(field1, field2, icfields,           v1, v2, array) {
 | |
|   return comparevic(field1, oget(field1), field2, oget(field2), icfields)
 | |
| }
 | |
| function compareic(field1, field2, icfields,           v1, v2, array) {
 | |
|   return comparevic(field1, get(field1), field2, get(field2), icfields)
 | |
| }
 | |
| function infields(field, fields,           array) {
 | |
|   split(fields, array, /,/)
 | |
|   return in_array(field, array, 1)
 | |
| }
 | |
| function parseheaders() {
 | |
|   if (do_once("parse-headers")) {
 | |
|     array_parsecsv(ORIGHEADERS, $0)
 | |
|     if (!user_headers_count) array_copy(HEADERS, ORIGHEADERS)
 | |
|     next
 | |
|   }
 | |
| }
 | |
| function printheaders() {
 | |
|   if (do_once("show-headers")) {
 | |
|     array_printcsv(HEADERS)
 | |
|   }
 | |
| }
 | |
| function resetheaders() {
 | |
|   array_new(HEADERS)
 | |
| }
 | |
| function resetfields(                 nf) {
 | |
|   $0 = ""
 | |
|   nf = array_len(HEADERS)
 | |
|   $nf = ""
 | |
| }
 | |
| function copyfield(field) {
 | |
|   set(field, oget(field))
 | |
| }
 | |
| function copyfields(fields,           array) {
 | |
|   split(fields, array, /,/)
 | |
|   for (i = 1; i <= array_len(array); i++) {
 | |
|     copyfield(array[i])
 | |
|   }
 | |
| }
 | |
| function copyall() {
 | |
|   array_getline(ORIGFIELDS)
 | |
| }
 | |
| function checkfields(fields, missings,          array, r, field) {
 | |
|   array_new(missings)
 | |
|   split(fields, array, /,/)
 | |
|   r = 1
 | |
|   for (i = 1; i <= array_len(array); i++) {
 | |
|     field = array[i]
 | |
|     if (geth(field) == 0) {
 | |
|       array_add(missings, field)
 | |
|       r = 0
 | |
|     }
 | |
|   }
 | |
|   return r
 | |
| }
 | |
| function checkvalues(fields, missings,          array, r, field) {
 | |
|   array_new(missings)
 | |
|   split(fields, array, /,/)
 | |
|   r = 1
 | |
|   for (i = 1; i <= array_len(array); i++) {
 | |
|     field = array[i]
 | |
|     if (geth(field) == 0 || !get(field)) {
 | |
|       array_add(missings, field)
 | |
|       r = 0
 | |
|     }
 | |
|   }
 | |
|   return r
 | |
| }
 | |
| function mapfields(fieldmap,           mapitems, parts) {
 | |
|   split(fieldmap, mapitems, /,/)
 | |
|   for (i = 1; i <= array_len(mapitems); i++) {
 | |
|     split(mapitems[i], parts, /:/)
 | |
|     if (array_len(parts) != 2) continue
 | |
|     desti = geth(parts[1])
 | |
|     srci = geth(parts[2])
 | |
|     if (desti == 0 && srci != 0) {
 | |
|       HEADERS[srci] = parts[1]
 | |
|     }
 | |
|   }
 | |
| }
 | |
| function build_skipfs(keepfields, skipfields, skipfs, addfs,         keepfs, keepfs_count, tmpfields, headers_count, i, field, fieldi) {
 | |
|   array_new(skipfs)
 | |
|   array_new(addfs)
 | |
|   if (skipfields == "*") { skipfields = ""; keepfields = "" }
 | |
|   if (keepfields == "*" && skipfields == "") return 0
 | |
|   # construire la liste des champs à garder dans keepfs
 | |
|   split(keepfields, keepfs, /,/)
 | |
|   if (in_array("*", keepfs)) {
 | |
|     array_del(keepfs, "*")
 | |
|     array_extend(keepfs, HEADERS)
 | |
|   }
 | |
|   split(skipfields, tmpfields, /,/)
 | |
|   for (i in tmpfields) {
 | |
|     array_del(keepfs, tmpfields[i], 1)
 | |
|   }
 | |
|   keepfs_count = array_len(keepfs)
 | |
|   # puis construire la liste des champs à supprimer dans skipfs
 | |
|   headers_count = array_len(HEADERS)
 | |
|   for (i = 1; i <= headers_count; i++) {
 | |
|     field = HEADERS[i]
 | |
|     if (!in_array(field, keepfs, 1)) {
 | |
|       fieldi = geth(field)
 | |
|       if (i != 0) array_add(skipfs, fieldi)
 | |
|     }
 | |
|   }
 | |
|   asort(skipfs)
 | |
|   # puis construire la liste des champs à ajouter dans addfs
 | |
|   for (i = 1; i <= keepfs_count; i++) {
 | |
|     field = keepfs[i]
 | |
|     if (!in_array(field, HEADERS, 1)) {
 | |
|       array_add(addfs, field)
 | |
|     }
 | |
|   }
 | |
|   return array_len(skipfs)
 | |
| }
 | |
| function filterheaders(skipfs, addfs,        skipfs_count, addfs_count) {
 | |
|   skipfs_count = array_len(skipfs)
 | |
|   for (i = skipfs_count; i >= 1; i--) {
 | |
|     array_deli(HEADERS, skipfs[i])
 | |
|   }
 | |
|   addfs_count = array_len(addfs)
 | |
|   for (i = 1; i <= addfs_count; i++) {
 | |
|     addh(addfs[i])
 | |
|   }
 | |
| }
 | |
| function filterfields(skipfs, addfs,         skipfs_count) {
 | |
|   skipfs_count = array_len(skipfs)
 | |
|   for (i = skipfs_count; i >= 1; i--) {
 | |
|     deli(skipfs[i])
 | |
|   }
 | |
|   addfs_count = array_len(addfs)
 | |
|   for (i = 1; i <= addfs_count; i++) {
 | |
|     add(addfs[i], "")
 | |
|   }
 | |
| }
 | |
| '
 | |
| function lawkcsv() {
 | |
|     local beforescript='{
 | |
|   if (user_headers_count && do_once("user-headers")) {
 | |
|     if (!parse_headers) array_copy(ORIGHEADERS, user_headers)
 | |
|     array_copy(HEADERS, user_headers)
 | |
|   }
 | |
|   if (parse_headers) parseheaders()
 | |
| }'
 | |
|     local append_beforescript=
 | |
|     local awkscript='{
 | |
|   array_parsecsv(ORIGFIELDS, $0, array_len(ORIGHEADERS))
 | |
|   if (reset_fields) { resetfields() } else { copyall() }
 | |
| }'
 | |
|     local append_awkscript=
 | |
|     local afterscript=
 | |
|     local append_afterscript='{
 | |
|   if (show_headers) printheaders()
 | |
|   printcsv()
 | |
| }'
 | |
| 
 | |
|     local -a args headers vars
 | |
|     local skip_lines=0 parse_headers=
 | |
|     local autosep=
 | |
|     local sepconf=',"' colsep=',' qchar='"' echar=
 | |
|     local show_headers=1 reset_fields=
 | |
|     local fieldmap checkfields checkvalues keepfields skipfields
 | |
|     if parse_opts \
 | |
|         -s:,--skip-lines: skip_lines= \
 | |
|         -h,--parse-headers parse_headers=1 \
 | |
|         --sepconf: '$autosep=sepconf; set@ sepconf' \
 | |
|         --colsep: '$autosep=indiv; set@ colsep' \
 | |
|         --qchar: '$autosep=indiv; set@ qchar' \
 | |
|         --echar: '$autosep=indiv; set@ echar' \
 | |
|         -n,--no-headers show_headers= \
 | |
|         --show-headers show_headers=1 \
 | |
|         -z,--reset-fields reset_fields=1 \
 | |
|         -v:,--var: vars \
 | |
|         --rb:,--rbe: '$set@ beforescript; append_beforescript=' \
 | |
|         -b:,--b:,--be:,--before-script: append_beforescript= \
 | |
|         --re: '$set@ awkscript; append_awkscript=' \
 | |
|         -e:,--e:,--awk-script:,--script: append_awkscript= \
 | |
|         -m:,--map:,--mapfields fieldmap= \
 | |
|         -c:,--checkfields: checkfields= \
 | |
|         --checkvalues: checkvalues= \
 | |
|         -k:,--keep:,--keepfields: keepfields= \
 | |
|         --skip:,--skipfields: skipfields= \
 | |
|         --ra:,--rae: '$set@ afterscript; append_afterscript=' \
 | |
|         -a:,--a:,--ae:,--after-script: append_afterscript= \
 | |
|         @ args -- "$@"; then
 | |
|         set -- "${args[@]}"
 | |
|     else
 | |
|         eerror "$args"
 | |
|         return 1
 | |
|     fi
 | |
| 
 | |
|     if [ "$autosep" == sepconf ]; then
 | |
|         colsep="${sepconf:0:1}"
 | |
|         qchar="${sepconf:1:1}"
 | |
|         echar="${sepconf:2:1}"
 | |
|         autosep=
 | |
|         if [ -n "$colsep" -o -n "$qchar" -o -n "$echar" ]; then
 | |
|             [ -n "$colsep" ] || colsep=','
 | |
|             [ -n "$qchar" ] || qchar='"'
 | |
|             # n'activer la configuration que si elle diffère de la configuration
 | |
|             # par défaut
 | |
|             [ "$colsep" != ',' -o "$qchar" != '"' -o -n "$echar" ] && autosep=1
 | |
|         fi
 | |
|     elif [ "$autosep" == indiv ]; then
 | |
|         autosep=1
 | |
|     fi
 | |
|     colsep="${colsep:0:1}"
 | |
|     qchar="${qchar:0:1}"
 | |
|     echar="${echar:0:1}"
 | |
| 
 | |
|     headers=()
 | |
|     while [ $# -gt 0 -a "$1" != "--" ]; do
 | |
|         array_add headers "$1"
 | |
|         shift
 | |
|     done
 | |
|     shift
 | |
| 
 | |
|     [ -n "${headers[*]}" ] || parse_headers=1
 | |
| 
 | |
|     if [ -n "$fieldmap" ]; then
 | |
|         awkscript="$awkscript"'{
 | |
|   if (do_once("mapfields")) { mapfields(fields2map) }
 | |
| }'
 | |
|         reset_fields=
 | |
|     fi
 | |
|     if [ -n "$checkfields" ]; then
 | |
|         awkscript="$awkscript"'{
 | |
|   if (do_once("checkfields")) { if (!checkfields(fields2check)) exit 1 }
 | |
| }'
 | |
|         reset_fields=
 | |
|     fi
 | |
|     if [ -n "$checkvalues" ]; then
 | |
|         awkscript="$awkscript"'{
 | |
|   if (!checkvalues(values2check)) exit 1
 | |
| }'
 | |
|         reset_fields=
 | |
|     fi
 | |
|     if [ -n "$skipfields" ]; then
 | |
|         [ -n "$keepfields" ] || keepfields="*"
 | |
|     fi
 | |
|     if [ -n "$keepfields" ]; then
 | |
|         awkscript="$awkscript"'{
 | |
|   if (do_once("filterfields")) {
 | |
|     build_skipfs(fields2keep, fields2skip, SKIPFS, ADDFS)
 | |
|     filterheaders(SKIPFS, ADDFS)
 | |
|   }
 | |
|   filterfields(SKIPFS, ADDFS)
 | |
| }'
 | |
|         reset_fields=
 | |
|     fi
 | |
| 
 | |
|     awkrun -f \
 | |
|         skip_lines:int="$skip_lines" parse_headers:int="$parse_headers" \
 | |
|         autosep:int="$autosep" colsep="$colsep" qchar="$qchar" echar="$echar" \
 | |
|         show_headers:int="$show_headers" reset_fields:int="$reset_fields" \
 | |
|         fields2map="$fieldmap" fields2check="$checkfields" values2check="$checkvalues" \
 | |
|         fields2keep="$keepfields" fields2skip="$skipfields" \
 | |
|         "user_headers[@]=headers" "${vars[@]}" \
 | |
| "$__AWKCSV_FUNCTIONS"'
 | |
| BEGIN {
 | |
|   if (autosep) {
 | |
|     DEFAULT_COLSEP = colsep
 | |
|     DEFAULT_QCHAR = qchar
 | |
|     DEFAULT_ECHAR = echar
 | |
|   }
 | |
| }
 | |
| NR <= skip_lines { next }
 | |
| '"$beforescript
 | |
| $append_beforescript
 | |
| $awkscript
 | |
| $append_awkscript
 | |
| $afterscript
 | |
| $append_afterscript
 | |
| " -- "$@"
 | |
| }
 | |
| 
 | |
| function cawkcsv() { LANG=C lawkcsv "$@"; }
 | |
| function awkcsv() { LANG=C lawkcsv "$@"; }
 | |
| 
 | |
| ################################################################################
 | |
| 
 | |
| __GREPCSV_HELP="\
 | |
| Faire une recherche dans un flux csv, et afficher uniquement les lignes qui
 | |
| correspondent à l'expression.
 | |
| EXPR est une expression awk, e.g. 'field == \"value1\" || field == \"value2\"'
 | |
| 
 | |
| Analyse du flux en entrée:
 | |
| -s, --skip-lines nblines
 | |
|     Sauter nblines au début du flux
 | |
| -h, --parse-headers
 | |
|     Lire la liste des champs à partir de la première ligne non ignorée du flux.
 | |
|     Si la liste des champs est vide, cette option est implicitement activée.
 | |
|     Par contre, si une liste de champs est spécifiée et que le flux en entrée
 | |
|     contient un ligne d'en-têtes, il *faut* spécifier explicitement cette
 | |
|     option.
 | |
| --sepconf 'CQE'
 | |
|     Spécifier en une seule option les caractères de séparation des colonnes (C),
 | |
|     le caractère pour encadrer les chaines (Q) et le caractère d'échappement
 | |
|     (E). Pour chacun des caractères, s'il n'est pas spécifié, la valeur par
 | |
|     défaut est prise. CQE vaut par défaut ',\"'
 | |
| --colsep C
 | |
| --qchar Q
 | |
| --echar E
 | |
|     Spécifier les caractères pour l'analyse du flux csv. Seul le premier
 | |
|     caractère de respectivement C, Q et E est considéré.
 | |
|     - C est le séparateur des colonnes, e.g \",\"
 | |
|     - Q est le caractère employé pour encadrer la valeur d'une colonne si elle
 | |
|       contient le caractère C
 | |
|     - E est le caractère employé pour mettre en echappement le caractère Q dans
 | |
|       la valeur d'une colonne. Si E est vide, le caractère Q doit être doublé.
 | |
| 
 | |
| Flux en sortie:
 | |
| -n, --no-headers
 | |
|     Ne pas afficher les en-têtes. Par défaut, les en-têtes sont affichés.
 | |
| -q, --quiet
 | |
|     Ne pas afficher les lignes. Indiquer seulement si des lignes ont été
 | |
|     trouvées.
 | |
| 
 | |
| Scripts:
 | |
| -v var=value
 | |
|     Définir une variable pour le script awk
 | |
| -e script
 | |
|     Instructions à exécuter pour chaque ligne en entrée. Utiliser la variante
 | |
|     --re pour remplacer les instructions par défaut qui sont exécutées avant les
 | |
|     instructions de -e
 | |
|     Par défaut, il n'y a pas d'instructions -e et les instructions de --re sont
 | |
|     de créer des variables nommées d'après les colonnes du flux csv. Il est
 | |
|     recommandé de ne pas modifier --re
 | |
|     Note d'implémentation: pour des raisons d'optimisation, le traitement
 | |
|     effectué sur chaque ligne n'est pas aussi complet que celui de la fonction
 | |
|     awkcsv(): Les tableaux ORIGFIELDS et ORIGHEADERS sont disponible; par
 | |
|     contre, la ligne courante est au format csv non analysé. Utiliser la
 | |
|     fonction copyall() pour être dans les mêmes conditions que le script awkcsv."
 | |
| 
 | |
| function lgrepcsv() {
 | |
|     local -a args vars
 | |
|     local skip_lines= parse_headers=
 | |
|     local sepconf= colsep= qchar= echar=
 | |
|     local no_headers= quiet=
 | |
|     local awkscript=--use-default--
 | |
|     local append_awkscript=
 | |
|     if parse_opts \
 | |
|         -s:,--skip-lines: skip_lines= \
 | |
|         -h,--parse-headers parse_headers=1 \
 | |
|         --sepconf: sepconf= \
 | |
|         --colsep: colsep= \
 | |
|         --qchar: qchar= \
 | |
|         --echar: echar= \
 | |
|         -n,--no-headers no_headers=1 \
 | |
|         --show-headers no_headers= \
 | |
|         -q,--quiet quiet=1 \
 | |
|         -v:,--var: vars \
 | |
|         --re: '$set@ awkscript; append_awkscript=' \
 | |
|         -e:,--e:,--awk-script:,--script: append_awkscript= \
 | |
|         @ args -- "$@"; then
 | |
|         set -- "${args[@]}"
 | |
|     else
 | |
|         eerror "$args"
 | |
|         return 1
 | |
|     fi
 | |
| 
 | |
|     local expr="$1"; shift
 | |
| 
 | |
|     local -a inputfiles tmpfiles
 | |
|     local inputfile
 | |
|     while [ $# -gt 0 -a "$1" != "--" ]; do
 | |
|         if [ "$1" == "-" ]; then
 | |
|             ac_set_tmpfile inputfile
 | |
|             array_add tmpfiles "$inputfile"
 | |
|             cat >"$inputfile"
 | |
|         else
 | |
|             inputfile="$1"
 | |
|         fi
 | |
|         array_add inputfiles "$inputfile"
 | |
|         shift
 | |
|     done
 | |
|     shift
 | |
|     if [ "${#inputfiles[*]}" -eq 0 ]; then
 | |
|         ac_set_tmpfile inputfile
 | |
|         array_add tmpfiles "$inputfile"
 | |
|         cat >"$inputfile"
 | |
|         array_add inputfiles "$inputfile"
 | |
|     fi
 | |
| 
 | |
|     local -a headers
 | |
|     headers=("$@")
 | |
|     if [ -z "${headers[*]}" ]; then
 | |
|         parse_headers=1
 | |
|         array_from_lines headers "$(awkrun -f lskip:int="$skip_lines" 'NR <= lskip { next }
 | |
| {
 | |
|   count = array_parsecsv(__fields, $0)
 | |
|   for (i = 1; i <= count; i++) {
 | |
|     print __fields[i]
 | |
|   }
 | |
|   exit
 | |
| }' -- "${inputfiles[@]}")"
 | |
|     fi
 | |
|     if [ "$awkscript" == --use-default-- ]; then
 | |
|         awkscript=
 | |
|         for header in "${headers[@]}"; do
 | |
|             awkscript="$awkscript
 | |
| $header = oget(\"$header\")"
 | |
|         done
 | |
|     fi
 | |
|     local grepscript="\
 | |
| BEGIN { ec = 1 }
 | |
| {
 | |
|   $awkscript
 | |
|   $append_awkscript
 | |
|   if ($expr) {
 | |
|     ec = 0
 | |
|     if (quiet) exit
 | |
|     printheaders()
 | |
|     print
 | |
|   }
 | |
| }
 | |
| END { exit ec }"
 | |
| 
 | |
|     lawkcsv ${skip_lines:+-s "$skip_lines"} ${parse_headers:+-h} \
 | |
|         ${sepconf:+--sepconf "$sepconf"} ${colsep:+--colsep "$colsep"} ${qchar:+--qchar "$qchar"} ${echar:+--echar "$echar"} \
 | |
|         ${no_headers:+--no-headers} -v quiet:int=$quiet \
 | |
|         --re '{array_parsecsv(ORIGFIELDS, $0, array_len(ORIGHEADERS))}' -a "$grepscript" \
 | |
|         -- "${headers[@]}" -- "${inputfiles[@]}"
 | |
|     local r=$?
 | |
| 
 | |
|     ac_clean "${tmpfiles[@]}"
 | |
|     return $r
 | |
| }
 | |
| 
 | |
| function cgrepcsv() { LANG=C lgrepcsv "$@"; }
 | |
| function grepcsv() { LANG=C lgrepcsv "$@"; }
 | |
| 
 | |
| ################################################################################
 | |
| 
 | |
| __AWKFSV2CSV_HELP="\
 | |
| Transformer un flux fsv (colonnes à largeurs fixes) en csv
 | |
| 
 | |
| Chaque argument doit être de la forme [-]header:size. La colonne sera incluse
 | |
| dans le fichier en sortie, sauf si elle est précédée de -
 | |
| 
 | |
| Analyse du flux en entrée:
 | |
| -s, --skip-lines nblines
 | |
|     Sauter nblines au début du flux
 | |
| -r, --no-trim
 | |
|     Ne pas trimmer les valeurs à droite.
 | |
| 
 | |
| Flux en sortie:
 | |
| -n, --no-headers
 | |
|     Ne pas afficher les en-têtes. Par défaut, les en-têtes sont affichés."
 | |
| 
 | |
| function lawkfsv2csv() {
 | |
|     local -a args headersizes
 | |
|     local skip_lines=0 trim_values=1 show_headers=1
 | |
|     if parse_opts \
 | |
|         -s:,--skip-lines: skip_lines= \
 | |
|         -r,--no-trim trim_values= \
 | |
|         -n,--no-headers show_headers= \
 | |
|         --show-headers show_headers=1 \
 | |
|         @ args -- "$@"; then
 | |
|         set -- "${args[@]}"
 | |
|     else
 | |
|         eerror "$args"
 | |
|         return 1
 | |
|     fi
 | |
| 
 | |
|     local -a headers starts sizes
 | |
|     local headersize header i size
 | |
|     i=1
 | |
|     while [ $# -gt 0 -a "$1" != "--" ]; do
 | |
|         headersize="$1"
 | |
|         shift
 | |
| 
 | |
|         splitpair "$headersize" header size
 | |
|         [ -n "$header" ] || {
 | |
|             eerror "header est requis"
 | |
|             return 1
 | |
|         }
 | |
|         [ -n "$size" ] || size=1
 | |
|         if [ "${header#-}" == "$header" ]; then
 | |
|             array_add headers "$header"
 | |
|             array_add starts "$i"
 | |
|             array_add sizes "$size"
 | |
|         fi
 | |
|         i="$(($i + $size))"
 | |
|     done
 | |
|     shift
 | |
| 
 | |
|     awkrun -f \
 | |
|         skip_lines:int="$skip_lines" trim_values:int="$trim_values" show_headers:int="$show_headers" \
 | |
|         headers[@] starts[@] sizes[@] \
 | |
|         "$__AWKCSV_FUNCTIONS"'
 | |
| BEGIN {
 | |
|   if (show_headers) {
 | |
|     print array_formatcsv(headers)
 | |
|   }
 | |
| }
 | |
| NR <= skip_lines { next }
 | |
| {
 | |
|   line = $0
 | |
|   $0 = ""
 | |
|   for (i = 1; i <= headers_count; i++) {
 | |
|     value = substr(line, starts[i], sizes[i])
 | |
|     if (trim_values) {
 | |
|       sub(/^ */, "", value)
 | |
|       sub(/ *$/, "", value)
 | |
|     }
 | |
|     $i = value
 | |
|   }
 | |
|   formatcsv()
 | |
|   print
 | |
| }
 | |
| ' -- "$@"
 | |
| }
 | |
| 
 | |
| function cawkfsv2csv() { LANG=C lawkfsv2csv "$@"; }
 | |
| function awkfsv2csv() { LANG=C lawkfsv2csv "$@"; }
 | |
| 
 | |
| ################################################################################
 | |
| 
 | |
| __MERGECSV_HELP="\
 | |
| Fusionner deux fichiers csv en faisant la correspondance sur la valeur d'un
 | |
| champ, qui est la clé
 | |
| 
 | |
| -h, --parse-headers
 | |
|     Lire la liste des champs à partir de la première ligne non ignorée des flux.
 | |
|     Si cette option est spécifiée (ce qui est le cas par défaut), les champs
 | |
|     spécifiés avec les options -k, --lk et --rk peuvent être les noms effectifs
 | |
|     des champs. Sinon, les champs ne peuvent être que numériques.
 | |
| -n, --numkeys
 | |
|     Ne pas analyser la première ligne pour les noms des champs. Les champs
 | |
|     spécifiés ne peuvent être que numériques.
 | |
| --lskip nblines
 | |
| --rskip nblines
 | |
|     Sauter nblines au début du flux, respectivement pour left et pour right
 | |
| --lheaders FIELDS
 | |
| --rheaders FIELDS
 | |
|     Spécifier les noms des champs à utiliser *en sortie*, respectivement pour
 | |
|     left et pour right. Les champs doivent être séparés par des virgules.
 | |
|     Si ces valeurs ne sont pas spécifiées, prendre les champs lus avec l'option
 | |
|     --parse-headers. Sinon, ne pas afficher d'en-têtes en sortie.
 | |
| --lprefix prefix
 | |
| --rprefix prefix
 | |
|     Spécifier un préfixe à rajouter automatiquement aux champs à utiliser *en
 | |
|     sortie*. Le prefix est rajouté que les champs soient déterminés par
 | |
|     --parse-headers ou spécifiés par --{l,r}headers. La correspondance des
 | |
|     champs avec --lk et --rk est faite sur les noms des champs *avant* qu'ils
 | |
|     soient modifiés par cette option.
 | |
| --lk, --lkey FIELD
 | |
| --rk, --rkey FIELD
 | |
|     Spécifier le champ utilisé pour faire la correspondance entre les deux flux,
 | |
|     respectivement pour left et pour right.
 | |
|     Si --parse-headers n'est pas spécifié, ces valeurs doivent être numériques.
 | |
|     La valeur par défaut est 1.
 | |
| -k, --key FIELD
 | |
|     Spécifier le champ utilisé pour faire la correspondance sur les deux
 | |
|     flux. Equivalent à '--lk FIELD --rk FIELD'
 | |
|     La correspondance des champs avec --lk et --rk est faite sur les noms des
 | |
|     champs *avant* qu'ils soient modifiés le cas échéant par --lprefix et/ou
 | |
|     --rprefix
 | |
| -i
 | |
|     Faire la correspondance de la valeur des champs sans tenir compte de la
 | |
|     casse
 | |
| 
 | |
| Après la fusion des deux fichiers csv, il est possible de faire une sélection
 | |
| des lignes selon certains critères:
 | |
| 
 | |
| -s SELECT
 | |
|     Spécifier un type de sélection à faire. SELECT peut valoir:
 | |
|     none, n
 | |
|         C'est l'action par défaut; toutes les lignes sont affichées
 | |
|     inner-join, j
 | |
|         N'inclure dans le résultat que les lignes pour lesquelles il y a une
 | |
|         correspondance sur les clés.
 | |
|         L'option -J est synonyme de '-s inner-join'
 | |
|     left-join, l
 | |
|         Inclure dans le résultat les lignes pour lesquelles il y a une
 | |
|         correspondance sur les clés, et les lignes de left pour lesquelles il
 | |
|         n'y a pas de correspondance. Les lignes de right sans correspondance ne
 | |
|         sont pas affichées.
 | |
|         L'option -L est synonyme de '-s left-join'
 | |
|     right-join, r
 | |
|         Inclure dans le résultat les lignes pour lesquelles il y a une
 | |
|         correspondance sur les clés, et les lignes de right pour lesquelles il
 | |
|         n'y a pas de correspondance. Les lignes de left sans correspondance ne
 | |
|         sont pas affichées.
 | |
|         L'option -R est synonyme de '-s right-join'
 | |
|     left-only, lo
 | |
|         N'inclure dans le résultat que les lignes de left pour lesquelles il n'y
 | |
|         a pas de correspondance dans right.
 | |
|     right-only, ro
 | |
|         N'inclure dans le résultat que les lignes de right pour lesquelles il n'y
 | |
|         a pas de correspondance dans left.
 | |
| 
 | |
| Les options suivantes sont des actions qu'il est possible d'effectuer après la
 | |
| sélection des lignes. Les noms des champs spécifiés dans ces options sont les
 | |
| noms en sortie, en tenant compte de l'analyse et du traitement effectués par les
 | |
| options --parse-headers, --{l,r}headers et --{l,r}prefix.
 | |
| 
 | |
| --copy, --lcopyf FIELDS
 | |
|     Copier les champs spécifiés de right vers left. Les champs spécifiés sont
 | |
|     séparés par des virgules et sont de la forme [dest:]src
 | |
| --rcopyf FIELDS
 | |
|     Copier les champs spécifiés de left vers right. Les champs spécifiés sont
 | |
|     séparés par des virgules et sont de la forme [dest:]src
 | |
| --lkeepf FIELDS
 | |
|     Garder les champs de left spécifiés. Les autres sont supprimés de la sortie.
 | |
|     FIELDS est une liste de champs séparés par des virgules, ou '*' pour
 | |
|     spécifier de garder tous les champs ou '' pour ne garder aucun champ.
 | |
|     Par défaut, avec '-s right-only', FIELDS vaut '', sinon FIELDS vaut '*'
 | |
| --keep, --rkeepf FIELDS
 | |
|     Garder les champs de right spécifiés. Les autres sont supprimés de la
 | |
|     sortie. FIELDS est une liste de champs séparés par des virgules, ou '*' pour
 | |
|     spécifier de garder tous les champs, ou '' pour ne garder aucun champ.
 | |
|     Par défaut, avec '-s left-only', FIELDS vaut '', sinon FIELDS vaut '*'
 | |
| --lskipf FIELDS
 | |
| --rskipf FIELDS
 | |
|     Exclure les champs de left (resp. de right) spécifiés. FIELDS est une liste
 | |
|     de champs séparés par des virgules."
 | |
| 
 | |
| : "${__MERGECSV_DEBUG:=}"
 | |
| function lmergecsv() {
 | |
|     # Fusionner sur la sortie standard les deux fichiers csv $1 et $2. La clé du
 | |
|     # fichier $1 est spécifiée par l'option --lkey et vaut 1 par défaut. La clé
 | |
|     # du fichier $2 est spécifiée par l'option --rkey et vaut 1 par défaut. Les
 | |
|     # valeurs des clés ne doivent pas faire plus de 64 caractères de long.
 | |
|     eval "$(utools_local)"
 | |
|     local parse_headers=auto
 | |
|     local ignore_case=
 | |
|     local lskip=0 lkey=1 lheaders= lprefix=
 | |
|     local rskip=0 rkey=1 rheaders= rprefix=
 | |
|     local select=none
 | |
| 
 | |
|     local postproc=auto
 | |
|     local lcopyf= rcopyf=
 | |
|     local lkeepf=--NOT-SET-- rkeepf=--NOT-SET--
 | |
|     local lskipf= rskipf=
 | |
|     parse_opts "${PRETTYOPTS[@]}" \
 | |
|         -h,--parse-headers parse_headers=1 \
 | |
|         -n,--numkeys parse_headers= \
 | |
|         --lskip: lskip= \
 | |
|         --lkey:,--lk: lkey= \
 | |
|         --lheaders:,--lh: lheaders= \
 | |
|         --lprefix:,--lp: lprefix= \
 | |
|         --rskip: rskip= \
 | |
|         --rkey:,--rk: rkey= \
 | |
|         --rheaders:,--rh: rheaders= \
 | |
|         --rprefix:,--rp: rprefix= \
 | |
|         -i,--ignore-case ignore_case=1 \
 | |
|         -k:,--key: '$set@ lkey; set@ rkey' \
 | |
|         -s:,--select: select= \
 | |
|         -J select=inner-join \
 | |
|         -L select=left-join \
 | |
|         -R select=right-join \
 | |
|         --lcopyf:,--copy: '$set@ lcopyf; postproc=1' \
 | |
|         --rcopyf: '$set@ rcopyf; postproc=1' \
 | |
|         --lkeepf: '$set@ lkeepf; postproc=1' \
 | |
|         --rkeepf:,--keep: '$set@ rkeepf; postproc=1' \
 | |
|         --lskipf: '$set@ lskipf; postproc=1' \
 | |
|         --rskipf: '$set@ rskipf; postproc=1' \
 | |
|         @ args -- "$@" && set -- "${args[@]}" || die "$args"
 | |
| 
 | |
|     local lfile="$1"
 | |
|     local rfile="$2"
 | |
|     [ -f "$lfile" -a -f "$rfile" ] || {
 | |
|         [ -f "$lfile" ] || eerror "$lfile: fichier introuvable"
 | |
|         [ -f "$rfile" ] || eerror "$rfile: fichier introuvable"
 | |
|         return 1
 | |
|     }
 | |
| 
 | |
|     local padding="----------------------------------------------------------------"
 | |
|     local padlen=${#padding}
 | |
| 
 | |
|     [ "$parse_headers" == "auto" ] && parse_headers=1
 | |
|     if [ -n "$parse_headers" ]; then
 | |
|         [ -n "$lheaders" ] || lheaders="$(<"$lfile" awkrun lskip:int="$lskip" 'NR <= lskip { next } { print; exit }')"
 | |
|         [ -n "$rheaders" ] || rheaders="$(<"$rfile" awkrun lskip:int="$lskip" 'NR <= lskip { next } { print; exit }')"
 | |
|     fi
 | |
| 
 | |
|     # faire le fichier de travail pour lfile
 | |
|     local tmpleft
 | |
|     ac_set_tmpfile tmpleft "" __mergecsv_left "" __MERGECSV_DEBUG
 | |
|     <"$lfile" awkrun -f \
 | |
|         padding="$padding" padlen:int="$padlen" \
 | |
|         parse_headers:int="$parse_headers" ignore_case:int="$ignore_case" \
 | |
|         lskip:int="$lskip" lkey="$lkey" \
 | |
|         "$__AWKCSV_FUNCTIONS"'
 | |
| NR <= lskip { next }
 | |
| parse_headers && do_once("parse-headers") {
 | |
|   array_parsecsv(HEADERS, $0)
 | |
|   lkey = geth(lkey)
 | |
|   if (!lkey) lkey = 1
 | |
|   next
 | |
| }
 | |
| {
 | |
|   oline = $0
 | |
|   parsecsv($0)
 | |
|   keyvalue = substr($lkey, 1, padlen)
 | |
|   if (ignore_case) keyvalue = tolower(keyvalue)
 | |
|   print keyvalue substr(padding, 1, padlen - length(keyvalue)) "1" oline
 | |
| }
 | |
| ' | csort -su >"$tmpleft"
 | |
| 
 | |
|     # faire le fichier de travail pour rfile
 | |
|     local tmpright
 | |
|     ac_set_tmpfile tmpright "" __mergecsv_right "" __MERGECSV_DEBUG
 | |
|     <"$rfile" awkrun -f \
 | |
|         padding="$padding" padlen:int="$padlen" \
 | |
|         parse_headers:int="$parse_headers" ignore_case:int="$ignore_case" \
 | |
|         rskip:int="$rskip" rkey="$rkey" \
 | |
|         "$__AWKCSV_FUNCTIONS"'
 | |
| NR <= rskip { next }
 | |
| parse_headers && do_once("parse-headers") {
 | |
|   array_parsecsv(HEADERS, $0)
 | |
|   rkey = geth(rkey)
 | |
|   if (!rkey) rkey = 1
 | |
|   next
 | |
| }
 | |
| {
 | |
|   oline = $0
 | |
|   parsecsv($0)
 | |
|   keyvalue = substr($rkey, 1, padlen)
 | |
|   if (ignore_case) keyvalue = tolower(keyvalue)
 | |
|   print keyvalue substr(padding, 1, padlen - length(keyvalue)) "2" oline
 | |
| }
 | |
| ' | csort -su >"$tmpright"
 | |
| 
 | |
|     # calculer les options de post-traitement
 | |
|     case "$select" in
 | |
|     none|n|"") select=none;;
 | |
|     inner-join|inner|join|j) select=inner-join;;
 | |
|     left-join|left|l) select=left-join;;
 | |
|     right-join|right|r) select=right-join;;
 | |
|     left-only|lo) select=left-only;;
 | |
|     right-only|ro) select=right-only;;
 | |
|     *)
 | |
|         ewarn "$select: valeur de --select invalide. Elle sera ignorée"
 | |
|         select=none
 | |
|         ;;
 | |
|     esac
 | |
|     if [ "$postproc" == "auto" ]; then
 | |
|         case "$select" in
 | |
|         left-only) lkeepf="*"; rkeepf=""; postproc=1;;
 | |
|         right-only) lkeepf=""; rkeepf="*"; postproc=1;;
 | |
|         *) lkeepf="*"; rkeepf="*"; postproc=;;
 | |
|         esac
 | |
|     fi
 | |
|     if [ -n "$postproc" ]; then
 | |
|         if [ "$lkeepf" == "--NOT-SET--" ]; then
 | |
|             case "$select" in
 | |
|             right-only) lkeepf=;;
 | |
|             *) lkeepf="*";;
 | |
|             esac
 | |
|         fi
 | |
|         if [ "$rkeepf" == "--NOT-SET--" ]; then
 | |
|             case "$select" in
 | |
|             left-only) rkeepf=;;
 | |
|             *) rkeepf="*";;
 | |
|             esac
 | |
|         fi
 | |
|         local -a tmpfields fields
 | |
|         local field src dest
 | |
|         if [ -n "$lcopyf" ]; then
 | |
|             array_split tmpfields "$lcopyf" ,
 | |
|             fields=()
 | |
|             for field in "${tmpfields[@]}"; do
 | |
|                 splitpair "$field" dest src
 | |
|                 [ -n "$src" ] || src="$dest"
 | |
|                 array_add fields "$dest:$src"
 | |
|             done
 | |
|             lcopyf="$(array_join fields ,)"
 | |
|         fi
 | |
|         if [ -n "$rcopyf" ]; then
 | |
|             array_split tmpfields "$rcopyf" ,
 | |
|             fields=()
 | |
|             for field in "${tmpfields[@]}"; do
 | |
|                 splitpair "$field" dest src
 | |
|                 [ -n "$src" ] || src="$dest"
 | |
|                 array_add fields "$dest:$src"
 | |
|             done
 | |
|             rcopyf="$(array_join fields ,)"
 | |
|         fi
 | |
|         if [ "$lskipf" == "*" ]; then
 | |
|             lskipf=
 | |
|             lkeepf=
 | |
|         fi
 | |
|         if [ -n "$lskipf" ]; then
 | |
|             [ "$lkeepf" == "*" ] && lkeepf="$lheaders"
 | |
|             array_split fields "$lkeepf" ,
 | |
|             array_split tmpfields "$lskipf" ,
 | |
|             for field in "${tmpfields[@]}"; do
 | |
|                 array_del fields "$field"
 | |
|             done
 | |
|             lkeepf="$(array_join fields ,)"
 | |
|         fi
 | |
|         if [ "$rskipf" == "*" ]; then
 | |
|             rskipf=
 | |
|             rkeepf=
 | |
|         fi
 | |
|         if [ -n "$rskipf" ]; then
 | |
|             [ "$rkeepf" == "*" ] && rkeepf="$rheaders"
 | |
|             array_split fields "$rkeepf" ,
 | |
|             array_split tmpfields "$rskipf" ,
 | |
|             for field in "${tmpfields[@]}"; do
 | |
|                 array_del fields "$field"
 | |
|             done
 | |
|             rkeepf="$(array_join fields ,)"
 | |
|         fi
 | |
|     fi
 | |
|     local -a lcopyfs rcopyfs lkeepfs rkeepfs
 | |
|     array_split lcopyfs "$lcopyf" ,
 | |
|     array_split rcopyfs "$rcopyf" ,
 | |
|     array_split lkeepfs "$lkeepf" ,
 | |
|     array_split rkeepfs "$rkeepf" ,
 | |
| 
 | |
|     # fusionner les deux fichiers
 | |
|     local tmpmerged
 | |
|     ac_set_tmpfile tmpmerged "" __mergecsv_merged "" __MERGECSV_DEBUG
 | |
|     csort -s -k 1,$(($padlen + 1)) "$tmpleft" "$tmpright" >"$tmpmerged"
 | |
| 
 | |
|     <"$tmpmerged" awkrun -f \
 | |
|         padlen:int="$padlen" \
 | |
|         parse_headers:int="$parse_headers" ignore_case:int="$ignore_case" \
 | |
|         lheaderscsv="$lheaders" lkey="$lkey" lprefix="$lprefix" \
 | |
|         rheaderscsv="$rheaders" rkey="$rkey" rprefix="$rprefix" \
 | |
|         select="$select" postproc:int="$postproc" \
 | |
|         lcopyfs[@] rcopyfs[@] \
 | |
|         lkeepfs[@] rkeepfs[@] \
 | |
|         "$__AWKCSV_FUNCTIONS"'
 | |
| function lgeth(field,                nbfields, i) {
 | |
|   nbfields = array_len(lheaders)
 | |
|   if (int(field) == field) {
 | |
|     field = int(field)
 | |
|     if (field >= 1 && field <= nbfields) return field
 | |
|     else return 0
 | |
|   }
 | |
|   field = tolower(field)
 | |
|   for (i = 1; i <= nbfields; i++) {
 | |
|     if (field == tolower(lheaders[i])) {
 | |
|       return i
 | |
|     }
 | |
|   }
 | |
|   return 0
 | |
| }
 | |
| function rgeth(field,                nbfields, i) {
 | |
|   nbfields = array_len(rheaders)
 | |
|   if (int(field) == field) {
 | |
|     field = int(field)
 | |
|     if (field >= 1 && field <= nbfields) return field
 | |
|     else return 0
 | |
|   }
 | |
|   field = tolower(field)
 | |
|   for (i = 1; i <= nbfields; i++) {
 | |
|     if (field == tolower(rheaders[i])) {
 | |
|       return i
 | |
|     }
 | |
|   }
 | |
|   return 0
 | |
| }
 | |
| function copyf(lfields, rfields,        i, fs, vs, l, r) {
 | |
|   for (i = 1; i <= lcopyfs_count; i++) {
 | |
|     fs = lcopyfs[i]
 | |
|     match(fs, /(.*):(.*)/, vs)
 | |
|     l = vs[1]; l = lgeth(l)
 | |
|     r = vs[2]; r = rgeth(r)
 | |
|     if (l && r) {
 | |
|       lfields[l] = rfields[r]
 | |
|     }
 | |
|   }
 | |
|   for (i = 1; i <= rcopyfs_count; i++) {
 | |
|     fs = rcopyfs[i]
 | |
|     match(fs, /(.*):(.*)/, vs)
 | |
|     l = vs[2]; l = lgeth(l)
 | |
|     r = vs[1]; r = rgeth(r)
 | |
|     if (l && r) {
 | |
|       rfields[r] = lfields[l]
 | |
|     }
 | |
|   }
 | |
| }
 | |
| function keepf(lfields, rfields,        i) {
 | |
|   for (i = lskipfs_count; i >= 1; i--) {
 | |
|     array_deli(lfields, lskipfs[i])
 | |
|   }
 | |
|   for (i = rskipfs_count; i >= 1; i--) {
 | |
|     array_deli(rfields, rskipfs[i])
 | |
|   }
 | |
| }
 | |
| function printmerged(lline, rline, nocopy,        linecsv, tmplinecsv) {
 | |
|   if (lline != "") array_parsecsv(lfields, lline, array_len(lheaders))
 | |
|   else array_newsize(lfields, array_len(lheaders))
 | |
|   if (rline != "") array_parsecsv(rfields, rline, array_len(rheaders))
 | |
|   else array_newsize(rfields, array_len(rheaders))
 | |
|   if (postproc) {
 | |
|     if (!nocopy) copyf(lfields, rfields)
 | |
|     keepf(lfields, rfields)
 | |
|   }
 | |
|   linecsv = array_formatcsv(lfields)
 | |
|   tmplinecsv = array_formatcsv(rfields)
 | |
|   if (tmplinecsv != "") {
 | |
|     if (linecsv != "") linecsv = linecsv ","
 | |
|     linecsv = linecsv tmplinecsv
 | |
|   }
 | |
|   print linecsv
 | |
| }
 | |
| BEGIN {
 | |
|   if (parse_headers) {
 | |
|     array_parsecsv(lheaders, lheaderscsv)
 | |
|     lheaders_count = array_len(lheaders)
 | |
|     if (lprefix != "") {
 | |
|       for (i = 1; i <= lheaders_count; i++) {
 | |
|         lheaders[i] = lprefix lheaders[i]
 | |
|       }
 | |
|       lheaderscsv = array_formatcsv(lheaders)
 | |
|     }
 | |
|     lkey = lgeth(lkey)
 | |
|     if (!lkey) lkey = 1
 | |
| 
 | |
|     array_parsecsv(rheaders, rheaderscsv)
 | |
|     rheaders_count = array_len(rheaders)
 | |
|     if (rprefix != "") {
 | |
|       for (i = 1; i <= rheaders_count; i++) {
 | |
|         rheaders[i] = rprefix rheaders[i]
 | |
|       }
 | |
|       rheaderscsv = array_formatcsv(rheaders)
 | |
|     }
 | |
|     rkey = rgeth(rkey)
 | |
|     if (!rkey) rkey = 1
 | |
|   }
 | |
|   LEFT = 1
 | |
|   RIGHT = 2
 | |
|   hasleft = 0
 | |
| 
 | |
|   # quelle sélection effectuer?
 | |
|   selectjoin = select ~ /none|inner-join|left-join|right-join/
 | |
|   selectleft = select ~ /none|left-join|left-only/
 | |
|   selectright = select ~ /none|right-join|right-only/
 | |
| 
 | |
|   # liste des indexes de champs a supprimer
 | |
|   array_new(lskipfs)
 | |
|   if (!in_array("*", lkeepfs)) {
 | |
|     for (i = 1; i <= lheaders_count; i++) {
 | |
|       field = lheaders[i]
 | |
|       if (!in_array(field, lkeepfs)) {
 | |
|         fieldi = lgeth(field)
 | |
|         if (i != 0) array_add(lskipfs, fieldi)
 | |
|       }
 | |
|     }
 | |
|     asort(lskipfs)
 | |
|   }
 | |
|   lskipfs_count = array_len(lskipfs)
 | |
| 
 | |
|   array_new(rskipfs)
 | |
|   if (!in_array("*", rkeepfs)) {
 | |
|     for (i = 1; i <= rheaders_count; i++) {
 | |
|       field = rheaders[i]
 | |
|       if (!in_array(field, rkeepfs)) {
 | |
|         fieldi = rgeth(field)
 | |
|         if (i != 0) array_add(rskipfs, fieldi)
 | |
|       }
 | |
|     }
 | |
|     asort(rskipfs)
 | |
|   }
 | |
|   rskipfs_count = array_len(rskipfs)
 | |
|   
 | |
|   if (parse_headers) {
 | |
|     # afficher les en-têtes après traitement de lkeepfs et rkeepfs, parce que
 | |
|     # printmerged() a besoin de lskipfs et rskipfs
 | |
|     printmerged(lheaderscsv, rheaderscsv, 1)
 | |
|   }
 | |
| }
 | |
| function readleft() {
 | |
|   lprefix = substr($0, 1, padlen)
 | |
|   lwhich = substr($0, padlen + 1, 1)
 | |
|   if (lwhich == "1") lwhich = LEFT; else lwhich = RIGHT
 | |
|   lline = substr($0, padlen + 2)
 | |
|   hasleft = 1
 | |
| }
 | |
| function readright() {
 | |
|   rprefix = substr($0, 1, padlen)
 | |
|   rwhich = substr($0, padlen + 1, 1)
 | |
|   if (rwhich == "1") rwhich = LEFT; else rwhich = RIGHT
 | |
|   rline = substr($0, padlen + 2)
 | |
| }
 | |
| function right2left() {
 | |
|   lprefix = rprefix
 | |
|   lwhich = rwhich
 | |
|   lline = rline
 | |
|   hasleft = 1
 | |
| }
 | |
| !hasleft {
 | |
|   readleft()
 | |
|   next
 | |
| }
 | |
| {
 | |
|   readright()
 | |
|   if (lprefix == rprefix && lwhich == LEFT && rwhich == RIGHT) {
 | |
|     if (selectjoin) printmerged(lline, rline)
 | |
|     hasleft = 0
 | |
|     next
 | |
|   } else {
 | |
|     if (lwhich == LEFT && selectleft) {
 | |
|       printmerged(lline, "")
 | |
|     } else if (lwhich == RIGHT && selectright) {
 | |
|       printmerged("", lline)
 | |
|     }
 | |
|     right2left()
 | |
|     next
 | |
|   }
 | |
| }
 | |
| END {
 | |
|   if (hasleft) {
 | |
|     if (lwhich == LEFT && selectleft) {
 | |
|       printmerged(lline, "")
 | |
|     } else if (lwhich == RIGHT && selectright) {
 | |
|       printmerged("", lline)
 | |
|     }
 | |
|   }
 | |
| }
 | |
| '
 | |
| 
 | |
|     ac_clean "$tmpleft" "$tmpright" "$tmpmerged"
 | |
|     return 0
 | |
| }
 | |
| 
 | |
| function cmergecsv() { LANG=C lmergecsv "$@"; }
 | |
| function mergecsv() { LANG=C lmergecsv "$@"; }
 | |
| 
 | |
| ################################################################################
 | |
| 
 | |
| __SORTCSV_HELP="\
 | |
| Trier un fichier csv sur la valeur d'un champ
 | |
| 
 | |
| --skip nblines
 | |
|     Sauter nblines au début du flux
 | |
| -h, --parse-headers
 | |
|     Lire la liste des champs à partir de la première ligne non ignorée des flux.
 | |
|     Si cette option est spécifiée (ce qui est le cas par défaut), le champ
 | |
|     spécifié avec l'option -k peut être le nom effectif du champ. Sinon, le
 | |
|     champ ne peut être que numérique.
 | |
| --numkeys
 | |
|     Ne pas analyser la première ligne pour les noms des champs. Les champs
 | |
|     spécifiés ne peuvent être que numériques.
 | |
| -k, --key FIELD
 | |
|     Spécifier le champ utilisé pour le tri. Si --parse-headers n'est pas
 | |
|     spécifié, cette valeur doit être numérique. La valeur par défaut est 1.
 | |
| --no-headers
 | |
|     Ne pas afficher les en-têtes en sortie.
 | |
| 
 | |
| -n, --numeric-sort
 | |
|     Comparer selon la valeur numérique du champ
 | |
| -i, -f, --ignore-case
 | |
|     Comparer sans tenir compte de la casse
 | |
| -r, --reverse
 | |
|     Inverser l'ordre de tri
 | |
| -s, --stable
 | |
|     Stabiliser le tri en inhibant la comparaison de dernier recours
 | |
| -u, --unique
 | |
|     Ne garder que la première occurence de plusieurs entrées identiques
 | |
|     rencontrées. Note: la correspondance se fait sur toute l'entrée, pas
 | |
|     uniquement sur la valeur de la clé.
 | |
| -o, --output OUTPUT
 | |
|     Ecrire le résultat dans OUTPUT au lieu de la sortie standard"
 | |
| 
 | |
| : "${__SORTCSV_DEBUG:=}"
 | |
| function lsortcsv() {
 | |
|     # Trier le fichier csv $1. La clé du tri est spécifiée par l'option -k et
 | |
|     # vaut 1 par défaut. Les valeurs des clés ne doivent pas faire plus de 64
 | |
|     # caractères de long.
 | |
|     eval "$(utools_local)"
 | |
|     local skip=0 parse_headers=auto key=1 show_headers=1
 | |
|     local numeric_sort= ignore_case= reverse_sort= stable_sort= unique_sort= output=
 | |
|     parse_opts "${PRETTYOPTS[@]}" \
 | |
|         --skip: skip= \
 | |
|         -h,--parse-headers parse_headers=1 \
 | |
|         --numkeys parse_headers= \
 | |
|         -k:,--key: key= \
 | |
|         --no-headers show_headers= \
 | |
|         --show-headers show_headers=1 \
 | |
|         -n,--numeric-sort numeric_sort=1 \
 | |
|         -i,-f,--ignore-case ignore_case=1 \
 | |
|         -r,--reverse reverse_sort=1 \
 | |
|         -s,--stable stable_sort=1 \
 | |
|         -u,--unique unique_sort=1 \
 | |
|         -o:,--output: output= \
 | |
|         @ args -- "$@" && set -- "${args[@]}" || die "$args"
 | |
| 
 | |
|     local input="$1"
 | |
|     if [ -z "$input" -o "$input" == "-" ]; then
 | |
|         input=/dev/stdin
 | |
|     elif [ ! -f "$input" ]; then
 | |
|         eerror "$input: fichier introuvable"
 | |
|         return 1
 | |
|     fi
 | |
| 
 | |
|     local padding="----------------------------------------------------------------"
 | |
|     local padlen=${#padding}
 | |
|     local headers
 | |
| 
 | |
|     local -a tmpfiles
 | |
| 
 | |
|     [ "$parse_headers" == "auto" ] && parse_headers=1
 | |
|     if [ -n "$parse_headers" -a -z "$headers" ]; then
 | |
|         if [ "$input" == /dev/stdin ]; then
 | |
|             # Si on lit depuis stdin, il faut faire une copie du flux dans un
 | |
|             # fichier temporaire pour calculer les en-têtes
 | |
|             local tmpinput
 | |
|             ac_set_tmpfile tmpinput "" __sortcsv_input0 "" __SORTCSV_DEBUG
 | |
|             array_add tmpfiles "$tmpinput"
 | |
|             cat >"$tmpinput"
 | |
|             input="$tmpinput"
 | |
|         fi
 | |
|         headers="$(<"$input" awkrun skip:int="$skip" 'NR <= skip { next } { print; exit }')"
 | |
|     fi
 | |
| 
 | |
|     # faire le fichier de travail
 | |
|     local tmpinput
 | |
|     ac_set_tmpfile tmpinput "" __sortcsv_input "" __SORTCSV_DEBUG
 | |
|     array_add tmpfiles "$tmpinput"
 | |
|     <"$input" >"$tmpinput" awkrun -f \
 | |
|         padding="$padding" padlen:int="$padlen" \
 | |
|         skip:int="$skip" parse_headers:int="$parse_headers" \
 | |
|         key="$key" \
 | |
|         "$__AWKCSV_FUNCTIONS"'
 | |
| NR <= skip { next }
 | |
| parse_headers && do_once("parse-headers") {
 | |
|   array_parsecsv(HEADERS, $0)
 | |
|   key = geth(key)
 | |
|   if (!key) key = 1
 | |
|   next
 | |
| }
 | |
| {
 | |
|   oline = $0
 | |
|   parsecsv($0)
 | |
|   keyvalue = substr($key, 1, padlen)
 | |
|   print keyvalue substr(padding, 1, padlen - length(keyvalue)) oline
 | |
| }
 | |
| '
 | |
| 
 | |
|     # trier le fichier de travail
 | |
|     local tmpsorted
 | |
|     ac_set_tmpfile tmpsorted "" __sortcsv_sorted "" __SORTCSV_DEBUG
 | |
|     array_add tmpfiles "$tmpsorted"
 | |
|     args=(# arguments de sort
 | |
|         ${numeric_sort:+-n} ${ignore_case:+-f}
 | |
|         ${reverse_sort:+-r} ${stable_sort:+-s}
 | |
|         ${unique_sort:+-u}
 | |
|     )
 | |
|     csort -k 1,$(($padlen + 1)) "${args[@]}" <"$tmpinput" >"$tmpsorted"
 | |
| 
 | |
|     # résultat
 | |
|     [ -n "$output" ] || output=/dev/stdout
 | |
|     <"$tmpsorted" >"$output" awkrun -f \
 | |
|         padlen:int="$padlen" \
 | |
|         headerscsv="$headers" show_headers:int="$show_headers" \
 | |
|         '
 | |
| BEGIN { if (show_headers) print headerscsv }
 | |
| { print substr($0, padlen + 1) }
 | |
| '
 | |
| 
 | |
|     ac_clean "${tmpfiles[@]}"
 | |
|     return 0
 | |
| }
 | |
| 
 | |
| function csortcsv() { LANG=C lsortcsv "$@"; }
 | |
| function sortcsv() { LANG=C lsortcsv "$@"; }
 |