Wie zerlege ich eine Datei wie split to stdout, um sie an einen Befehl weiterzuleiten?


7

Ich habe eine große .sqlDatei voller SELECTAnweisungen, die Daten enthalten, die ich in meine SQL Server-Datenbank einfügen möchte. Ich suche nach einer Möglichkeit, den Inhalt der Datei in 100 Zeilen gleichzeitig an die Befehle zu übergeben, die ich für den Rest festgelegt habe.

Grundsätzlich suche ich nach einer splitAusgabe stdout, nicht nach Dateien.

Ich verwende CygWin auch unter Windows, sodass ich nicht auf die gesamte Tool-Suite zugreifen kann.


Hast du dir die Verwendung angesehen BULK INSERT? Trennen Sie die Daten von der SQL-Anweisung.
BSD

Antworten:


5

Ich denke, der einfachste Weg, dies zu tun, ist:

while IFS= read -r line; do
  { printf '%s\n' "$line"; head -n 99; } |
  other_commands
done <database_file

Sie müssen readfür die erste Zeile in jedem Abschnitt verwenden, da es anscheinend keine andere Möglichkeit gibt, anzuhalten, wenn das Ende der Datei erreicht ist. Weitere Informationen finden Sie unter:


2
_linc() ( ${sh-da}sh ${dbg+-vx} 4<&0 <&3 ) 3<<-ARGS 3<<\CMD
        set -- $( [ $((i=${1%%*[!0-9]*}-1)) -gt 1 ] && {
                shift && echo "\${inc=$i}" ; }
        unset cmd ; [ $# -gt 0 ] || cmd='echo incr "#$((i=i+1))" ; cat'
        printf '%s ' 'me=$$ ;' \
        '_cmd() {' '${dbg+set -vx ;}' "$@" "$cmd" '
        }' )
        ARGS
        s= ; sed -f - <<-INC /dev/fd/4 | . /dev/stdin
                i_cmd <<"${s:=${me}SPLIT${me}}"
                ${inc:+$(printf '$!n\n%.0b' `seq $inc`)}
                a$s
        INC
CMD

Die obige Funktion verwendet sed, um ihre Argumentliste als Befehlszeichenfolge auf ein beliebiges Zeileninkrement anzuwenden. Die Befehle, die Sie in der Befehlszeile angeben, werden in eine temporäre Shell-Funktion eingespeist, die ein Here-Dokument auf stdin erhält, das aus den Zeilenzeilen aller Inkremente besteht.

Sie verwenden es so:

time printf 'this is line #%d\n' `seq 1000` |
_linc 193 sed -e \$= -e r \- \| tail -n2
    #output
193
this is line #193
193
this is line #386
193
this is line #579
193
this is line #772
193
this is line #965
35
this is line #1000
printf 'this is line #%d\n' `seq 1000`  0.00s user 0.00s system 0% cpu 0.004 total

Der Mechanismus hier ist sehr einfach:

i_cmd <<"${s:=${me}SPLIT${me}}"
${inc:+$(printf '$!n\n%.0b' `seq $inc`)}
a$s

Das ist das sedDrehbuch. Grundsätzlich haben wir nur printf $increment * n;. Wenn Sie also Ihr Inkrement auf 100 setzen printf, erhalten Sie ein sedSkript, das aus 100 Zeilen besteht, die nur sagen $!n, eine insertZeile für das obere Ende des Here-Dokuments und eineappend für die untere Zeile - fertig. Der größte Teil des Restes behandelt nur Optionen.

Der nBefehl ext weist Sie sedan, die aktuelle Zeile zu drucken, zu löschen und die nächste zu ziehen. Das $!gibt an, dass es nur eine Zeile außer der letzten versuchen soll.

Vorausgesetzt, nur ein Inkrementierer wird es:

printf 'this is line #%d\n' `seq 10` |                                  
_linc 3
    #output
incr #1
this is line #1
this is line #2
this is line #3
incr #2
this is line #4
this is line #5
this is line #6
incr #3
this is line #7
this is line #8
this is line #9
incr #4
this is line #10

Was hier also hinter den Kulissen passiert, ist, dass die Funktion auf echoeinen Zähler gesetzt und catdessen Eingabe erfolgt, wenn keine Befehlszeichenfolge angegeben wird. Wenn Sie es in der Befehlszeile sehen würden, würde es so aussehen:

{ echo "incr #$((i=i+1))" ; cat ; } <<HEREDOC
this is line #7
this is line #8
this is line #9
HEREDOC

Es führt eine davon für jedes Inkrement aus. Aussehen:

printf 'this is line #%d\n' `seq 10` |
dbg= _linc 3
    #output
set -- ${inc=2}
+ set -- 2
me=$$ ; _cmd() { ${dbg+set -vx ;} echo incr "#$((i=i+1))" ; cat
}
+ me=19396
        s= ; sed -f - <<-INC /dev/fd/4 | . /dev/stdin
                i_cmd <<"${s:=${me}SPLIT${me}}"
                ${inc:+$(printf '$!n\n%.0b' `seq $inc`)}
                a$s
        INC
+ s=
+ . /dev/stdin
+ seq 2
+ printf $!n\n%.0b 1 2
+ sed -f - /dev/fd/4
_cmd <<"19396SPLIT19396"
this is line #1
this is line #2
this is line #3
19396SPLIT19396
+ _cmd
+ set -vx ; echo incr #1
+ cat
this is line #1
this is line #2
this is line #3
_cmd <<"19396SPLIT19396"

WIRKLICH SCHNELL

time yes | sed = | sed -n 'p;n' |
_linc 4000 'printf "current line and char count\n"
    sed "1w /dev/fd/2" | wc -c
    [ $((i=i+1)) -ge 5000 ] && kill "$me" || echo "$i"'

    #OUTPUT

current line and char count
19992001
36000
4999
current line and char count
19996001
36000
current line and char count
[2]    17113 terminated  yes |
       17114 terminated  sed = |
       17115 terminated  sed -n 'p;n'
yes  0.86s user 0.06s system 5% cpu 16.994 total
sed =  9.06s user 0.30s system 55% cpu 16.993 total
sed -n 'p;n'  7.68s user 0.38s system 47% cpu 16.992 total

Oben sage ich, dass es alle 4000 Zeilen erhöht werden soll. 17s später habe ich 20 Millionen Zeilen verarbeitet. Natürlich ist die Logik dort nicht ernst - wir lesen jede Zeile nur zweimal und zählen alle ihre Zeichen, aber die Möglichkeiten sind ziemlich offen. Auch wenn Sie genau hinschauen, werden Sie vielleicht bemerken, dass es anscheinend die Filter sind, die die Eingabe liefern, die sowieso die meiste Zeit in Anspruch nehmen.


Es ist erwähnenswert, dass die Scherkomplexität der Shell-Magie sie nicht portabel macht - sie läuft auf bash4 unter osx 10.9 sicherlich nicht. :) es will erweitert werden, um zu verwenden dash, und sed -f -macht bsd sed auch nicht glücklich ... ganz zu schweigen davon, dass man die Heredoc-Marker zurück auf ^ ... ziehen muss
scharf

