#!/bin/bash
#-------------------------------------------------------------------------------------------
#<Beschreibung>
#  Funktion 1: Merge (mehrere korrespondierende Key-Spalten in beiden Dateien, 
#  1-n Value-Spalten in Datei 1, eine Zielspalte in Datei 2) => Ausgabe in Datei 2_merged.csv oder explizite Ausgabedatei)
#    Merged Spalten einer csv-Datei in eine andere csv-Datei, sofern beide Dateien eine oder 
#    mehrere gemeinsame Key-Spalte(n) besitzen
#      - Sonderfunktion: Ist die Value-Spalte aus Datei 1 '999' wird in Datei 2 hinter deren Zielspalte nur ausgegeben, ob
#        die Key-Kombination in Datei 1 vorhanden war.
#  Funktion 2: Comparison (mehrere Key-Spalten, mehrere Value-Spalten in Datei 1 und Datei 2 => Ausgabe in diff.csv)
#    Schreibt hinter die Value-Spalten aus Datei 2
#      ob die Key-Kombination in der ersten Datei enthalten war,
#      genau dann jeweils
#        die korrespondierenden values uebereinstimmten oder abwichen
#  Cave: Nicht in Datei 2 gefundene keys aus Datei 1 (und umgekehrt) werden auf stderr ausgegeben (ggf. umleiten)
#</Beschreibung>
#-------------------------------------------------------------------------------------------

. /usr/local/bins/common

