Shell é lento?

Categoria: Shell Scripts Publicado: Sábado, 04 Junho 2022 Escrito por Helio Loureiro

Durante a apresentação do grande prof. Júlio Neves na BSD Day 2022 sobre shell, ele fez uma comparação interessante sobre shell ser dito lento.  E mostrou o seguinte slide:

Eu achei muito interessante o exemplo, mas resolvi testar ele pra valer.

Então testei não com 200 interações, mas com 20 milhões.  Daí sim a coisa fica mais interessante.

Fiz o mesmo script em shell:

#! /usr/bin/env bash

for ((i=1; i<20000000; i++)) {
  : > arq-shell
}

Em python:

#! /usr/bin/env python3

for i in range(20000000):
    with open("arq-python3", "w") as fd:
        None

em perl:

#! /usr/bin/env perl

for ($i=0;$i<20000000;$i++) {
  open(FD, ">arq-perl");
}

e finalmente em Go:

package main

import (
        "log"
        "os"
)

func main() {
        for i := 0; i < 20000000; i++ {
                fd, err := os.Create("arq-go")
                fd.Close()
                if err != nil {
                        log.Fatal(err)
                }
        }
}

Os resultados foram os seguintes:

helio@goosfraba /t/comparacao-shell> time ./20M-touch.sh  

________________________________________________________
Executed in  303.69 secs    fish           external
  usr time  164.93 secs    0.00 micros  164.93 secs
  sys time  133.79 secs  533.00 micros  133.79 secs

helio@goosfraba /t/comparacao-shell> time ./20M-touch.py

________________________________________________________
Executed in  374.49 secs    fish           external
  usr time  225.16 secs  623.00 micros  225.16 secs
  sys time  143.90 secs  125.00 micros  143.90 secs

helio@goosfraba /t/comparacao-shell> time ./20M-touch.pl

________________________________________________________
Executed in  173.94 secs    fish           external
  usr time   47.95 secs    1.05 millis   47.95 secs
  sys time  122.10 secs    0.00 millis  122.10 secs


helio@goosfraba /t/comparacao-shell> time ./20M-touch

________________________________________________________
Executed in  147.80 secs    fish           external
  usr time   45.82 secs  579.00 micros   45.82 secs
  sys time  107.38 secs  113.00 micros  107.38 secs

Eu achei os resultados um pouco miseráveis pra python.  Então re-escrevi a função como era feito desde o python 1.2 (usando 3.10.4):

#! /usr/bin/env python3

for i in range(20000000):
    fd = open("arq-python3", "w")
    fd.close()

e o resultado não melhorou muita coisa.

helio@goosfraba /t/comparacao-shell> time ./20M-touch.py 

________________________________________________________
Executed in  370.23 secs    fish           external
  usr time  218.59 secs  641.00 micros  218.59 secs
  sys time  146.02 secs  132.00 micros  146.02 secs

Acho que agora os números falam por si só sobre shell ser lento ou não.  Claro que pra coisas mais simples é bem mais fácil fazer em shell, mas isso não significa um desempenho melhor.

Tirando essa parte de desafio, a palestra foi espetacular.  Quem não viu, recomendo que assistam.

 

Refatorando meu script de bloqueio de youtube no openwrt

Categoria: Shell Scripts Publicado: Domingo, 19 Dezembro 2021 Escrito por Helio Loureiro

Filho aborrecente é... aborrecente.  E infelizmente tenho de tempos em tempos de usar a artimanha de bloquear o YouTube pra conseguir sua atenção e fazer as suas tarefas.

Hoje eu estava revendo o script que criei em bloqueando Youtube no OpenWRT, criado em 2018.  Dei uma melhorada no código e fiz o cáculo do horário de uma forma melhor.

Precisei carregar os módulos "bc" e "iptables-mod-filter" no openwrt pra funcionar como desejado.


#! /bin/sh
# save it into /usr/lib/scripts/firewall.sh
# and add into scheduled tasks as
# */5 * * * * /usr/lib/scripts/firewall.sh timetable

NOW=$(date +"%H:%M")
TIMETABLE="07:55,10:00 12:00,18:00 20:30,22:00"

status_file=/tmp/firewall_status