2

GNU Parallel ist dafür gemacht:

cat bigfile | parallel --pipe -N100 yourscript

Standardmäßig wird 1 Job pro CPU-Kern ausgeführt. Sie können die Ausführung eines einzelnen Jobs mit '-j1' erzwingen.

Die Version 20140422 enthält eine schnelle Version, die 3,5 GB / s liefern kann. Der Preis ist, dass es nicht die exakten 100 Zeilen liefern kann, aber wenn Sie die ungefähre Zeilenlänge kennen, können Sie --block auf das 100-fache einstellen (hier gehe ich davon aus, dass die Zeilenlänge nahe bei 500 Bytes liegt):

parallel --pipepart --block 50k yourscript :::: bigfile

1

Am Ende hatte ich etwas, das scheinbar eklig ist. Wenn es einen besseren Weg gibt, poste es bitte:

#!/bin/sh

DONE=false
until $DONE; do
    for i in $(seq 1 $2); do 
        read line || DONE=true;
        [ -z "$line" ] && continue;
        lines+=$line$'\n';
    done
    sql=${lines::${#lines}-10}
    (cat "Header.sql"; echo "$sql";) | sqlcmd
    #echo "--- PROCESSED ---";
    lines=;
done < $1

Führen Sie aus, ./insert.sh "File.sql" 100wobei die 100 die Anzahl der Zeilen ist, die gleichzeitig verarbeitet werden sollen.


Ich bin mir nicht sicher, welche Annahmen mit SQL sicher sind, aber aus Gründen der allgemeinen Sicherheit sollten Sie dies tun IFS= read -r line. Betrachten Sie den Unterschied zwischen echo ' \t\e\s\t ' | { read line; echo "[$line]"; }und echo ' \t\e\s\t ' | { IFS= read -r line; echo "[$line]"; }. Auch echoist nicht sicher mit beliebigen Zeichenfolgen (zB line="-n"; echo "$line"), es ist sicherer zu verwenden printf '%s\n.
Graeme

1

Grundsätzlich suche ich nach einer splitAusgabe stdout, nicht nach Dateien.

Wenn Sie Zugriff auf haben gnu split, führt die --filterOption genau das aus:

‘--filter=command’

    With this option, rather than simply writing to each output file, write
    through a pipe to the specified shell command for each output file.

In Ihrem Fall können Sie diese Befehle also entweder mit --filterz

split -l 100 --filter='{ cat Header.sql; cat; } | sqlcmd; printf %s\\n DONE' infile

oder schreiben Sie ein Skript, zB myscript:

#!/bin/sh

{ cat Header.sql; cat; } | sqlcmd
printf %s\\n '--- PROCESSED ---'

und dann einfach laufen

split -l 100 --filter=./myscript infile
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.