2016-04-01 17 views
0

ich zwei ascii Tabellen verketten möchte (untereinander) der Anteil einige, aber nicht alle Spaltenüberschriften, und ich will „blanks“ mit einigen Zeichenfolge ersetzen, wie „nan“ in der Ausgabe. (Bezogen: can we do this with emacs, aber keine Antwort, als der noch)Concatenate ascii/org Tabellen mit einigen verschiedenen Rubriken

z.B.

Tabelle 1

Head1 HeadA 
1  a 
2  b 

Tabelle 2

HeadA HeadFoo 
c  bar 

Und das Ergebnis wäre

Head1 HeadA HeadFoo 
1  a  nan 
2  b  nan 
nan c  bar 

ich die folgende sehr frisch zsh Skript geschrieben haben (verwendet ein zsh-only-Befehl) aber es ist langsam wenn es viele Spalten gibt (für ob Gründe).

Beachten Sie, dass oben Ich benutze Tab-separierte Beispiele, aber mein Skript erwartet durch Leerzeichen getrennte Tabellen.

#!/bin/zsh 
# 
# take several dat files, possibly with different headers 
# 
containsElement() { 
    local e 
    for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done 
    return 1 
} 


ALL_COLUMNS=() 

for A in "[email protected]"; do 
    if [ ! -f ${A} ] 
    then 
      echo not a file 
      exit 
    fi 
    ALL_COLUMNS=("${ALL_COLUMNS[@]}" `head -1 "${A}"`) 
done 

typeset -U ALL_COLUMNS 

echo $ALL_COLUMNS 

for A in "[email protected]"; do 
    HEADER=($(head -1 "${A}")) 
    TMP="_TMP_${A}_" 
    TMPFILE="_TMPFILE" 
    #create empty temporary files (/hack) 
    touch "${TMPFILE}" 
    touch "${TMP}" 
    rm "${TMP}" 
    rm "${TMPFILE}" 
    touch "${TMPFILE}" 
    touch "${TMP}" 
    for C in ${ALL_COLUMNS[@]}; do 
     if ! containsElement "${C}" "${HEADER[@]}" 
     then 
       "${C}" not in "${HEADER[@]}" 
       #paste a column of nans to TMPFILE 
       paste "${TMP}" <(sed '1d;s/.*/nan/' "${A}") > "${TMPFILE}" 
       cat "${TMPFILE}" > "${TMP}" 
     else 
      # echo "${C}" is in "${HEADER[@]}" 
      #find which column this is, cut it, and paste it to TMPFILE 
      COUNT=1 
      for H_KEY in $(head -1 ${A}); do 
       if [ "${C}" = "${H_KEY}" ]; then 
         break 
       else 
        let COUNT=COUNT+1 
       fi 
      done 
      paste "${TMP}" <(cut -d " " -f${COUNT} <(sed '1d' ${A})) > "${TMPFILE}" 
      cat "${TMPFILE}" > "${TMP}" 
     fi 
    done 
    #cat the current input file to stdout, with any additional nan columns. 
    #remove leading white space left by paste 
    sed 's/^[[:space:]]*//' "${TMP}" 
    rm "${TMP}" 
done 

Bearbeiten: Hier sind zwei weitere Eingabedateien (Leerzeichen diesmal begrenzt) zu versuchen.

% cat test1.dat 
A B C 
1 2 3 
% cat test2.dat 
A B D 
1 2 4 

% ./collate_dat_files_different_headers.sh test2.dat test1.dat 

A B D C 
1 2 4 nan 
1 2 nan 3 

Und hier ist es, eine größere Menge von Eingaben (beachten Sie die Leerzeichen getrennte Eingänge, wenn Sie dies versuchen, auf Ihr eigenes Skript aus.):

% ROWS=10; (echo A B C D E F G && seq $ROWS > _tmp && paste _tmp _tmp _tmp _tmp _tmp _tmp _tmp | sed 's/\t/ /g') > bigtest.dat 
% cat bigtest.dat 
A B C D E F G 
1 1 1 1 1 1 1 
2 2 2 2 2 2 2 
3 3 3 3 3 3 3 
4 4 4 4 4 4 4 
5 5 5 5 5 5 5 
6 6 6 6 6 6 6 
7 7 7 7 7 7 7 
8 8 8 8 8 8 8 
9 9 9 9 9 9 9 
10 10 10 10 10 10 10 

% cut -d" " -f1,2,5,7 bigtest.dat > bigtest1.dat 
% cut -d" " -f1,2,4,7 bigtest.dat > bigtest2.dat 
% cut -d" " -f1,2 bigtest.dat > bigtest3.dat 
% cut -d" " -f7,6,4,2,3,1 bigtest.dat > bigtest4.dat 