blocked_pattern="youtubei.googleapis.com"
blocked_pattern="$blocked_pattern googlevideo.com"
blocked_pattern="$blocked_pattern ytimg-edge-static.l.google.com"
blocked_pattern="$blocked_pattern i.ytimg.com"
blocked_pattern="$blocked_pattern youtube-ui.l.google.com"
blocked_pattern="$blocked_pattern www.youtube.com"
blocked_pattern="$blocked_pattern googleapis.l.google.com"
blocked_pattern="$blocked_pattern youtubei.googleapis.com"
blocked_pattern="$blocked_pattern video-stats.l.google.com"
blocked_pattern="$blocked_pattern ytimg-edge-static.l.google.com"

enable_firewall() {
    echo "Enabling firewall"
    for chain in INPUT FORWARD OUTPUT
        do
        count=1
        for proto in tcp udp
            do
                for blocked in $blocked_pattern
                    do
                    echo iptables -I $chain $count -p $proto -m string --algo bm --string "$blocked" -j DROP
                    iptables -I $chain $count -p $proto -m string --algo bm --string "$blocked" -j DROP
                    count=`expr $count + 1`
                done
        done
        echo iptables -I $chain $count -p udp --sport 443 -j DROP
        iptables -I $chain $count -p udp --sport 443 -j DROP
        count=`expr $count + 1`
        echo iptables -I $chain $count -p udp --dport 443 -j DROP
        iptables -I $chain $count -p udp --dport 443 -j DROP
        count=`expr $count + 1`
    done
    echo -n "enabled" > $status_file
}

disable_firewall() {
    echo "Disabling firewall"
    for chain in INPUT FORWARD OUTPUT
        do
        for proto in tcp udp
            do
                for blocked in $blocked_pattern
                    do
                    echo iptables -D $chain -p $proto -m string --algo bm --string "$blocked" -j DROP
                    iptables -D $chain -p $proto -m string --algo bm --string "$blocked" -j DROP
                done
        done
        echo iptables -D $chain -p udp --sport 443 -j DROP
        iptables -D $chain -p udp --sport 443 -j DROP
        echo iptables -D $chain -p udp --dport 443 -j DROP
        iptables -D $chain -p udp --dport 443 -j DROP
    done
    echo -n "disabled" > $status_file
}

_get_time_as_integer() {
  time=$1

  hour=$(echo $time | cut -d: -f 1)
  minute=$(echo $time | cut -d: -f 2)

  echo "$hour * 100 + $minute" | bc
}

_get_start_time(){
    # expected format: 07:00,10:00
    time_str=$1

    time_start=$(echo $time_str | cut -d, -f 1)
    _get_time_as_integer $time_start
}

_get_stop_time() {
    #expected format: 07:00,10:00
    time_str=$1

    time_stop=$(echo $time_str | cut -d, -f 2)
    _get_time_as_integer $time_stop
}

get_timetable() {

do_activate=0
for value in $TIMETABLE
    do
    start=$(_get_start_time $value)
    stop=$(_get_stop_time $value)
    cur_time=$(_get_time_as_integer $NOW)
    if [ $start -lt $cur_time ]; then
        if [ $cur_time -lt $stop ]; then
           do_activate=1
        fi
    fi
done

cur_status=$(cat $status_file)
if [ $do_activate ]; then
    if [ "$cur_status" = "enabled" ]; then
        echo "firewall already activated"
    else
       echo "activating firewall"
       enable_firewall
    fi
else
    if [ "$cur_status" = "enabled" ]; then
       echo "deactivating firewall"
       disable_firewall
    else
        echo "firewall already deactivated"
    fi
fi
}

case $1 in
    start) enable_firewall
           exit 0;;
    stop) disable_firewall
          exit 0;;
    timetable) get_timetable
               exit 0;;
    status) echo "firewall rules are $(cat $status_file)";;
    *) echo "Use: $0 [start|stop|timetable|status]"
       exit 0
esac

exit 0

Agora os horário de bloqueio ficam na variável TIMETABLE e no format "<horário início HH:MM>,<horário fim HH:MM>".  O firewall permite um direto "start" e "stop" pra ativar, assim como um "status".  Crie alguma funções com o "_" no início, pra seguir um pouco o padrão do python de funções internas/privadas.

