使用gcloud SSH命令传参时参数被识别为单个字符串的问题排查
GCP虚拟机通过Python调用gcloud ssh执行带参数命令失败问题
问题背景
正在搭建在Google Cloud虚拟机上运行命令的系统,计划使用at命令在指定时间执行tcpdump。测试带参数的命令执行时遇到异常:通过Python调用gcloud compute ssh传递的命令与参数被识别为单个长命令,而非独立参数。
最初尝试Bash实现,以为是引号问题,改用Python后仍出现相同问题。
相关代码
定义的Python函数及调用代码
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="")
执行错误信息
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", "tbtst-test3-app-egress-nztw", "--project=devops-tb-sandbox-250222", "--zone=europe-west1-b", "--tunnel-through-iap", "--command=\"/bin/bash -c \'echo \"hello world\"\'\"", ]): 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.
问题原因
使用subprocess.Popen传递参数时,不需要给--command的内容额外添加引号。当前写法中,--command=\""+command+"\"会把引号作为参数的一部分传给gcloud,导致远端bash接收到带外层引号的字符串,进而错误地将/bin/bash -c 'echo hello当成完整命令(内部引号解析混乱导致命令截断),最终找不到这个不存在的命令。
手动SSH登录时,shell会自动处理引号解析,所以命令能正常执行,但Python的subprocess是按列表分割传递参数的,额外引号会被当作参数内容,而非shell语法符号。
解决方案
- 去掉
--command参数外层的额外引号,直接将命令字符串作为--command的值传递。 - 调整内部命令的引号转义,避免解析混乱。
修改后的核心代码
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") total_time=str(duration*60) # 调整命令的引号,用双引号包裹内部命令,避免转义混乱 command='/bin/bash -c "echo \\"hello world\\""' # --command参数直接传命令字符串,不需要额外加引号 for path in execute([ "/usr/bin/gcloud", "compute", "ssh", instance, f"--project={project}", f"--zone={zone}", "--tunnel-through-iap", f"--command={command}" ]): print(path, end="")
简化写法(无特殊需求时)
如果只是执行简单命令,可去掉额外的bash -c嵌套:
# 简化命令 command='echo "hello world"' # 传递给gcloud ssh for path in execute([ "/usr/bin/gcloud", "compute", "ssh", instance, f"--project={project}", f"--zone={zone}", "--tunnel-through-iap", f"--command={command}" ]): print(path, end="")
针对tcpdump的最终实现
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") # 构造tcpdump命令 tcpdump_cmd = f'/usr/sbin/tcpdump -nn -i ens4 -G {duration*60} -W 1 -w ./$(uname -n)-%Y-%m-%d_%H.%M.%S.pcap' # 用at命令调度执行 command = f'at {time} -f <(echo "{tcpdump_cmd}")' # 传递给gcloud ssh for path in execute([ "/usr/bin/gcloud", "compute", "ssh", instance, f"--project={project}", f"--zone={zone}", "--tunnel-through-iap", f"--command={command}" ]): print(path, end="")
关键要点
subprocess的参数列表中,每个元素都是独立的命令/参数,不需要手动加引号分割,subprocess会自动处理。- 远端执行的命令内部如果需要引号,要正确转义,避免和外层引号冲突。
- 执行复杂命令时,确保远端shell能正确解析结构,必要时用
bash -c包裹,但要注意引号的嵌套和转义。
内容的提问来源于stack exchange,提问作者djsmiley2kStaysInside
相关产品推荐
相关产品推荐