% ./collate_dat_files_different_headers.sh bigtest1.dat bigtest2.dat bigtest3.dat bigtest4.dat 
A B E G D C F 
1 1 1 1 nan nan nan 
2 2 2 2 nan nan nan 
3 3 3 3 nan nan nan 
4 4 4 4 nan nan nan 
5 5 5 5 nan nan nan 
6 6 6 6 nan nan nan 
7 7 7 7 nan nan nan 
8 8 8 8 nan nan nan 
9 9 9 9 nan nan nan 
10 10 10 10 nan nan nan 
1 1 nan 1 1 nan nan 
2 2 nan 2 2 nan nan 
3 3 nan 3 3 nan nan 
4 4 nan 4 4 nan nan 
5 5 nan 5 5 nan nan 
6 6 nan 6 6 nan nan 
7 7 nan 7 7 nan nan 
8 8 nan 8 8 nan nan 
9 9 nan 9 9 nan nan 
10 10 nan 10 10 nan nan 
1 1 nan nan nan nan nan 
2 2 nan nan nan nan nan 
3 3 nan nan nan nan nan 
4 4 nan nan nan nan nan 
5 5 nan nan nan nan nan 
6 6 nan nan nan nan nan 
7 7 nan nan nan nan nan 
8 8 nan nan nan nan nan 
9 9 nan nan nan nan nan 
10 10 nan nan nan nan nan 
1 1 nan 1 1 1 1 
2 2 nan 2 2 2 2 
3 3 nan 3 3 3 3 
4 4 nan 4 4 4 4 
5 5 nan 5 5 5 5 
6 6 nan 6 6 6 6 
7 7 nan 7 7 7 7 
8 8 nan 8 8 8 8 
9 9 nan 9 9 9 9 
10 10 nan 10 10 10 10 

Meine Frage: Gibt es eine schnellere/bessere Möglichkeit, dies zu tun oder Verbesserungen an meinem Skript?

+2

Haben Sie bei 'paste' sah, 'Join' oder' Spalte'? – LinuxDisciple

+0

Ich glaube nicht, Join ist, was ich will, da es Tabellen zusammenführen wird, anstatt sie zu katzen. Paste ist in meinem Skript oben, und Spalte kann hilfreich sein beim Zusammenfügen von Dingen (wie Paste ist), aber ich sehe nicht, wie es das Skript beschleunigen wird. Gut zu wissen über diese obwohl. – Alejandro

+0

Sollte nicht das letzte * Ergebnis * "nan c bar" sein ?? –

Antwort

0

Ich bin nicht sicher, dass die folgenden alle Ihre Anforderungen für die verschiedenen Dateien entsprechen, werden nicht Teil der Frage gemacht, sondern auch für Ihre Beispieldateien, es tut. Ob es schneller oder langsamer ist, müssen Sie testen. Sie müssen einige Anpassungen vornehmen, da es in bash geschrieben ist, aber der Hauptzweck war, ein paar zusätzliche Ideen zu geben, wie Sie das Problem angehen können, anstatt eine Ein-Skript-Lösung zu sein, um alle möglichen Dateien zusammenzuführen.

Anstatt auf externe Versorgungs verlassen, macht das Skript Verwendung von mehreren Arrays. Zuerst lesen Sie die Header von $1 und $2 in separate Dateien und scannen die Header-Felder für ein Feld, um file1 und file2 zu verbinden (eine gemeinsame Überschrift - sie nimmt die erste gefunden). Es speichert das (nullbasierte) Join-Feld für Datei1 in j1 und das Join-Feld für Datei2 in j2. Die verbleibenden Zeilen für jede Datei werden in die Arrays alines und blines eingelesen. Das Skript durchläuft dann alines, wobei alines und blines in separate Felder (afields und bfields) unterteilt werden, die auf einen gemeinsamen Wert für die Join-Felder j1 und j2 prüfen. Wenn sie gefunden wird, werden alle Werte für die gemeinsame Feld verbindet gedruckt, wenn kein gemeinsamer Wert in dem Verknüpfungsfeld gefunden wird, afields gedruckt und nan wird in dem freien Feld gedruckt von $2.