Seu funcionamento agora ficou muito bom e fácil, pra desespero dos aborrecentes.

root@OpenWrt:/usr/lib/scripts# ls
firewall.sh
root@OpenWrt:/usr/lib/scripts# ./firewall.sh status
firewall rules are disabled
root@OpenWrt:/usr/lib/scripts# ./firewall.sh timetable
activating firewall
Enabling firewall
iptables -I INPUT 1 -p tcp -m string --algo bm --string youtubei.googleapis.com -j DROP
iptables -I INPUT 2 -p tcp -m string --algo bm --string googlevideo.com -j DROP
iptables -I INPUT 3 -p tcp -m string --algo bm --string ytimg-edge-static.l.google.com -j DROP
iptables -I INPUT 4 -p tcp -m string --algo bm --string i.ytimg.com -j DROP
iptables -I INPUT 5 -p tcp -m string --algo bm --string youtube-ui.l.google.com -j DROP
iptables -I INPUT 6 -p tcp -m string --algo bm --string www.youtube.com -j DROP
iptables -I INPUT 7 -p tcp -m string --algo bm --string googleapis.l.google.com -j DROP
iptables -I INPUT 8 -p tcp -m string --algo bm --string youtubei.googleapis.com -j DROP
iptables -I INPUT 9 -p tcp -m string --algo bm --string video-stats.l.google.com -j DROP
iptables -I INPUT 10 -p tcp -m string --algo bm --string ytimg-edge-static.l.google.com -j DROP
iptables -I INPUT 11 -p udp -m string --algo bm --string youtubei.googleapis.com -j DROP
iptables -I INPUT 12 -p udp -m string --algo bm --string googlevideo.com -j DROP
iptables -I INPUT 13 -p udp -m string --algo bm --string ytimg-edge-static.l.google.com -j DROP
iptables -I INPUT 14 -p udp -m string --algo bm --string i.ytimg.com -j DROP
iptables -I INPUT 15 -p udp -m string --algo bm --string youtube-ui.l.google.com -j DROP
iptables -I INPUT 16 -p udp -m string --algo bm --string www.youtube.com -j DROP
iptables -I INPUT 17 -p udp -m string --algo bm --string googleapis.l.google.com -j DROP
iptables -I INPUT 18 -p udp -m string --algo bm --string youtubei.googleapis.com -j DROP
iptables -I INPUT 19 -p udp -m string --algo bm --string video-stats.l.google.com -j DROP
iptables -I INPUT 20 -p udp -m string --algo bm --string ytimg-edge-static.l.google.com -j DROP
iptables -I INPUT 21 -p udp --sport 443 -j DROP
iptables -I INPUT 22 -p udp --dport 443 -j DROP
iptables -I FORWARD 1 -p tcp -m string --algo bm --string youtubei.googleapis.com -j DROP
iptables -I FORWARD 2 -p tcp -m string --algo bm --string googlevideo.com -j DROP
iptables -I FORWARD 3 -p tcp -m string --algo bm --string ytimg-edge-static.l.google.com -j DROP
iptables -I FORWARD 4 -p tcp -m string --algo bm --string i.ytimg.com -j DROP
iptables -I FORWARD 5 -p tcp -m string --algo bm --string youtube-ui.l.google.com -j DROP
iptables -I FORWARD 6 -p tcp -m string --algo bm --string www.youtube.com -j DROP
iptables -I FORWARD 7 -p tcp -m string --algo bm --string googleapis.l.google.com -j DROP
iptables -I FORWARD 8 -p tcp -m string --algo bm --string youtubei.googleapis.com -j DROP
iptables -I FORWARD 9 -p tcp -m string --algo bm --string video-stats.l.google.com -j DROP
iptables -I FORWARD 10 -p tcp -m string --algo bm --string ytimg-edge-static.l.google.com -j DROP
iptables -I FORWARD 11 -p udp -m string --algo bm --string youtubei.googleapis.com -j DROP
iptables -I FORWARD 12 -p udp -m string --algo bm --string googlevideo.com -j DROP
iptables -I FORWARD 13 -p udp -m string --algo bm --string ytimg-edge-static.l.google.com -j DROP
iptables -I FORWARD 14 -p udp -m string --algo bm --string i.ytimg.com -j DROP
iptables -I FORWARD 15 -p udp -m string --algo bm --string youtube-ui.l.google.com -j DROP
iptables -I FORWARD 16 -p udp -m string --algo bm --string www.youtube.com -j DROP
iptables -I FORWARD 17 -p udp -m string --algo bm --string googleapis.l.google.com -j DROP
iptables -I FORWARD 18 -p udp -m string --algo bm --string youtubei.googleapis.com -j DROP
iptables -I FORWARD 19 -p udp -m string --algo bm --string video-stats.l.google.com -j DROP
iptables -I FORWARD 20 -p udp -m string --algo bm --string ytimg-edge-static.l.google.com -j DROP
iptables -I FORWARD 21 -p udp --sport 443 -j DROP
iptables -I FORWARD 22 -p udp --dport 443 -j DROP
iptables -I OUTPUT 1 -p tcp -m string --algo bm --string youtubei.googleapis.com -j DROP
iptables -I OUTPUT 2 -p tcp -m string --algo bm --string googlevideo.com -j DROP
iptables -I OUTPUT 3 -p tcp -m string --algo bm --string ytimg-edge-static.l.google.com -j DROP
iptables -I OUTPUT 4 -p tcp -m string --algo bm --string i.ytimg.com -j DROP
iptables -I OUTPUT 5 -p tcp -m string --algo bm --string youtube-ui.l.google.com -j DROP
iptables -I OUTPUT 6 -p tcp -m string --algo bm --string www.youtube.com -j DROP
iptables -I OUTPUT 7 -p tcp -m string --algo bm --string googleapis.l.google.com -j DROP
iptables -I OUTPUT 8 -p tcp -m string --algo bm --string youtubei.googleapis.com -j DROP
iptables -I OUTPUT 9 -p tcp -m string --algo bm --string video-stats.l.google.com -j DROP
iptables -I OUTPUT 10 -p tcp -m string --algo bm --string ytimg-edge-static.l.google.com -j DROP
iptables -I OUTPUT 11 -p udp -m string --algo bm --string youtubei.googleapis.com -j DROP
iptables -I OUTPUT 12 -p udp -m string --algo bm --string googlevideo.com -j DROP
iptables -I OUTPUT 13 -p udp -m string --algo bm --string ytimg-edge-static.l.google.com -j DROP
iptables -I OUTPUT 14 -p udp -m string --algo bm --string i.ytimg.com -j DROP
iptables -I OUTPUT 15 -p udp -m string --algo bm --string youtube-ui.l.google.com -j DROP
iptables -I OUTPUT 16 -p udp -m string --algo bm --string www.youtube.com -j DROP
iptables -I OUTPUT 17 -p udp -m string --algo bm --string googleapis.l.google.com -j DROP
iptables -I OUTPUT 18 -p udp -m string --algo bm --string youtubei.googleapis.com -j DROP
iptables -I OUTPUT 19 -p udp -m string --algo bm --string video-stats.l.google.com -j DROP
iptables -I OUTPUT 20 -p udp -m string --algo bm --string ytimg-edge-static.l.google.com -j DROP
iptables -I OUTPUT 21 -p udp --sport 443 -j DROP
iptables -I OUTPUT 22 -p udp --dport 443 -j DROP
root@OpenWrt:/usr/lib/scripts# ./firewall.sh timetable
firewall already activated
root@OpenWrt:/usr/lib/scripts# ./firewall.sh status
firewall rules are enabled
root@OpenWrt:/usr/lib/scripts# date
Sun Dec 19 13:51:32 CET 2021

