sh icon indicating copy to clipboard operation
sh copied to clipboard

Nested quotes not visible in stringification

Open eode opened this issue 6 months ago • 3 comments

I'm using sh.py to run commands in docker containers:

>>> container = sh.docker.bake(CONTAINER_NAME).bash.bake('-c')

# example usage -- works correctly
# executes the bash equivalent of "/usr/bin/docker exec mycontainer bash -c 'ls -l foo bar baz'"
>>> container("ls -l foo bar baz")

# this does the same, but leaves me an object I can use for introspection and logging
>>> baked = container.bake("ls -l foo bar baz")
>>> baked()    # executes correctly

# example stringification of the baked command
str(baked)
"/usr/bin/docker exec mycontainer bash -c ls -l foo bar baz"

# ..so
"/usr/bin/docker exec mycontainer bash -c 'ls -l foo bar baz'"    # execution
"/usr/bin/docker exec mycontainer bash -c ls -l foo bar baz"      # stringification

Is it reasonably possible to change this to be more accurate, for the purposes of logging what commands are being used?

Thanks for your development of this library, btw, it's probably my favorite subprocess tool -- and thanks for your time looking at this issue.

As a side note, since bash requires the actual command given to '-c' to follow as a single argument, I can't use sh.py as though it had access to the docker container directly. This is, overall, acceptable, though, because I can still use sh.py for the job of making things easier to reference. I'm not sure if this is a library limitation or my limited understanding of sh.py. Care to enlighten me?

# side note example
# Possible
container = sh.docker.bake(CONTAINER_NAME).bash.bake('-c')
container("ls -l foo bar baz")

# Not Possible, unless there's a way I don't know
# example 1
container.ls('-l', 'foo', 'bar', 'baz')
# example 2
container_ll = container.ls.bake('-l')
container_ll('foo', 'bar', 'baz')

Again, that last code snippet is just an aside.

eode avatar May 19 '25 16:05 eode

Regarding your side note, you're effectively constructing a command that is a string argument to another command. You could do that with the tools that are already available:

import ls
container = sh.docker.bake("exec", "my_container", "bash", "-c")
cmd = sh.ls.bake("-l", "foo", "bar", "baz")
container(str(cmd))

ecederstrand avatar May 19 '25 19:05 ecederstrand

Regarding the original question, we would need to create a function that identifies if an argument requires quoting at all, since you probably don't want the str() output to be 'bash' '-c' 'ls ...' even though that's valid syntax. Plus, we'd need to escape existing single quotes correctly when printing quoted arguments.

There's a pretty good explanation of single-quoted bash variables rules at https://tldp.org/LDP/abs/html/quotingvar.html.

ecederstrand avatar May 23 '25 06:05 ecederstrand

I want the problem to be as simple as "if there are spaces in this single word argument, quote it", but knowing my luck there's some other kind of chicanery.

also, it might be worth mentioning that, if I'm reading it right, the container(str(subcommand))() method wouldn't work if subcommand itself had a multiword argument, because that also wouldn't be quoted, due to the str(command_with_multiword_arg) issue.

eode avatar May 23 '25 11:05 eode