通过Python管道执行需用户输入的Shell脚本无输出问题
解决Python执行需交互的Shell脚本无输出问题
你遇到的问题很典型——你的svnenv.sh脚本需要用户输入,但当前的subprocess配置没有处理标准输入,导致脚本卡在等待输入的环节,communicate()一直拿不到输出结果。我给你几个针对性的解决方案:
问题核心
你的代码里只设置了stdout=subprocess.PIPE,但没有处理stdin(标准输入),而脚本需要用户输入才能继续执行,所以进程会一直阻塞在等待输入的状态,自然无法返回输出。
解决方案
1. 脚本输入固定,直接传入预设内容
如果脚本需要的输入是固定的(比如确认输入yes),可以直接在communicate()里传入输入内容,同时要指定stdin=subprocess.PIPE:
def(request,self): args_str1 = "flexiserver/build/svnenv.sh -j 'svntasktag.pl ss_fvnteste'" args1 = shlex.split(args_str1) # 同时指定stdin和stderr管道,才能处理输入和错误输出 pi = subprocess.Popen( args1, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE ) print("***** Before out err *****") # 假设脚本需要输入"yes",转成字节类型传入 input_content = b"yes\n" out, err = pi.communicate(input=input_content) # 把字节输出转成字符串,方便页面展示 out_str = out.decode('utf-8') err_str = err.decode('utf-8') print("OUT is %s " % out_str) context = {'out': out_str, 'err': err_str} req_context = RequestContext(request) return render_to_response( 'pai_app/create_tag.html', context, context_instance=req_context )
这里要注意:
- 必须指定
stdin=subprocess.PIPE,才能给子进程传递输入 communicate()的input参数需要是字节类型,所以用b"yes\n"- 把
out和err解码成字符串,避免页面显示字节对象
2. 让用户在网页端输入内容后传递给脚本
如果需要用户手动输入内容,先在前端页面加一个输入框(比如name设为script_input),然后在视图函数里获取用户输入再传递:
def(request,self): # 从POST请求中获取用户输入,默认空字符串 user_input = request.POST.get('script_input', '') + "\n" args_str1 = "flexiserver/build/svnenv.sh -j 'svntasktag.pl ss_fvnteste'" args1 = shlex.split(args_str1) pi = subprocess.Popen( args1, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE ) print("***** Before out err *****") # 把用户输入转成字节类型传入 out, err = pi.communicate(input=user_input.encode('utf-8')) out_str = out.decode('utf-8') err_str = err.decode('utf-8') print("OUT is %s " % out_str) context = {'out': out_str, 'err': err_str} req_context = RequestContext(request) return render_to_response( 'pai_app/create_tag.html', context, context_instance=req_context )
3. 处理复杂交互式场景(多次输入)
如果脚本需要多次交互(比如先输入用户名,再输入密码),subprocess就不太方便了,推荐用pexpect库专门处理交互式命令:
首先安装pexpect:
pip install pexpect
然后修改代码:
import pexpect def(request,self): cmd = "flexiserver/build/svnenv.sh -j 'svntasktag.pl ss_fvnteste'" # 启动子进程 child = pexpect.spawn(cmd) # 监听脚本输出,匹配到"Enter username:"时发送输入 child.expect("Enter username:") child.sendline("your_username") # 匹配到"Enter password:"时发送密码 child.expect("Enter password:") child.sendline("your_password") # 等待命令执行结束 child.expect(pexpect.EOF) # 获取输出内容 out = child.before.decode('utf-8') # 获取错误输出 err = child.stderr.read().decode('utf-8') if child.stderr else "" context = {'out': out, 'err': err} req_context = RequestContext(request) return render_to_response( 'pai_app/create_tag.html', context, context_instance=req_context )
pexpect的优势是可以精准匹配脚本的输出提示,然后发送对应的输入,完美处理多步骤交互。
额外注意事项
- 一定要指定
stderr=subprocess.PIPE(或用pexpect处理错误输出),否则你拿不到脚本的错误信息,调试会很麻烦 - 注意字节和字符串的转换,Python3里subprocess的输出默认是字节类型,必须解码成字符串才能正常展示
- 安全问题:如果涉及用户输入,一定要做严格的校验,避免命令注入攻击
内容的提问来源于stack exchange,提问作者SinghB
相关产品推荐
相关产品推荐