Boa diversão.  Ou não caso seja o aborrescente lendo esse artigo pra descobrir o porquê seu YouTube parou de funcionar.

No roadmap: incluir TikTok e Instagram.

UPDATE: eu por fim criei um repositório no github pra ficar mais fácil a manutenção: https://github.com/helioloureiro/opewrt-youtube-blocker

Uma conversa introdutória sobre shell scripts com o prof. Juliano

Categoria: Shell Scripts Publicado: Sexta, 04 Junho 2021 Escrito por Helio Loureiro

Esses dias, um pouco antes da BSD Day pra dize a verdade, eu aceitei o convite do prof. Juliano pra falar sobre shell scripts.

Eu não preparei muito antes, e fiz um tipo de live coding sobre shell scripts mostrando um pouco de cada coisa.   Não espero que seja tido com um tipo de aprendizado justamente porque eu realmente não preparei nada como aula ou palestra, mas espero que sirva de inspiração pra quem quiser iniciar.

Boa diversão!

Chamada pro vídeo.

 

 

Vídeo completo.

 

Usando curl pra monitorar um site

Categoria: Shell Scripts Publicado: Sexta, 05 Fevereiro 2021 Escrito por Helio Loureiro

Por muitos anos o meu comando preferido pra baixar páginas e até fazer mirrors foi o comando wget.  Até eu descobrir o curl.