Der letzte Satz von Schleifen, Schleifen durch blines im Wesentlichen die gleiche Sache für blines.Hier ist jedoch, wenn für einen gemeinsamen Wert in dem Verknüpfungsfeld überprüft, ob ein gemeinsamer Wert gefunden wird, dann wird diese Zeile ist nicht-output (aufgrund oben bei der Iteration der alines gedruckt.

Wesentliches beiden Sätze von Nested Loops behandeln einfach alle Zeilen in $1 und alle Zeilen von $2 mit einem gemeinsamen Wert im Join-Feld (erster Satz) .Dann behandelt der zweite Satz Zeilen für $2, die zuvor im ersten Satz nicht behandelt wurden. Hoffentlich die Verwendung von Arrays anstelle von tmp Dateien werden die Operationen beschleunigen, aber für große Dateien wird die Leistung jedes Skripts leiden.

Schauen Sie es sich an und lassen Sie mich wissen, wenn Sie welche haben Fragen:

#!/bin/bash 

[ ! -f "$1" -o ! -f "$2" ] && { ## validate 2 input filenames 
    printf "error: insufficient input, usage: %s file1 file2\n" "${0//*\/}" 
    exit 1 
} 

j1=0  ## join field file 1, 2, joined flag 
j2=0 
joined=0 

read -r -a a1 < "$1" ## read file headers 
read -r -a a2 < "$2" 

for ((i = 0; i < "${#a1[@]}"; i++)); do  ## find join fields 
    for ((j = 0; j < "${#a2[@]}"; j++)); do 
     if [ "${a1[i]}" = "${a2[j]}" ]; then 
      j1=$i 
      j2=$j 
      joined=1 ## set found flag 
      break 
     fi 
    done 
    [ "$joined" -eq 1 ] && break ## found - done 
done 

printf "%-6s" ${a1[@]}    ## print file1 header 
printf "%-6s" ${a2[@]:$((j2+1))} ## print file2 header from j2 on 
printf "\n" 

oifs="$IFS"  ## save internal field separator 
IFS=$'\n'  ## set to break on space 

alines=($(tail -n+2 "$1")) ## read remainder of $1 & $2 into line arrays 
blines=($(tail -n+2 "$2")) 

IFS="$oifs"  ## reset IFS to original (space, tab, newline) 
key=0   ## common key field value flag 

for ((i = 0; i < "${#alines[@]}"; i++)); do   ## for each line in $1 
    afields=(${alines[i]})      ## separate into fields 
    for ((j = 0; j < "${#afields[@]}"; j++)); do ## for each field 
     printf "%-6s" ${afields[j]}     ## print field 
    done 
    for ((k = 0; k < "${#blines[@]}"; k++)); do  ## for each line in $2 
     bfields=(${blines[k]})     ## check if key fields match 
     [ "${afields[j1]}" = "${bfields[j2]}" ] && printf "%-6s" ${afields[j]} && 
     key = 1 
    done 
    [ "$key" -eq 0 ] && printf "%-6s" "nan"  ## if no match print 'nan' 
    key=0 
    printf "\n" 
done 

for ((i = 0; i < "${#blines[@]}"; i++)); do   ## for each line in $2 
    printf "%-6s" "nan"        ## field 1 always 'nan' 
    bfields=(${blines[i]})      ## separate into fields 
    for ((k = 0; k < "${#alines[@]}"; k++)); do  ## for each line in $1 
     afields=(${alines[k]})     ## separate fields 
     [ "${afields[j1]}" = "${bfields[j2]}" ] && key = 1 ## check match 
    done 
    [ "$key" -eq 1 ] && key=0 && continue   ## if match already output 
    for ((j = 0; j < "${#bfields[@]}"; j++)); do ## print $2 fields 
     printf "%-6s" ${bfields[j]} 
    done 
    printf "\n" 
done 

Eingabedateien

$ cat dat/f1.txt 
Head1 HeadA 
1  a 
2  b 

$ cat dat/f2.txt 
HeadA HeadFoo 
c  bar 

Verwendung/Output

$ bash joinarray.sh dat/f1.txt dat/f2.txt 
Head1 HeadA HeadFoo 
1  a  nan 
2  b  nan 
nan c  bar 

(Anmerkung: die Ausgabe ist die Ausgabe, die logischen Sinn wie angesprochen in meinem Kommentar macht zu Sie unter der ursprünglichen Frage)

+0

Ich kämpfe um diese Arbeit zu machen. Posted Input-Dateien in OP, weil keine Formatierung in Kommentaren. – Alejandro

+0

Ah, OK, du hast mir einen Kurvenball mit den neuen Datendateien geworfen. In den Originaldateien gab es nur ein gemeinsames Feld zwischen den Dateien, in den neuen Datendateien ist fast jedes Feld zwischen den Datendateien gemeinsam. Immer noch machbar, aber wir müssen die Schleifenstruktur überarbeiten, um mehrere übereinstimmende Felder in jeder Datendatei zu behandeln. Sie haben Recht, dass 'join' und' paste' in diesem Fall keine Hilfe sind. Sie müssen im Wesentlichen einen Parser/Joiner schreiben, um jede unterschiedliche Menge von Dateien (oder zumindest die Eingangsroutinen) zu behandeln. Ich werde in ein paar Stunden Zeit haben, um wieder zu kommen. –

+0

Für das, was es wert ist, funktioniert mein Skript, also ist es vielleicht kein Neuschreiben, das ich brauche. – Alejandro

0

Verwenden Gnu verbinden und eine „vollständige äußere Verknüpfung“ auf den beiden Tabellen tun:

%join -a1 -a2 -1 2 -2 1 -o 1.1 0 2.2 -e "nan" table1 table2 

Head1 HeadA HeadFoo 
1 a nan 
2 b nan 
nan c bar 

Sie möchten können ziemlich hoch die Ausgabe mit Spalte:

%join -a1 -a2 -1 2 -2 1 -o 1.1 0 2.2 -e "nan" f1 f2 | column -c3 -t 
Head1 HeadA HeadFoo 
1  a  nan 
2  b  nan 
nan c  bar