function usage(){ #{{{{
  cat <<EOF

     $0 -i(nsert) <csv1>:<keycol(s)>:<valuecol(s)> -o(ut) <csv2>:<keycol(s)>:<targetColFile2> [-c(oncatenate|-r(eplace)|-R(eplaceforce)] [-n(oconvert)] [<outfile>]
     $0 -i(nsert) <csv1>:<keycol(s)>:<valuecol(s)> -o(ut) <csv2>:<keycol(s)>:<valuecol(s)> -C(ompare) [-m(atchonly] [-n(oconvert] [<outfile>]

     Cave: Option -c => concatenate (bei merge)
           Option -C => compare (anstelle merge)

     Cave: Nicht in Datei 2 vorhandene Keys aus Datei 1 (und umgekehrt) werden auf stderr ausgegeben (ggf. umleiten)

     Funktion 1: Merge
       - mehrere key-Spalten in Datei 1 und Datei 2 (werden zusammengefasst)
       - mehrere value-Spalten in Datei 1 (werden zusammengefasst)
       - eine Zielspalte in Datei 2
         - einfuegen dahinter oder Konkatenierung mit dieser
       - Ausgabe der erweiterten Datei 2 in Datei 2_merged.csv oder <outfile>

       $0 -i(nsert) <csv1>:<keycol(s)>:<valuecol(s)> -o(ut) <csv2>:<keycol(s)>:<targetColFile2> [-c(oncatenate|-r(eplace)|-R(eplaceforce)] [-n(oconvert)]

       -n: Keine Konvertierung (Booster auf eigene Gefahr !)

       Fuegt die Werte aus Spalten der ersten hinter oder in die Spalte einer zweiten csv-Datei ein.

       <keycol(s)> ist dabei entweder die (bezueglich Auspraegungen) gemeinsame Schluessel-Spalte beider Dateien fuer
       die Zuordnung der Werte aus <valuecol(s)> oder ein Ausdruck fuer mehrere jeweils gemeinsame Schluessel-Spalten der Form
         <Zahl>u(nd)<Zahl>u(nd)<Zahl>...

       Ebenso bezeichnet <valuecol(s)> entweder eine einzige oder mehrere Spalten <Zahl>u(nd)<Zahl>

       Bei Option -c wird die Spalte mit der Zielspalte konkateniert, anderfalls dahinter eingefuegt
       Bei Option -r wird die Spalte in die Zielspalte eingefuegt und ueberschreibt ggf. dortige Leer-Werte
       Bei Option -R werden im Unterschied zu -r auch bestehende Werte der Zielspalte ueberschrieben

       Convenience: 
         Ist die Zielspalte "hinten", wird hinter der letzten Spalte in csv2 eingefuegt.
         Ist die Zielspalte "vorne", wird vor der ersten Spalte in csv2 eingefuegt.
           Cave: Letzeres ist die einzige Moeglichkeit hierzu da '0' in awk \$0 entspricht und alle Spalten referenziert

       Beispiel:
         
         $0 -i Steinbruch.csv:1:2 -o Zieldatei.csv:1:5 -c
           
           Key:    jeweils Spalte 1 
           Value:  Spalte 2 in erster Datei
           Target: Spalte 5 in zweiter Datei (Konkatenierung)
                   Bei alternativer Option -r wuerde Spalte 5 ueberschrieben

         $0 -i Steinbruch.csv:1u3:2 -o Zieldatei.csv:2u4:5 

            Key: Spalten 1 und 3 in erster und 2 und 4 in zweiter Datei
                 Einfuegen der Spalte 2 der ersten Datei nach Spalte 5 der zweiten Datei (keine Konkatenierung)

         $0 -i Steinbruch.csv:1u3u9:2u4u7 -o Zieldatei.csv:2u3u4:vorne 

            Key: Spalten 1,3,9 in erster und 2,3,4 in zweiter Datei
                 Einfuegen der Spalten 2,4,7 der ersten Datei vor erster Spalte der zweiten Datei (implizit/logisch keine Konkatenierung)

         $0 -i Steinbruch.csv:1u3u9:2u4u7 -o Zieldatei.csv:2u3u4:hinten

            dito, jedoch automatische Bestimmung der Zielspalte

       Cave: Sofern keine Ausgabedatei angegeben war, ist Ausgabedatei ist immer *_merged.csv; selbst wenn die Eingabedatei bereits "merged" war ...

       Besonderheit: Ist die Value-Spalte aus Datei 1 '999' wird in Datei 2 hinter (oder in) deren Zielspalte nur ausgegeben, ob
                     die Key-Kombination in Datei 1 vorhanden war.

     Funktion 2: Comparison (Option -C)
       - mehrere Key-Spalten in Datei 1 und Datei 2 (werden zusammengefasst)
       - mehrere Value-Spalten in Datei 1 und Datei 2 (werden NICHT zusammengefallst, sondern getrennt betrachtet)
       - Markierung fehlender oder Einfuegen abweichender Werte genau hinter die jeweilige value-Spalte in Datei 2
       - Ausgabe in diff.csv
         Cave: Nur key- und value-Spalten beider Dateien werden ausgegeben

       $0 -i(nsert) <csv1>:<keycol(s)>:<valuecol(s)> -o(ut) <csv2>:<keycol(s)>:<valuecol(s)> -C(ompare) [-m(atchonly] [-n(oconvert]
       
        -C: Unterschied zu Funktion 1 => compare anstelle merge
        -m: Ausgabe von 'match' bei Uebereinstimmung, sonst des abweichenden Wertes bei Daten aus Datei 1
            Cave: Fuer Match werden Werte normiert 

       Beispiel
         
         $0 -i Referenz.csv:1u2u3:4u5u6 -o Pruefling.csv:2u1u3:5u6u7 -C 

EOF
} #}}}}
#{{{{ variables
dummy=""
col=""
csv1=""
csv2=""
keyColFile1=""
keyColFile2=""
valColFile1=""
targetColFile2=""
insert=""
tmp="dummy.csv"
csvMerged=""
csvDiff="diff.csv"
compare=""
matchOnly=""
noConvert=""
outfile=""
#}}}}
#{{{{ getOpts
while getopts "CchHi:o:mnrR" opt; do
  case $opt in
    c) insert="concatenate" ;;
    C) compare="yes" ;;
    h) usage; exit ;;
    i) csv1="${OPTARG%%:*}" 
       valColFile1="${OPTARG##*:}" 
       dummy="${OPTARG#*:}"
       keyColFile1="${dummy%:*}"
       for col in "$keyColFile1" "$valColFile1"; do
         [[ ! "$col" =~ [0-9u] ]] && die "csv1: Konnte Parameter $OPTARG nicht zerlegen" 
       done 
       [ "$keyColFile1" == "$valColFile1" ] && die "Key- und Value-Spalte der ersten Datei duerfen nicht uebereinstimmen" ;;
    m) matchOnly="yes" ;;
    o) csv2="${OPTARG%%:*}" 
       targetColFile2="${OPTARG##*:}" 
       dummy="${OPTARG#*:}"
       keyColFile2="${dummy%:*}"
       for col in "$keyColFile2" "$targetColFile2"; do
         if [[ ! "$col" =~ [0-9u] ]]; then
           if [ "$col" != "hinten" -a "$col" != "vorne" ]; then                          # ja, waere fuer $keyColFile2 oder bei Option "compare" falsch, ist aber verschmerzbar ;-)
             die "csv2: Konnte Parameter $OPTARG nicht zerlegen" 
           fi
         fi
       done  ;;
    n) noConvert="j" ;;
    r) insert="replace" ;;
    R) insert="replaceforce" ;;
    *) die "Keine gueltige Option" ;;
  esac
