Shshsh is a bridge connects Python and shell
github.com
github.com
For me, a little bit of syntax sugar (and maybe a line or two of code savings) isn't worth taking on the dependency burden, especially in production code.
Each and every dependency is one where I invite risk of requirements collision, or that the upstream goes away, or that the maintainer breaks compatibility, etc. It's more time spent installing dependencies, and more time spent processing lockfiles.
Why carry that burden when subprocess.Popen() exists and works fine?
But I 100% understand the motivation for this project. Like you said, subprocess.Popen works well enough, but it's so much less ergonomic than shelling out in Ruby, Perl, or even Awk.
In certain situations though, the readability/maintainability gains from using this library might outweigh the added complexity of a layer of indirection.
Situations like this: whether to build a utility solution completely [1] in shell, or invert things and use mix of Python and shell. E.g., wrapping 'git' to create a note-taking CLI, or shelling out to $WEIRD_INTERNAL_TOOL and scraping its logs and stdout/stderr.
I think infrastructure like this repo can result in more readable and maintainable code than an all-shell OR all-Python solution in such situations.
You'll be less tempted to start doing things completely in shell and then get locked into that lock minimum.
However, you MUST be prepared to understand the source code of all dependencies you take [except the language's standard library, or similarly mature external libs]. e.g., I pick bottlepy.org vs Flask for personal projects.
With a small/medium library like this, I might import it to get a headstart and will be ok with it ultimately ending up as an incompatible, heavily-modified fork of the upstream project. Which IMO doesn't reflect poorly on the user or the library author.
[1] "completely" means some non-trivial processing that makes you use hash tables, arrays, lightweight tokenizing using read, arithmetic evaluation, sed+awk to massage command outputs so Bash's lightweight data structures can handle them, os.path manipulations.
I respect and often agree with the parent's hesitation to introduce inessential dependencies, but the cost/benefit of making a shell script more comfortable to read/write is very different than that of adding a mystery brick to the foundation of your app.
I totally see the benefit of moving complex shell logic to Python (and still calling some shell helpers) but that doesn't mean you need to have a whole extra dependency for it.
You could write a two-line function that wraps subprocess.Popen.communicate (or subprocess.check_output), and use it instead for 90% of the benefit.
If the argument is "I don't want to learn it", or "too much effort", then I'd ask - is saving 15 minutes one time during development really worth a permanent dependency?
I remember Leftpad, and the lessons that it taught.
I've also had to deal with codebases where the build kept breaking because a large team of developers all Pip-installed whatever they wanted. A package here, two packages there. Overall a maintenance disaster, and you'd be surprised at how often those dependencies outlasted any code that used them.
I wish there was a stdlib package that sat between subprocess and something else with a higher level syntax for process management. Running binary `foo` takes 3 letters in shell, and many many more in subprocess.
I can also almost feel the spirit of Rachel Kroll (rachelbythebay) glaring disapprovingly at this, because the last time someone tried the equivalent of `exec('mkdir -p ' + path)` instead of doing it properly, it led to an SEV: https://rachelbythebay.com/w/2021/12/24/mkdir/
Yes, the page has something like "prepared statements" to avoid obvious injection holes, but it might not be obvious to the everyday user how important that is to get right.
ripgrep has on the order of 100 flags. I don't have the bandwidth to maintain both a CLI and a stable library interface for all of that.
So while it might seem better to you, there are realities that make it difficult to do given constrained time and resources.
On top of that, ripgrep has a `--json` flag with a pretty simple format. Running another process is often not a Big Deal, and thus, there is another off ramp to avoid the complexity of ripgrep's libraries that doesn't really exist for SSL.
Have you not heard of `re` and its functions, search, match, and sub?
Absolutely is grep. If you need a shell tool to search for something in a python script, youre doin it entirely wrong
And that's not even accounting for just regex engine throughput on its own. ripgrep for example uses rust/regex. Compare its performance with python/re: https://github.com/BurntSushi/rebar#summary-of-search-time-b...
Really?
Let me see you write this in native python with the same level of conciseness as the shell can pull off.
from pathlib import Path
for path in Path().glob("*test*"):
print(path.name)Where's grep ?
And where's the pipe ?
If you want to run a shell pipeline that is more challenging to port to pure Python, then you could the shell directly:
S.run("a | b", shell=True)
You could emulate it in Python using the subprocess module but it would be more verbose. It depends on specific use-case whether it is worth the trouble.Calling the shell pipeline from Python gets you the best of both worlds: a shell one liner is used as a concise domain specific language to run commands , and more complex logic, proper error handling, etc is left to Python.
IMHO the following is about the only one that's tasteful in terms of project scope and not going off the deep end: https://github.com/hauntsaninja/pyp
I like that you support the scripting use case with vanilla Python syntax. That's something that made Xonsh a non-starter for me (I'm not going to write scripts when syntax highlighting and auto-formatting don't work because it's a superset of valid Python syntax).
Evidently my definition of "going off the deep end" here is giving up lots of working tooling and integrations.
Lately I've been finding it useful to write bash scripts with some marcel, e.g.
#!/bin/bash
echo ...
pushd ...
marcel <<EOF
...
EOF"Saving streams" is killer. I've been "meaning to build something to solve that" for a looong time. :) :) But you did. Thanks!!
https://www.marceltheshell.org/saving-and-recalling-streams
-----
One feature request to think about if it doesn't exist: defining multiline functions right at the prompt, which you later export to a .py file. So I think a decent inline editor with support for arrow keys might be useful. https://www.marceltheshell.org/functions-1
This is not a showstopper obviously. The current 'import' functionality should be enough for me for now.
Why inline vs. shelling out a dedicated editor: because replacing the screen will take me out of flow because I frequently refer to previous commands' output on the terminal when constructing/editing the next command.
Multiline functions can be defined right now:
M 0.18.3 jao@loon ~$ f = (lambda: \
M +$ lambda x, y: x + y \
M +$ )
M 0.18.3 jao@loon ~$ (f(3,4))
7
So all that would be needed is a way to dump them into a file. That should be doable with
another function.One thing to note: You have choices beyond inline and imports for defining new functions. There is a startup file, typically in ~/.config/marcel/startup.py. You can define functions in there, or do imports. The REPL loop checks for modifications to that file, so if you change it, you pick up the changes for your next command.
For example, I just put this into my startup file:
def hello(x):
return f'Hello {x}'
and then without restarting marcel: M 0.18.3 jao@loon ~$ (hello('world'))
Hello worldI made something similar myself, pyxargs [1], which doesn't have all the magic of pyp, but although being geared towards more xargs like usage, it also functions somewhat similarly.
For example, although not as clean as the magic variables x, l or line, you can use '{}'
ls | pyp 'x[:3]' == ls | pyxargs --pyev "'{}'[:3]"
Overall the syntax of pyp looks much better, and the --explain feature is nice since pyp seems to do a little more for you under the hood (the closest pyxargs equivalent being --dry-run). I think I'll start using pyp myself when not running commands over multiple directories of files or in parallel.But yeah I didn't know either existed and I wish I did, seems like it could be really useful in the shell
I didn't really highlight it in the documentation but it can also run commands in parallel as multiple windows in a multiplexer so it's easier to checkup on their progress or interact with them.
https://sh.readthedocs.io/en/latest/
It is brilliant to do quick interactions with existing bash commands or “pseudo-bash scripts” in Python
`I >>` and `>= keep` might be terse, but they're hard to reason about unless you're already familiar with the library.
This probably isn't the kind of stuff you want to employ code-golf syntax for.
It also does the inverse, allowing you to run marcel commands from Python, e.g. https://www.marceltheshell.org/scripting-1
unlike xonsh that can't be ran as a python script and must be ran through its own interpreter, this actually brings the shell into a python script
which looks nifty!
it makes for an easier time collecting, within the python ecosystem, output from a function call that exists outside the python ecosystem, i would highlight that as the main difference rather than just being a "bridge" to the shell, nice work