dotfiles/notes/shell/bash_rosetta.md

2.5 KiB

Using shell in other languages

Bash is incredibly concise at allowing programs to interact on a basic level, however it severely lacks control flow and sensible data structures, which make it a bad choice for logic-heavy scripting

In these cases we can attempt to recreate the high-level control bash has over programs in other languages. All languages are able to call programs exactly like bash, though almost all of them are much more verbose

The examples below use translate line of bash

swaymsg -t get_tree | jq '.nodes[1].nodes[].representation' | tr -d '\"'

Python

A great and portable choice for easy scripting

from subprocess import Popen, PIPE

p1 = Popen(["swaymsg", "-t", "get_tree"], stdout=PIPE);
p2 = Popen(["jq", ".nodes[1].nodes[].representation"],
        stdin=PIPE,
        stdout=PIPE);
p3 = Popen(["tr", "-d", '"'), stdin=PIPE, stdout=PIPE)

p1_stdout, _ = p1.communicate(timeout=1)
p2_stdout, _ = p2.communicate(p1_stdout, timeout=1)
p3_stdout, _ = p3.communicate(p2_stdout, timeout=1)
print(str(p3_stdout))

Shell-like strings can easily be split into the args lists required by Popen

from shlex import split as shsplit
# ...
p3 = Popen(shsplit("tr -d '\"'"), stdin=PIPE, stdout=PIPE)
# ...

Do not use pipes even in the shsplit strings. Pipes must be manually managed, unless the shell=True parameter is passed to Popen. However, that essentially completely gives up control over the subprocess, so it's highly not recommend

Rust

Rust is very fast, though not very portable. Portable binaries can be made by compiling with a musl libc target. That way everything will be statically linked into the binary, which typically makes it work across all Linux systems

cargo build --release --target=x86_64-unknown-linux-musl

Rust is extremely verbose, though allows for very fine error handling. The below uses unwrap mostly

use std::io::{self, BufRead, BufReader};
use std::process::{Command, Stdio, ChildStdout};

fn get_tree() -> io::Result<ChildStdout> {
    let swaymsg = Command::new("swaymsg")
        .arg("-t")
        .arg("get_tree")
        .stdout(Stdio::piped())
        .spawn()?;

    let jq = Command::new("jq")
        .arg(".nodes[1].nodes[].representation")
        .stdin(swaymsg.stdout.unwrap())
        .stdout(Stdio::piped())
        .spawn()
        .expect("`jq` binary is not available");

    let tr = Command::new("tr")
        .arg("-d")
        .arg("\"")
        .stdin(jq.stdout.unwrap())
        .stdout(Stdio::piped())
        .spawn()?;

    Ok(tr.stdout.unwrap())
}