Fork me on GitHub
#babashka
<
2023-04-12
>
grzm12:04:15

I too often struggle figuring out how to pass args and options to babashka process, and I'm sure it's a "me" problem. I generally build up arguments into a vector and then use apply to pass it to p/process along with some options. Often there's some thread macro usage as well. For example:

;; *-args are vectors of command arguments
(-> (p/process some-cmd-args {:dir working-dir})
    (p/process {:out :string, :dir working-dir} (str/join " " some-other-cmd-args))
    (deref))
That took an emabarrassingly long time for me to make work, and I'm not happy with my use of str/join to make the second command work. And the flipping of the options and command positions doesn't make sense to me either. I don't have a great conceptual model of what's going on here, and I'm sure that (a) I'm doing something wrong and (b) it's because I don't understand what's going on. How do others think about this? How do others approach it?

2
borkdude12:04:46

@U0EHU1800 The official syntax is:

(process ?opts-map args+)

borkdude12:04:12

you don't have to str/join them, any number of strings works, the first string is tokenized automatically

grzm12:04:26

How should I approach this if I want to have a vector of arguments? I don't like building up strings for shell commands, and I like to be able to build them up in incrementally. Using apply to try to use varargs has been hard to do, too.

borkdude12:04:27

Just don't use the thread operator if that confuses things, maybe?

(let [p1 (apply p/process {:dir working-dir} some-cmd-args)
      p2 (apply p/process p1 {:out :string, :dir working-dir} some-other-cmd-args)]
  (deref p2))

borkdude12:04:02

Maybe a single map version of process would help to clarify stuff here

grzm13:04:06

Potentially, yeah. Now I'm wondering whether that first (p/process args opts) is doing what I think it's doing. Though, hard for me to think otherwise as it wouldn't find the stuff on disk it needs if it weren't.

borkdude13:04:29

It's (p/process opts args...)

borkdude13:04:55

or:

(p/process piped-process opts args...)

grzm13:04:25

Yeah, that's what I'm saying. I definitely have (p/process args opts) in my code right now.

borkdude13:04:33

Yes, this is legacy

borkdude13:04:36

it still works

grzm13:04:42

Ah. Gotcha. That explains it.

grazfather13:04:53

strace -e execve to see if the arguments are 'spread' across arguments to the invocation of the process

grzm13:04:48

Interesting. @U01KUQRDMLG can you elaborate?

grzm13:04:11

@U04V15CAJ Thanks for explaining why the two different spellings work.

borkdude13:04:54

I've been contemplating the single map argument version for a while. The varargs version is particularly handy with (apply p/process **command-line-args*)*

borkdude13:04:22

And people were confused why p/shell had a different argslists than p/process so it all got normalized

grzm13:04:24

(I've stared at the source of how options and arguments are handled, and boy, that's been hard for me to follow)

grzm13:04:48

That normalization makes sense. I'll probably submit a PR with some more examples in the readme, if you don't mind.

grazfather13:04:19

❯ strace -f -e execve bb
user=> (require '[babashka.process :refer [process check sh pipeline pb]])
nil
user=> (process "echo test 123")
strace: Process 2309863 attached
[pid 2309863] execve("/usr/bin/echo", ["echo", "test", "123"], 0x7ffe51b74fe8 /* 42 vars */) = 0

grzm13:04:22

This definitely has helped clarify things for me. Cheers!

grazfather13:04:40

I did strace -f -e execve bb when i started bb and now i can see exactly how the args are passed to the subprocess

borkdude13:04:12

there is also a process/parse-args function you can use for this

grzm13:04:32

@U01KUQRDMLG Cool! Thanks! Same thing should work for JVM? (e.g., strace -f -e execve clj )

grazfather13:04:47

strace is very very useful

borkdude13:04:54

user=> (proc/parse-args "ls -la")
{:prev nil, :cmd ["l" "s" " " "-" "l" "a"], :opts nil}

borkdude13:04:09

eh I mean:

user=> (proc/parse-args ["ls -la"])
{:prev nil, :cmd ["ls" "-la"], :opts nil}

nice 2
grzm13:04:11

Have you used strace with, say, CIDER? (I spend most of my time in an emacs buffer). Actually, I could use cider-connect instead of cider-jack-in. That'd be easy enough.

grazfather13:04:24

I have not, it should work fine, just use -o to log to a file, -e is the syscall filter, you can filter whatever you want

👍 2
grzm13:04:59

Thanks, all!

grazfather13:04:22

no problem 🙂

borkdude15:04:52

@U0EHU1800 So how about this?

(def ls-proc (proc/process {:cmd ["ls"]}))
(:out @(proc/process {:prev ls-proc :cmd ["wc" "-l"] :out :string}))
"      13\n"
The :prev argument is the "previous process that is piped in" (could use a better name but this is what I have used internally) The :cmd vector are the process arguments

grzm15:04:17

I like the :cmd vector. (I could also see that being optionally a string to be tokenized or a vector because some people like the string form, but I personally would only use the vector form: I could also see deferring adding the string option until people clamor for it)

grzm15:04:41

I do like the transparent behavior of threading to "pipe" processes. Would that still work?

borkdude15:04:08

yes:

user=> (-> ls-proc (proc/process {:cmd ["wc" "-l"] :out :string}) deref :out)
"      13\n"

👍 2
borkdude15:04:34

I think people can use proc/tokenize explicitly to tokenize a string into a vec of strings maybe

borkdude15:04:18

what about the :prev name, keep it or change it?

grzm15:04:37

(Thanks for thinking about this, by the way. The discussion we had above is enough for me to be able to use process as it currently works)

borkdude15:04:27

and should we allow (proc/process {:cmd ["ls"]} "-la") ? (as in merge the both things? I think that's getting out of hand)

grzm15:04:41

re: :prev vs other names, I'd just be spit-balling. :in-proc? Is there any term of art for this in terms of pipelining generally?

borkdude15:04:59

yes, :in-proc sounds perfect, although there is no :out-proc

borkdude15:04:20

in-proc clearly conveys the meaning though

borkdude15:04:32

maybe :in could just support a process

borkdude15:04:36

{:in ls-proc}

borkdude15:04:59

makes sense I think

👍 2
grzm15:04:55

agreed re: getting out of hand with additonal args concat'ed in

borkdude15:04:52

I'll stay with :prev since I already leaked that (bad) name elsewhere

borkdude15:04:06

PR: https://github.com/babashka/process/pull/109 I'll finish it after dinner with some docs.

borkdude10:04:54

Actually I'll wait with merging, since clojure 1.12 will have something like process as well and it's very much inspired by bb process and has the same (recommended) calling convention. https://clojure.atlassian.net/browse/CLJ-2759 By having the same calling convention for both and just better docs should probably solve this.

borkdude11:04:26

I added some extra notes here: https://github.com/babashka/process#syntax Feel free to send a PR with additional improvements if you think it helps

grzm20:04:50

Will do! (If you have any tuits—particularly of the round variety—I'd appreciate any you can pass my way 😛)

mjhika05:04:15

that was a fun video

tcarls23:04:55

Hmm. Adding a new feature (locally -- I don't think there's a compelling case for inclusion upstream here), I can require its namespace when running ./bb (but not run it successfully yet; getting an ExceptionInInitializerError); trying to repro under a "real" JVM in the hopes of getting a more informative stack trace, though, java -jar ./target/babashka-1.3.177-SNAPSHOT-standalone.jar is simply not finding the namespace when trying to import it.