done
shift $(( OPTIND -1 ))
outfile="$1";
#}}}}
#{{{{ verification
[ -z "$csv1" -o -z "$csv2" ] && die "Bitte 2 csv-Dateien angeben"
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
for file in "$csv1" "$csv2"; do
  [[ ! "$file" =~ .csv$ ]] && die "$file ist keine csv-Datei"
  verifyFile "$file"
  [ -z "$noConvert" ] && convertReplaceUml "$file"
done
[[ "$keyColFile1" =~ u ]] && [[ ! "$keyColFile2" =~ u ]] && die "Fuer die zweite Datei muessen ebenfalls mehrere Schluessel-Spalten angegeben werden"
[[ ! "$keyColFile1" =~ u ]] && [[ "$keyColFile2" =~ u ]] && die "Die zweite Datei darf ebenfalls nur eine Schluessel-Spalte besitzen"
if [ -n "$outfile" ]; then
  csvMerged="$outfile";
elif [[ "$csv2" =~ merged ]]; then
  csvMerged="$csv2"
else
  csvMerged="${csv2%.csv}"_merged.csv
fi
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
cat <<EOF
  Datei 1            : $csv1
  Key 1              : $keyColFile1
  Value 1            : $valColFile1
  Datei 2            : $csv2
  Key 2              : $keyColFile2
  Zielspalte/Value 2 : $targetColFile2
