0

I'm trying to setup a system to run some commands on VM's in google cloud, in my case we want to run a tcpdump at a certain time using the 'at' command. Right now I'm just trying to execute any commands successfully, when I have to pass arguments along with the command and getting confusing behaviour, which appears to be that the command, and the arguments are executed as a single long command instead of separate arguments.

I first tried in bash, and thinking my issue was one of quoting, I moved to using python to hopefully make things easier to understand, but I appear to be hitting the same issue and figure I must be doing something wrong.

I have the following functions defined in python, and call them

def execute(cmd):
    popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True)
    for stdout_line in iter(popen.stdout.readline, ""):
        yield stdout_line
    popen.stdout.close()
    return_code = popen.wait()
    if return_code:
        raise subprocess.CalledProcessError(return_code, cmd)

def runCapture(project, instance, zone, time, duration):
    ## Run capture against server
    print ("Running capture against Project: " + project + ", Instance: " + instance + ", Zone: " + zone, "at: " + time, "for " + str(duration) + " minutes")

    ## First connect,  schedule capture
    ## Connect again, schedule upload of capture at capture time + duration time + some overrun.
    ## gcloud compute ssh --project=${PROJECT} ${INSTANCE} --zone="${ZONE}" --command="...do stuff..." --tunnel-through-iap


    ## CMD=\${1:-"/usr/sbin/tcpdump -nn -i ens4 -G \$(( ${DURATION}*60 )) -W 1 -w ./\$(uname -n)-%Y-%m-%d_%H.%M.%S.pcap"}

    total_time=str(duration*60)
    command="/bin/bash -c 'echo \"hello world\"'"

    for path in execute(["/usr/bin/gcloud", "compute", "ssh", instance, "--project="+project, "--zone="+zone, "--tunnel-through-iap", "--command=\""+command+"\"", ]):
        print(path, end="")

The resulting errors are as follows:

bash: /bin/bash -c 'echo hello: No such file or directory
Traceback (most recent call last):
  File "./ingressCapture.py", line 79, in <module>
    results = runCapture(project, instance, zone, time, duration)
  File "./ingressCapture.py", line 33, in runCapture
    for path in execute(["/usr/bin/gcloud", "compute", "ssh", instance, "--project="+project, "--zone="+zone, "--tunnel-through-iap", "--command=\""+command+"\"", ]):
  File "./ingressCapture.py", line 17, in execute
    raise subprocess.CalledProcessError(return_code, cmd)
subprocess.CalledProcessError: Command '['/usr/bin/gcloud', 'compute', 'ssh', 'tbtst-test3-app-egress-nztw', '--project=devops-tb-sandbox-250222', '--zone=europe-west1-b', '--tunnel-through-iap', '--command="/bin/bash -c \'echo "hello world"\'"']' returned non-zero exit status 127.

It appears to me, that instead of invoking the bash shell and running the echo command, it is instead invoking a command that includes the bash shell and then all the arguments too. I have a bash shell when I login normally via SSH, and can run the commands manually (and they work). Why are the arguments for the command from --command="....." getting called like this and how do I prevent it?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459

1 Answers1

1

I'm pretty sure your problem is that you have too many quotes.

When you write --command="bash -c 'echo \"Hello World\"'" on the command line, the shell internally marks all the stuff inside the quotes as being in a quoted state and then removes the quotes. The actual argument that ends up going to the program is --command=bash -c 'echo "Hello World"' as a single string in argv (or your language's equivalent).

Try putting import sys ; print(sys.argv[1]) inside a small python script and calling it with ./test.py --command="bash -c 'echo \"Hello World\"'" to see for yourself.

However, in your arglist to subprocess, you're forming this string: --command="/bin/bash -c 'echo "hello world"'", presumably because you thought you needed to match what you'd normally type on the command line. You can see this in the stacktrace (minus the escaped single quotes, since that's syntax highlighting from python). Since python does not perform quote removal, those quotes are going through to the other side of your ssh connection where the login shell is attempting to reparse it as a shell command. The first "word" on the other end of the connection is /bin/bash -c 'echo hello because of those extra quotes so the shell attempts to find a command with that name on the path, and it clearly doesn't exist.

What you need to put into your arglist for subprocess is simply "--command="+command.

tjm3772
  • 2,346
  • 2
  • 10
  • So in the python, each argument for the execute(...) must be quoted. It appears I'd messed up because of that ;) This appears to work so far, I need to test further but thank you :D – djsmiley2kStaysInside Jan 20 '23 at 08:49
  • 1
    @djsmiley2kStaysInside If you run into trouble with more complex commands then [BashFAQ/096](http://mywiki.wooledge.org/BashFAQ/096) might be of further help. Running complex commands over ssh is notoriously tricky. – tjm3772 Jan 20 '23 at 16:59