Como separar um arquivo como split em stdout para canalizar um comando?

6

Eu tenho um grande arquivo .sql cheio de SELECT declarações que contêm dados que eu quero inserir em meu banco de dados do SQL Server. Estou procurando como eu poderia basicamente pegar o conteúdo do arquivo, 100 linhas por vez, e passá-lo para os comandos que defini para fazer o resto.

Basicamente, estou procurando por split que irá gerar a stdout , não arquivos.

Também estou usando o CygWin no Windows, por isso não tenho acesso ao conjunto completo de ferramentas.

    
por Ehryk 20.04.2014 / 12:21

5 respostas

1

Acabei com algo aparentemente nojento, se houver uma maneira melhor, poste:

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

Execute com ./insert.sh "File.sql" 100 , em que 100 é o número de linhas a processar por vez.

    
por 20.04.2014 / 13:29
5

Acho que a maneira mais fácil de fazer isso é:

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

Você precisa usar read para a primeira linha em cada seção, pois parece não haver outra maneira de parar quando o final do arquivo for atingido. Para mais informações, consulte:

por 20.04.2014 / 14:35
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

A função acima usa sed para aplicar sua lista de argumentos como uma cadeia de comandos a um incremento de linha arbitrário. Os comandos que você especifica na linha de comando são originados em uma função de shell temporária que é alimentada com um documento aqui em stdin que consiste no valor de cada passo do incremento de linhas.

Você usa assim:

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

O mecanismo aqui é muito simples:

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

Esse é o script sed . Basicamente, nós apenas printf $increment * n; . Portanto, se você definir seu incremento para 100%,printf escreverá para você um script sed que consiste em 100 linhas que dizem apenas $!n , uma insert linha para a extremidade superior do here-doc e uma append para a linha de fundo - é isso. A maioria do resto apenas lida com opções.

O comando n ext informa ao sed para imprimir a linha atual, excluí-la e puxar a próxima. O $! especifica que só deve tentar em qualquer linha, mas a última.

Contanto que seja apenas um incrementador:

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

Então, o que está acontecendo nos bastidores aqui é que a função está definida como echo a counter e cat sua entrada se não tiver uma string de comando. Se você viu na linha de comando seria parecido com:

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

Ele executa um desses para cada incremento. Olhe:

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"

REALMENTE RÁPIDO

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

Acima eu digo para incrementar a cada 4000 linhas. 17s depois e eu processei 20 milhões de linhas. Claro que a lógica não é séria lá - nós só lemos cada linha duas vezes e contamos todos os seus personagens, mas as possibilidades são bem abertas. Além disso, se você olhar de perto, poderá notar que aparentemente os filtros fornecem a entrada que está demorando a maior parte do tempo de qualquer maneira.

    
por 21.04.2014 / 16:47
1

O GNU Parallel é feito para isso:

cat bigfile | parallel --pipe -N100 yourscript

O padrão será executar 1 trabalho por núcleo da CPU. Você pode forçar a execução de um único trabalho com '-j1'.

A versão 20140422 inclui uma versão rápida que pode fornecer 3,5 GB / s. O preço é que ele não pode entregar as 100 linhas exatas, mas se você souber o tamanho aproximado da linha, você pode definir --block para 100 vezes (aqui eu suponho que o tamanho da linha esteja próximo de 500 bytes):

parallel --pipepart --block 50k yourscript :::: bigfile
    
por 26.04.2014 / 02:42
1

Basically, I'm looking for split that will output to stdout, not files.

Se você tiver acesso a gnu split , a opção --filter fará exatamente isso:

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

Então, no seu caso, você pode usar esses comandos com --filter , por exemplo

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

ou escreva um script, por exemplo myscript :

#!/bin/sh

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

e depois simplesmente execute

split -l 100 --filter=./myscript infile
    
por 22.01.2016 / 20:08