Làm cách nào để chia nhỏ một tệp như split to stdout để chuyển sang lệnh?


7

Tôi có một .sqltệp lớn chứa đầy các SELECTcâu lệnh chứa dữ liệu tôi muốn chèn vào cơ sở dữ liệu SQL Server của mình. Tôi đang tìm cách về cơ bản tôi có thể lấy nội dung của tệp, 100 dòng cùng một lúc và chuyển nó đến các lệnh tôi đã đặt để thực hiện phần còn lại.

Về cơ bản, tôi đang tìm kiếm splitsẽ xuất ra stdoutchứ không phải các tệp.

Tôi cũng đang sử dụng CygWin trên Windows, vì vậy tôi không có quyền truy cập vào bộ công cụ đầy đủ.


Bạn đã xem sử dụng BULK INSERT? Tách dữ liệu khỏi câu lệnh SQL.
bsd

Câu trả lời:


5

Tôi nghĩ cách dễ nhất để làm điều này là:

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

Bạn cần sử dụng readcho dòng đầu tiên trong mỗi phần vì dường như không có cách nào khác để dừng khi kết thúc tập tin. Để biết thêm thông tin xem:


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

Hàm trên sử dụng sedđể áp dụng danh sách đối số của nó dưới dạng chuỗi lệnh cho gia số dòng tùy ý. Các lệnh bạn chỉ định trên dòng lệnh được lấy nguồn từ hàm shell tạm thời được cung cấp tài liệu ở đây trên stdin bao gồm các dòng có giá trị từng bước tăng dần.

Bạn sử dụng nó như thế này:

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

Cơ chế ở đây rất đơn giản:

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

Đó là sedkịch bản. Về cơ bản chúng tôi chỉ printf $increment * n;. Vì vậy, nếu bạn đặt số gia của mình thành 100 printfsẽ viết cho bạn một sedtập lệnh chỉ gồm 100 dòng $!n, một insertdòng cho đầu cuối của tài liệu này và một dòngappend dòng cho dòng dưới cùng - đó là nó. Hầu hết phần còn lại chỉ xử lý các tùy chọn.

Lệnh next sedsẽ in dòng hiện tại, xóa nó và kéo vào dòng tiếp theo. Các $!chỉ định rằng nó chỉ nên thử trên bất kỳ dòng nào nhưng cuối cùng.

Chỉ được cung cấp một số gia tăng, nó sẽ:

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

Vì vậy, những gì xảy ra đằng sau hậu trường ở đây là chức năng được đặt thành bộ echođếm và catđầu vào của nó nếu không được cung cấp chuỗi lệnh. Nếu bạn thấy nó trên dòng lệnh, nó sẽ trông như sau:

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

Nó thực hiện một trong những điều này cho mỗi lần tăng. Nhìn:

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"

RẤT NHANH

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

Ở trên tôi nói với nó để tăng trên mỗi 4000 dòng. 17s sau và tôi đã xử lý 20 triệu dòng. Tất nhiên logic không nghiêm trọng ở đó - chúng tôi chỉ đọc mỗi dòng hai lần và đếm tất cả các ký tự của chúng, nhưng khả năng là khá mở. Ngoài ra, nếu bạn nhìn kỹ, bạn có thể nhận thấy rằng dường như các bộ lọc cung cấp đầu vào đang chiếm phần lớn thời gian.


điều đáng chú ý là sự phức tạp cắt của ma thuật vỏ trong điều này làm cho nó không thể di động - nó chắc chắn không chạy trên bash4 trên osx 10.9. :) nó muốn mở rộng để sử dụng dash, và sed -f -doesnt làm bsd sed hạnh phúc hoặc là ... chưa kể đến việc phải kéo heredoc dấu sao để ^ ...
sắc sảo

2

GNU Parallel được tạo cho việc này:

cat bigfile | parallel --pipe -N100 yourscript

Nó sẽ mặc định chạy 1 công việc trên mỗi lõi CPU. Bạn có thể buộc chạy một công việc duy nhất với '-j1'.

Phiên bản 20140422 bao gồm một phiên bản nhanh có thể cung cấp 3,5 GB / s. Giá là nó không thể cung cấp chính xác 100 dòng, nhưng nếu bạn biết độ dài dòng gần đúng, bạn có thể đặt - chặn tới 100 lần (ở đây tôi giả sử độ dài của dòng là gần 500 byte):

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

1

Tôi đã kết thúc với một cái gì đó có vẻ thô thiển, nếu có cách nào tốt hơn xin vui lòng gửi nó:

#!/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

Chạy với ./insert.sh "File.sql" 100100 là số dòng cần xử lý tại một thời điểm.


Tôi không chắc chính xác những giả định nào là an toàn với SQL, nhưng để đảm bảo an toàn chung, bạn nên làm IFS= read -r line. Hãy xem xét sự khác nhau giữa echo ' \t\e\s\t ' | { read line; echo "[$line]"; }echo ' \t\e\s\t ' | { IFS= read -r line; echo "[$line]"; }. Cũng echokhông an toàn với các chuỗi tùy ý (ví dụ line="-n"; echo "$line"), nó an toàn hơn để sử dụng printf '%s\n.
Graeme

1

Về cơ bản, tôi đang tìm kiếm splitsẽ xuất ra stdoutchứ không phải các tệp.

Nếu bạn có quyền truy cập gnu split, --filtertùy chọn thực hiện chính xác điều đó:

‘--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.

Vì vậy, trong trường hợp của bạn, bạn có thể sử dụng các lệnh đó với --filter, vd

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

hoặc viết một kịch bản, ví dụ myscript:

#!/bin/sh

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

và sau đó chỉ cần chạy

split -l 100 --filter=./myscript infile
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.