EOF
#}}}}
#{{{{ awk
/usr/bin/awk -F ";" -v csv1="$csv1" -v keyColFile1="$keyColFile1" -v valColFile1="$valColFile1" -v keyColFile2="$keyColFile2" -v targetColFile2="$targetColFile2" -v insert="$insert" -v compare="$compare" -v matchOnly="$matchOnly" '
@include "/usr/local/bins/commonroutines.awk"
BEGIN{ #{{{{
  getResourceNorming();  # Muster fuer Ressourcenmappings einlesen fuer den Fall, dass Spalten Ressourcen entsprechen
  #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  # Ausgabe-Trennzeichen fuer keys und values festlegen
  #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  keySeparator=" -- ";
  if(compare == "yes"){
    valSeparator="||";
  }
  else{
    valSeparator =" -- ";
  }
  emptyVal="";  # Cave: Hochkommata wuerden ggf. folgende csv2csv-Wandlung behindern (Cave: Zuvor war "<>" gesetzt; am 27.09.23 auf "" geaendert
  notFoundVal="-";
  #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  # 1 oder mehrere Value-Spalten in Datei 1 ?
  #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  if(valColFile1 ~ /u/){
    numValColsFile1=split(valColFile1,valColArrFile1,"u");
  }
  else{
    numValColsFile1=1;
    valColArrFile1[1]=valColFile1;
  }
  #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  # 1 oder mehrere Key-Spalten in Datei 1 ?
  #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  if(keyColFile1 ~ /u/){
    numKeyColsFile1=split(keyColFile1,keyColArrFile1,"u");
  }
  else{
    numKeyColsFile1=1;
    keyColArrFile1[1]=keyColFile1;
  }
  #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  # 1 oder mehrere Key-Spalten in Datei 2 ?
  #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  if(keyColFile2 ~ /u/){
    numKeyColsFile2=split(keyColFile2,keyColArrFile2,"u");
    if(numKeyColsFile1 != numKeyColsFile2){
      die("Anzahl der Key-Spalten in Datei 1 und 2 weichen ab");
    }
  }
  else{
    numKeyColsFile2=1;
    keyColArrFile2[1]=keyColFile2;
  }
  #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  # Hash der Key-Spalten und Value-Spalten fuer Datei 1 aufbauen
  #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  while(getline < csv1){
    sub("\r","",$0);  # Newline entfernen
    key="";
    for(i=1;i<=numKeyColsFile1;i++){
      # key=(key != "") ? key keySeparator normString($(keyColArrFile1[i])) : normString($(keyColArrFile1[i]));
      key=(key != "") ? key keySeparator $(keyColArrFile1[i]) : $(keyColArrFile1[i]);
    }
    if(key){
      #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      # Sonderfunktion "999" => Kein Spaltenuebertrag sondern nur Markierung eines Matches in/hinter
      #                         Zielspalte in Datei 2
      #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      if((numValColsFile1 == 1) && (valColArrFile1[1] == "999")){
        tmpVal="Match";
      }
      #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      # Regulaere Funktion => Werte der value-Spalten sammeln
      #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      else{
        tmpVal="";
        for(i=1;i<=numValColsFile1;i++){
          dummy=$(valColArrFile1[i]);
          dummy=(dummy ~ /^[[:space:]]*$/) ? emptyVal : dummy;
          tmpVal=(tmpVal != "") ? tmpVal valSeparator dummy : dummy; 
        }
      }
      valHash[key]=tmpVal;
    }
  }
  #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  lastColInFile2=0;
  #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  # Debugging
  #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#  for(key in valHash){
