运行Python脚本后终端混乱(不显示新行)
我有一个Python脚本,用来在多个主机上并行执行命令,使用的是Python的subprocess模块。这个脚本实际上是通过SSH来运行命令,基本上是这样调用的:
output = subprocess.Popen(["/bin/env", env, "/usr/bin/ssh", "-t", "%s@%s" % (user, host), "--", command], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
实际执行的命令是这样的:
/bin/env TERM=$TERM:password /usr/bin/ssh -t "%s@%s" % (user, host), "--", command
这个脚本运行得挺好的,但有时候我会遇到一个问题,就是在运行脚本后,我的终端会出现一些混乱(比如换行符丢失)。我可以通过在命令行输入“reset”来修复这个问题,但我不太明白这是怎么发生的。我注意到有时候在元组输出的第一个项目末尾会有一个“\r\n”,有时候又没有。比如下面这个,特别是“Permission denied\r\n”:
**** Okay output ****
[user@/home/user]# ./command.py hosts.lists "grep root /etc/shadow"
Running command "grep root /etc/shadow" on hosts in file "hosts.test"
('grep: /etc/shadow: Permission denied\r\n', 'Connection to server1.example.com closed.\r\n')
('grep: /etc/shadow: Permission denied\r\n', 'Connection to server2.example.com closed.\r\n')
[user@/home/user]#
**** Output causes terminal to not display newlines ****
[user@/home/user]# ./command.py hosts.list "grep root /etc/shadow"
('grep: /etc/shadow: Permission denied\r\n', 'Connection to server1.example.com closed.\r\n')
('grep: /etc/shadow: Permission denied\n', 'Connection to server2.example.com closed.\r\n')
[user@/home/user]# [user@/home/user]# [user@/home/user]
第二个输出稍微修改了一下,但显示了缺失的“\r”,以及在运行脚本后我的提示符是怎么被“搞乱”的。
我觉得这可能和我在subprocess命令中使用的“-t”选项有关。不知道为什么我会丢失这个\r。如果我去掉“-t”选项,这个问题就消失了,但长话短说,我需要这个选项来传递环境变量,以便在远程机器上使用(我有点“黑科技”地使用TERM变量来传递用户的sudo密码,因为我不能指望远程的sshd服务器允许随意传递变量;我这样做是为了避免在命令行中传递密码,因为那样会在远程机器的进程列表中显示出来)。
我只是想知道有没有人知道怎么解决这个问题,而不去掉“-t”选项?
更新:看起来在我的脚本中运行subprocess.Popen(...).communicate()命令后,我的tty设置会被改变,不管我是否真的把输出打印到屏幕上。我觉得这很奇怪。以下是我tty配置的前后差异(来自stty -a):
-ignbrk brkint ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff
-ignbrk brkint ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl -ixon -ixoff
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
-opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon -iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt
-isig -icanon -iexten -echo -echoe -echok -echonl -noflsh -xcase -tostop -echoprt
我想知道怎么才能阻止communicate()改变我的终端设置?这是可能的吗,还是说这是个bug?
2 个回答
我在一个Perl脚本中遇到了同样的问题。为了解决这个问题,我需要先保存当前终端的设置(这样在脚本结束时可以恢复),然后在执行远程命令之前加上“stty -raw”。
在Perl中可以这样做:
# 保存当前终端设置(你可以在文件名中加上进程ID)
`stty -g > ~/tmp/.currentTtySettings`;
# 执行远程命令时加上“stty -raw”
my @out=`ssh -t -q user@server1.example.com "stty -raw ; grep root /etc/shadow"`;
# 恢复终端设置
`stty \`cat ~/tmp/.currentTtySettings\``;
希望这对你有帮助!
其他非常有用的链接:
- 关于ssh和tty(-t选项)的详细解释 https://unix.stackexchange.com/questions/151916/why-is-this-binary-file-being-changed
- 一些Perl和ssh的灵感 http://search.cpan.org/~bnegrao/Net-SSH-Expect-1.09/lib/Net/SSH/Expect.pod
- 如何在使用sudo时避免“-t” https://unix.stackexchange.com/questions/122616/why-do-i-need-a-tty-to-run-sudo-if-i-can-sudo-without-a-password