Compare commits
3 Commits
3571ff690f
...
7c87a519f3
Author | SHA1 | Date |
---|---|---|
Julio Biason | 7c87a519f3 | 7 months ago |
Julio Biason | 7899388d93 | 7 months ago |
Julio Biason | 14415b5e2e | 8 months ago |
2 changed files with 247 additions and 0 deletions
@ -0,0 +1,146 @@
|
||||
+++ |
||||
title = "Running a Command and Saving Its Output to File in Rust" |
||||
date = 2023-09-01 |
||||
|
||||
[taxonomies] |
||||
tags = ["random", "rust", "command", "log"] |
||||
+++ |
||||
|
||||
I had an issue: I needed to run a command inside Rust, but I needed that all |
||||
its output should go to a file, and I needed to check if there were certain |
||||
phrases in it. |
||||
|
||||
<!-- more --> |
||||
|
||||
So, first step: Create a script that could "replicate" the output of a command, |
||||
with the expected strings to be captured: |
||||
|
||||
```bash |
||||
#!/usr/bin/env bash |
||||
|
||||
for loop in {1..1000} |
||||
do |
||||
echo "Hello, I'm a script!" |
||||
echo "I write stuff in the output." |
||||
echo "Everything should go to a file." |
||||
echo "But also, you need to capture warnings:" |
||||
|
||||
if (( $loop%7 == 0)); then |
||||
echo "WARNING: This is a warning" |
||||
echo " It continues if the line starts with spaces" |
||||
echo " And keeps going till there are no more spaces-prefixes" |
||||
fi |
||||
|
||||
if (( $loop%8 == 0)); then |
||||
# ERR is just to make sure we find it easily in the logs |
||||
echo "ERR: Sometimes, I also write in stderr!" >&2 |
||||
echo "ERR: Just for funsies!" >&2 |
||||
fi |
||||
|
||||
echo "Like this." |
||||
echo "Then you're good to go." |
||||
echo "" |
||||
done |
||||
``` |
||||
|
||||
What this script does is to print a message over 1,000 times, and sometimes it |
||||
will display a "WARNING" text -- which is the special output I need to capture -- |
||||
and sometimes it will print things to stderr. |
||||
|
||||
For the code, what we need to do is: |
||||
|
||||
1. Spawn the command; |
||||
2. Take the stdour (and stderr) from it. |
||||
3. Spawn a thread that will keep listening to the output, doing the search, |
||||
and writing everything to a file (our log). |
||||
4. The thread returns the list of captured messages, which we can get back |
||||
when we `.join()` it. |
||||
5. Since I was expecting stderr to be smaller enough, I did the capturing of |
||||
it after the thread completes (which would also close the file, so we can |
||||
be sure that we can open it again without any issues). |
||||
|
||||
The first step is quite easy: Just use `std::process::Command` and use the |
||||
`.spawn()` function to create the `Child` controller. |
||||
|
||||
For the second step, we use the `Child` structure and use `.take()` on both |
||||
stdout and stderr. This will give us the file descriptor for both (think about |
||||
them as `File`s). |
||||
|
||||
The third step is quite easy, actualy: `std::thread::spawn()` to create a |
||||
thread, and just read the content from the file descriptors from step 2. In |
||||
this, I used `BufReader`, which gives access to reading the content line by |
||||
line, which is way easier than reading to a buffer and processing it. |
||||
|
||||
```rust |
||||
use std::fs::{File, OpenOptions}; |
||||
use std::io::{BufRead, BufReader, Read, Write}; |
||||
use std::path::PathBuf; |
||||
use std::process::Command; |
||||
|
||||
fn main() { |
||||
// this requires always running with `cargo run` |
||||
let base = PathBuf::from(env!("CARGO_MANIFEST_DIR")); |
||||
let the_script = base.join("src").join("the_script.sh"); |
||||
|
||||
let mut cmd = Command::new("bash") |
||||
.arg(the_script) |
||||
.stdout(std::process::Stdio::piped()) |
||||
.stderr(std::process::Stdio::piped()) |
||||
.spawn() |
||||
.unwrap(); |
||||
|
||||
// capture both the stdout and stderr as File structs (actually FDs, but basically the same |
||||
// thing) |
||||
let stdout = cmd.stdout.take().unwrap(); |
||||
let mut stderr = cmd.stderr.take().unwrap(); |
||||
|
||||
// spawn a thread to keep capturing and processing the stdout. |
||||
let writer_pid = std::thread::spawn(move || { |
||||
let reader = BufReader::new(stdout); |
||||
let lines = reader.lines(); |
||||
let mut log_file = File::create("script.log").unwrap(); |
||||
let mut in_warning = false; |
||||
let mut result = Vec::new(); |
||||
|
||||
for line in lines { |
||||
let line = line.unwrap(); |
||||
log_file.write(line.as_bytes()).unwrap(); |
||||
log_file.write(b"\n").unwrap(); // 'cause lines() eat it |
||||
|
||||
if line.starts_with("WARNING:") { |
||||
in_warning = true; |
||||
} else if line.starts_with(" ") && in_warning { |
||||
result.push(line); |
||||
} else if in_warning { |
||||
in_warning = false; |
||||
} |
||||
} |
||||
|
||||
result |
||||
}); |
||||
|
||||
// run the command till it finishes |
||||
cmd.wait().unwrap(); |
||||
|
||||
// ... and wait till the thread finishes processing the whole output. |
||||
let warnings = writer_pid.join().unwrap(); |
||||
|
||||
// this is somewhat a hack: Instead of spawning a thread for stderr and trying to fight with |
||||
// stdout for the lock to be able to write in the log file, we do this after the thread ends |
||||
// (which closes the file) and then open it again and write the stderr in the end. We do this |
||||
// 'cause we expect that the stderr is way smaller than stdout and can fit in memory without |
||||
// any issues. |
||||
let mut buffer = String::new(); |
||||
stderr.read_to_string(&mut buffer).unwrap(); |
||||
|
||||
let mut file = OpenOptions::new().append(true).open("script.log").unwrap(); |
||||
file.write(buffer.as_bytes()).unwrap(); |
||||
|
||||
// This is purely for diagnostic purposes. We could put the warnings in another file, or pass |
||||
// it along to something else to process it. Here, we just display them. |
||||
// Same for stderr: Since we already put them in the file, this is used just to make sure we |
||||
// are capturing the errors without looking at the file. |
||||
println!("Warnings:\n{:?}", warnings); |
||||
println!("ERR:\n{:?}", buffer) |
||||
} |
||||
``` |
@ -0,0 +1,101 @@
|
||||
+++ |
||||
title = "Algo Ganho, Algo Perdido" |
||||
date = 2023-10-20 |
||||
draft = true |
||||
|
||||
[taxonomies] |
||||
tags = ["apresentações", "pythonbrasil"] |
||||
+++ |
||||
|
||||
<!-- more --> |
||||
|
||||
Minha vida com comunidades de programação começou em 1997, quando foi |
||||
bolsista de iniciação científica na Unisinos. Na época, a internet |
||||
ainda estava engatinhando no Brasil, e no grupo de bolsistas a ideia |
||||
de software livre estava se tornando cada vez mais forte. Linux era a |
||||
nossa principal ferramenta, as aplicações ainda eram bem crus e, para |
||||
um grupo de jovens na casa dos 20 anos, viciados em programar e com |
||||
algum tempo livre, mexer nas coisas dos outros era o mais legal -- e |
||||
ainda mais legal era ter acesso ao código dos outros, e poder ver como |
||||
outras pessoas resolviam seus problemas, e poder alterar o código para |
||||
fazer algo que queríamos, era o máximo. |
||||
|
||||
E eu posso dizer que eu fui muito sortudo em estar no meio desse |
||||
grupo. |
||||
|
||||
Mas faculdade termina, e bolsas de estudo terminam ainda mais rápido, |
||||
e logo eu acabei me afastando disso. |
||||
|
||||
... até 2016, quando participei da minha primeira PythonBrasil. |
||||
|
||||
Num dos corredores, entre os períodos de apresentações, eu estava |
||||
conversando com algumas pessoas quando alguém -- cujo nome eu nunca |
||||
perguntei -- veio dizendo que éramos "gaúchos de araque", porque não |
||||
tínhamos ido tentar defender Porto Alegre como próxima sede do evento. |
||||
Eu tive que ser bruto: "Nós não conseguimos nem manter um evento |
||||
local, e agora querem que a gente faça um evento nacional?" Por sorte, |
||||
o Filipe Cifali, organizador do PyTche, estava do meu lado, e comentou |
||||
que ele não estava conseguindo manter tudo sozinho. Naquele momento, |
||||
eu me ofereci pra tentar erguer o grupo, organizando as coisas junto |
||||
com o Filipe. E, no fim, Tony Dourado se juntou a nós, e o grupo teve |
||||
3 organizadores. |
||||
|
||||
E por 3 anos, o PyTche continuou tendo eventos todos os meses, talvez |
||||
com a ocasional exclusão de Dezembro e Janeiro, por causa do Natal e |
||||
primeiro mês do ano, quando boa parte do pessoal estava ainda de |
||||
férias, ou nos meses em que havia PythonBrasil e os três organizadores |
||||
estavam afastados. |
||||
|
||||
Na época, o "queridinho" para organizar comunidades era o Meetup. E, a |
||||
partir dele nós tínhamos fotos dos eventos, organização de enquetes |
||||
(que incluía "Qual o melhor dia da semana de X para fazer o |
||||
encontro?", em que, invariavelmente, sábado de manhã ganhava), e |
||||
organização da lista de presença (para encontrar o lugar do evento). |
||||
|
||||
Infelizmente, depois de 2019, o PyTche não teve mais encontros. |
||||
|
||||
Em 2018, fui contatado pelo Leonardo Vaz para participar do Tchelinux. |
||||
Ao contrário do PyTche (e outras comunidades) que se organizavam |
||||
localmente, a ideia do Tchelinux era ir aos lugares, para falar com |
||||
quem estava começando a estudar programação e mostrar que haviam |
||||
ferramentas que eles poderiam não só ter de graça, mas também poderiam |
||||
aprender com elas, e modificar da forma que quisessem. Todo o processo |
||||
era organizado pelo Leonardo, que encontrava professores da área de |
||||
ciências de computação de alguma universidade e perguntava se eles |
||||
tinham interesse em trabalhar com o Tchelinux para fazer um evento na |
||||
universidade. |
||||
|
||||
Em 2019 eu participei de 13 dos 15 eventos do Tchelinux, percorrendo |
||||
quase 3.000km pelo estado do Rio Grande do Sul para fazer minhas |
||||
apresentações -- entre elas, "Fugindo Para as Colinas com Python", em |
||||
que eu desconstruo uma aplicação em Python, explicando o que cada |
||||
parte faz e por que algumas decisões de design foram tomadas. Mas a |
||||
parte mais legal era que, quando me perguntavam como continuar |
||||
aprendendo Python, eu comentava da experiência com o PyTche, dos |
||||
encontros e como cada um trazia alguma coisa, ou tentávamos resolver, |
||||
em conjunto, um problema do Exercism, e sempre havia uma animação em |
||||
fazer algo parecido. |
||||
|
||||
Mas 2019 foi o meu último ano com o Tchelinux. |
||||
|
||||
Por volta do começo de 2018 eu comecei a me interessar pela linguagem |
||||
Rust, e descobri que havia um grupo local, chamado "Rust in POA", |
||||
através do Meetup. Como o grupo estava meio parado por falta de tempo |
||||
dos organizadores, e como eu já tinha experiência em organizar esse |
||||
tipo de evento, me comprometi em ajudar na organização. |
||||
|
||||
No começo de 2020, o Rust in POA parou de se encontrar. |
||||
|
||||
O motivo dessas paradas é óbvio: Em 2020, tivemos os primeiros efeitos |
||||
da COVID-19 no Brasil, e ninguém sabia como o vírus se espalhava ou |
||||
como se proteger, e contatos pessoais ou próximos foram veemente |
||||
desencorajados. Nesse cenário, a utilização de outras ferramentas de |
||||
comunicação contínua, como Telegram e WhatsApp explodiram, e a |
||||
utilização de ferramentas de vídeo chamada também. |
||||
|
||||
|
||||
|
||||
<!-- |
||||
vim:spelllang=pt:spell:tw=70 |
||||
--> |
||||
|
Loading…
Reference in new issue