#    printf("Key: %s - Value: %s\n",key,valHash[key]);
#  }
} #}}}}
#################################################################################################
#---------------------------------------------------------------------------------------------
# Ausgabe der zweiten Datei mit Anreicherungen aus erster Datei oder Ausgabe einer Vergleichsdatei
#---------------------------------------------------------------------------------------------
{ #{{{{
  #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  # Letzte Spalte bestimmen 
  # Feldzahl muss ueber alle Zeilen konstant sein
  #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  if(NR == 1){
    lastColInFile2=NF;
  }
  else if(NF != lastColInFile2){
    die("Zeile "NR": Spaltenzahl "NF" weicht von Spaltenzahl der ersten Zeile "lastColInFile2" ab");
  }
  #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  if((compare != "yes") && (! (targetColFile2 ~ /(vorne|hinten)/)) && (targetColFile2 > NF)){
    print("Zielspalte " targetColFile2 " ueberschreitet Feldzahl " NF " auf Zeile " FNR) >> "/dev/stderr" ;
    next;
  }
  sub("\r","",$0);  # Newline entfernen
  key="";
  for(i=1;i<=numKeyColsFile2;i++){
    # key=key ? key keySeparator normString($(keyColArrFile2[i])) : normString($(keyColArrFile2[i]));
    key=key ? key keySeparator $(keyColArrFile2[i]) : $(keyColArrFile2[i]);
  }
  #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  # Fuer evtl. zusammengesetzten key value aus Datei 1 ermitteln
  #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  if(key in valHash){
    val=valHash[key];
    usedHash[key]="yes";
  }
  else{
    val="";
    # failureHash[key]="yes";
    failureHash[key]=$0;
  }
  #-------------------------------------------------------------------------------------------
  # Compare ?
  #-------------------------------------------------------------------------------------------
  if(compare == "yes"){
    valColFile2=targetColFile2; # Zielspalte ist nun der falsche Begriff => value-Spalte
    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # 1 oder mehrere value-Spalten ?
    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    if(valColFile2 ~ /u/){
      numValColsFile2=split(valColFile2,valColArrFile2,"u");
    }
    else{
      numValColsFile2=1;
      valColArrFile2[1]=valColFile2;
    }
    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    if(numValColsFile1 != numValColsFile2){
      die("mismatch of number of value-columns Datei 1: " numValColsFile1 " <=> Datei 2: " numValColsFile2);
    }
    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # Key-Spalten ausgeben
    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    for(i=1;i<=numKeyColsFile2;i++){
      printf("%s;",$(keyColArrFile2[i]));
    }
    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # Value-Spalten "interleaved" ausgeben, auf Wunsch jedoch Werte aus Datei 1 nur, sofern kein Match
    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    numValsFile1=split(val,valArrFile1,valSeparator);
    for(i=1;i<=numValColsFile2;i++){
      #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      # Value aus Datei 2
      # Cave: normedValFile2 historische Bezeichnung, als Wert noch normiert wurde
      #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      dummy=$(valColArrFile2[i]);
      dummy=(dummy ~ /^[[:space:]]*$/) ? emptyVal : dummy;
      printf("%s;",dummy);
      # normedValFile2=normString($(valColArrFile2[i]));
      normedValFile2=$(valColArrFile2[i]);
      #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      # Value aus Datei 1
      # Cave: normedValFile1 historische Bezeichnung, als Wert noch normiert wurde
      #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      if(numValsFile1 >= i){
        #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        # Nur Match-Kennzeichen und Wert lediglich bei Abweichung ausgeben ?
        #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        if(matchOnly == "yes"){
          # normedValFile1=normString(valArrFile1[i]);
          normedValFile1=valArrFile1[i];
          if(normedValFile2 != normedValFile1){
            printf("%s;",valArrFile1[i]);
          }
          else{
            printf("match;");
          }
        }
        #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        # Nicht Match-Pruefung, sondern Wert ausgeben
        #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        else{
          printf("%s;",valArrFile1[i]);
        }
      }
      else{
        printf("%s;",notFoundVal);
      }
    }
    printf("\n"); 
  }
  #-------------------------------------------------------------------------------------------
  # Merge
  #-------------------------------------------------------------------------------------------
  else{
    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # Virtuelle Zielspalte "0" => alle zu uebertragenden Werte der ersten Datei als erstes ausgeben
    #                             , anschliessend alle Werte der zweiten Datei hinten anhaengen
    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    if(targetColFile2 == "vorne"){
      printf("%s;",val)                     # Wert oder Werte aus csv1
      for(i=1;i<NF;i++){
        printf("%s;",$i);                   # Alle Zeilenwerte bis auf den letzten aus csv2
      }
      printf("%s\n",$NF);                  # Letzte Spalte ohne Semikolon
      next;
    }
    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # Zielspalte aus ermittelter Gesamtspaltenzahl ermitteln ?
    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    if(targetColFile2 == "hinten"){
      targetColFile2=lastColInFile2;
    }
    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # Felder vor Zielspalte ausgeben
    # Cave:
    #   Falls die Zielspalte ausserhalb der letzten Zeilenspalte liegt, muessen Leerspalten
    #   eingefuegt werden.
    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    if(targetColFile2 <= NF){
      for(i=1;i<targetColFile2;i++){
        printf("%s;",$i);
      }
    }
    #-   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   
    # Zielspalte liegt ausserhalb der letzten Zeilenspalte => 
    #   alle Nutzspalten und anschliessend Leerspalten bis vor Zielspalte ausgeben
    #-   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   
    else{
      for(i=1;i<=NF;i++){
        printf("%s;",$i);
      }
      for(i=NF+1;i<targetColFile2;i++){
        printf("%s;","");
      }
    }
    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # Entweder merge oder replace oder nach Zielspalte ausgeben
    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    if(insert ~ /(concatenate|replace)/){
      newVal="";
      #-------------------------------------------------------------------------------------
      # Verkettung ?
      #-------------------------------------------------------------------------------------
      if(insert == "concatenate"){
        val=(val) ? val : emptyVal;
        newVal=$targetColFile2 valSeparator val;
      }
      #-------------------------------------------------------------------------------------
      # Ersetzung bei Leerwert (sofern Ersetzung nicht selbst ein Leerwert ist) ?
      #-------------------------------------------------------------------------------------
      else if(insert == "replace"){
        if(trueVal(val)){
          newVal=(trueVal($targetColFile2)) ? $targetColFile2 : val;   # nur etwaigen Leer- oder Nullwert ueberschreiben
        }
        else{
          newVal=$targetColFile2;                                      # bisherigen Wert beibehalten, da Ersetzung nutzlos
        }
      }
      #-------------------------------------------------------------------------------------
      # Zwingende Ersetzung  (sofern Ersetzungswert vorhanden, d.h. Key-Match zuvor) ?
      #-------------------------------------------------------------------------------------
      else if(insert == "replaceforce"){
        newVal=(val != "") ? val : $targetColFile2;
      }
      else{
        die("Internal Error: Ungueltiger Wert fuer insert: "insert);
      }
      #-------------------------------------------------------------------------------------------------------
      # Ausgabe von Verkettung oder Ersetzung (oder altem Wert)
      #-------------------------------------------------------------------------------------------------------
      #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      # Zielspalte vor letztem Feld ?
      #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      if(targetColFile2 < NF){
        printf("%s;",newVal);                      # Ausgabe mit Semikolon, da nicht letzte Spalte
        #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        # restliche Felder ausgeben 
        #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        for(i=targetColFile2+1;i<NF;i++){          # Ausgabe bis einschliesslich der vorletzten Spalte (auch nichts)
          printf("%s;",$i);
        }
        printf("%s\n",$NF);                       # Letzte Spalte ohne Semikolon
      }
      #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      # Zielspalte == letztes Feld
      #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      else{
        printf("%s\n",newVal);                     # Abschluss, da letzte Spalte
      }
    }
    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # keine Verkettung, sondern Hintanfuegen
    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    else{
      if(targetColFile2 < NF){
        printf("%s;%s;",$targetColFile2,val);      # Ausgabe mit Semikolon, da nicht letzte Spalte
        #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        # restliche Felder ausgeben 
        #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        for(i=targetColFile2+1;i<NF;i++){          # Ausgabe bis einschliesslich der vorletzten Spalte (auch nichts)
          printf("%s;",$i);
        }
        printf("%s\n",$NF);                       # Letzte Spalte ohne Semikolon
        #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      }
      else{
        printf("%s;%s\n",$targetColFile2,val);   # Abschluss, da letzte Spalte
      }
    }
  }
} #}}}}
#---------------------------------------------------------------------------------------------
# Verifikation, ob alle potentiellen Anreicherungen verwertet wurden
#---------------------------------------------------------------------------------------------
END{ #{{{{
  for(key in valHash){
    if(! (key in usedHash)){
      gsub(keySeparator,";",key)   # Key-Folge als csv-Folge schreiben
      printf("Ueberhang_Datei_1;%s\n",key) > "/dev/stderr";
    }
  }
  #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  for(key in failureHash){
    gsub(keySeparator,";",key)   # Key-Folge als csv-Folge schreiben
    printf("Ueberhang_Datei_2;%s\n",key) > "/dev/stderr";
  }
} #}}}}
#---------------------------------------------------------------------------------------------
' < "$csv2" > $tmp
checkReturn "awk-Fehler"
#}}}}
#-------------------------------------------------------------------------------------------
if [ -n "$compare" ]; then
  mv "$tmp" "$csvDiff"
  checkReturn "mv-Fehler"
  [ -z "$noConvert" ] && convertReplaceUml "$csvDiff"
  commonOut "=> $csvDiff"
else
  mv "$tmp" "$csvMerged"
  checkReturn "mv-Fehler"
  [ -z "$noConvert" ] && convertReplaceUml "$csvMerged"
  commonOut "=> $csvMerged"
fi