Não que eu já não conhecesse o curl.  Com tantos anos de estrada com Unix, principalmente Linux e FreeBSD, curl era um conhecido que eu evitava usar.  O motivo?  Zona de conforto.  wget era um comando conhecido e que eu sempre usava, desde que ando comecei a aprender Unix, em 1997.  curl por outro lado era talvez tão antigo quanto, mas cheio de opções cabulosas que eu sentia um certo receio de usar.

Atualmente eu trabalho bastante com backend web e curl virou meu braço direito.  E conforme fui aprendendo a usar mais, fui adorando.  Hoje em dia não faço um container que seja sem o curl dentro.  E acabei até deixando pra lá o wget mas... hoje é dia de falar de curl.

Durante essa semana no trabalho tivemos um problema de servidor web down.  Ao tentar conectar ele ficava um tempo tentando dar a resposta e depois enviava um erro 500 (acho que era 503, mas isso não importa).  Então como eu queria acessar o serviço assim qeu estivesse disponível e não queria ficar dando reload na aba do navegador, fiz um script com curl.   Ele usa uma das opções do curl de retorna um valor do header de resposta, que no caso era os status code.

status_code=0
while [ $status_code -ne 200 ]
    do
    sleep 10
    status_code=$(curl -s -o /dev/null -w "%{http_code}" https://helio.loureiro.eng.br/)
    echo "status_code=$status_code"
done

O script fica monitorando o site e retorna o valor do status code em loop. 

status_code=500
status_code=500
status_code=500
status_code=500
status_code=500
status_code=200

Assim que receber uma resposta 200, que é ok, para.  Deixei isso rodando num shell no canto do desktop (uso duas telas então não bloqueou minha visão de nada).  Assim que parou, voltei ao browser e acessei o serviço.

Coisa linda, não?

A dica mais preciosa de shell scripts dos últimos tempos

Categoria: Shell Scripts Publicado: Sábado, 19 Setembro 2020 Escrito por Helio Loureiro

Entre os vários grupos do Telegram, um que é muito bom é o de shell, o t.me/shellbr pros mais íntimos.

Um dia desses, entre discussões de como fazer um shell melhor e eu postando sticker pros falantes de língua inglesa de que o grupo é em língua portuguesa, apareceu algo que abriu minha visão em shell pra algo muito maravilhoso.  Não é uma novidade de um comando mágico que faz algo completamente novo, mas a simplicidade da solução que esteve esse tempo todo na minha cara e eu nunca vi foi o que me fascinou.

Pra dar um pouco mais de contexto, o que se referia isso: quem nunca precisou mandar um "ps auxwww | grep firefox" e pegou o próprio comando grep na saída?

 ~> ps auxwww | grep firefox 
helio   5719 15.5  2.9 3994484 475156 ?      Rl   20:57   4:38 /usr/lib/firefox/firefox --ProfileManger --no-remote
helio   5815  6.0  1.6 3021712 259472 ?      Sl   20:58   1:47 /usr/lib/firefox/firefox -contentproc -childID 1 
helio   5876  1.0  1.2 2687192 201516 ?      Sl   20:58   0:18 /usr/lib/firefox/firefox -contentproc -childID 2 
helio   5916  0.5  1.0 2646492 161856 ?      Sl   20:58   0:10 /usr/lib/firefox/firefox -contentproc -childID 3 
helio   5939  5.8  2.0 3008700 325944 ?      Rl   20:58   1:44 /usr/lib/firefox/firefox -contentproc -childID 4 
helio   9210  1.0  1.5 2875692 241532 ?      Sl   21:08   0:12 /usr/lib/firefox/firefox -contentproc -childID 9 
helio  10161  1.5  1.1 2676620 179336 ?      Rl   21:11   0:15 /usr/lib/firefox/firefox -contentproc -childID 10 
helio  10394  0.2  0.7 2583360 120484 ?      Sl   21:12   0:02 /usr/lib/firefox/firefox -contentproc -childID 11 
helio  11818  0.0  0.4 2549948 77524 ?       Sl   21:18   0:00 /usr/lib/firefox/firefox -contentproc -childID 13 
helio  14368  0.0  0.0  18056  1052 pts/2    S+   21:27   0:00 grep --color=auto firefox

Comecei com Linux em 1997, quando aprendi a usar as Sparc stations da universidade.  Mais de 20 anos nessa indústria vital e eu sempre, sempre, usei desse jeito:

 ~ > ps auxwww | grep firefox | grep -v grep 
helio   5719 14.6  3.0 3991196 481364 ?      Sl   20:57   4:56 /usr/lib/firefox/firefox --ProfileManger --no-remote
helio   5815  6.2  1.7 3019784 279384 ?      Sl   20:58   2:05 /usr/lib/firefox/firefox -contentproc -childID 1 
helio   5876  0.9  1.2 2687192 202532 ?      Sl   20:58   0:19 /usr/lib/firefox/firefox -contentproc -childID 2 
helio   5916  0.5  1.0 2646492 164072 ?      Sl   20:58   0:10 /usr/lib/firefox/firefox -contentproc -childID 3 
helio   5939  5.4  2.0 3008700 330532 ?      Sl   20:58   1:49 /usr/lib/firefox/firefox -contentproc -childID 4 
helio   9210  0.9  1.5 2875692 241876 ?      Sl   21:08   0:13 /usr/lib/firefox/firefox -contentproc -childID 9 
helio  10161  1.3  1.1 2676620 179336 ?      Sl   21:11   0:16 /usr/lib/firefox/firefox -contentproc -childID 10 
helio  10394  0.2  0.7 2583360 120588 ?      Sl   21:12   0:02 /usr/lib/firefox/firefox -contentproc -childID 11 
helio  11818  0.0  0.4 2549948 77524 ?       Sl   21:18   0:00 /usr/lib/firefox/firefox -contentproc -childID 13 

Com essa maravilhosa dica do Hélio Campos, basta fazer assim:

 ~ > ps auxwww | grep [f]irefox
helio   5719 14.5  3.1 4005676 500088 ?      Sl   20:57   5:01 /usr/lib/firefox/firefox --ProfileManger --no-remote
helio   5815  6.7  1.8 3034264 293864 ?      Rl   20:58   2:18 /usr/lib/firefox/firefox -contentproc -childID 1 
helio   5876  0.9  1.2 2687192 203656 ?      Sl   20:58   0:19 /usr/lib/firefox/firefox -contentproc -childID 2 
helio   5916  0.5  1.0 2646492 164072 ?      Sl   20:58   0:10 /usr/lib/firefox/firefox -contentproc -childID 3 
helio   5939  5.3  2.0 3008700 331836 ?      Sl   20:58   1:51 /usr/lib/firefox/firefox -contentproc -childID 4 
helio   9210  0.9  1.5 2875692 241876 ?      Sl   21:08   0:14 /usr/lib/firefox/firefox -contentproc -childID 9 
helio  10161  1.3  1.1 2676620 179336 ?      Sl   21:11   0:17 /usr/lib/firefox/firefox -contentproc -childID 10 
helio  10394  0.2  0.7 2583360 120588 ?      Sl   21:12   0:02 /usr/lib/firefox/firefox -contentproc -childID 11 
helio  11818  0.0  0.4 2549948 77524 ?       Sl   21:18   0:00 /usr/lib/firefox/firefox -contentproc -childID 13

Eu achei espetácular.  Pode ser que eu esteja exagerando, mas achei mesmo.  Uma dica muito simples e acabou com décadas usando um extra "grep" pra resolver as coisas. 

Muito obrigado Hélio Campos e grupo shellbr!