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