如何调试接受标准输入的Python CLI?

34
我正在尝试调试我编写的Python命令行界面,它可以从stdin获取它的参数。一个简单的测试案例会产生以下输出:
echo "test" | python mytool.py

等价于输出结果为

python mytool.py test

我想调试一些这个工具的问题,所以我尝试运行了这个命令:

echo "test" | pdb mytool.py

但我得到了这个输出,然后pdb退出:

> /path/to/mytool.py(5)<module>()
-> '''
(Pdb) *** NameError: name 'test' is not defined
(Pdb)

如果我在shebang中添加-m python,或者在脚本中运行pdb.set_trace(),也会发生同样的事情。

这里到底发生了什么?


2
你能否修改你的脚本,使其可以从除标准输入外的文件中接受输入? - nmichaels
1
@user1901786 在 CLI 脚本中如何访问 stdin?使用 sys.stdin 吗?此外,您是想为此编写一个测试还是需要在脚本内启动调试器?如果您需要调试器,在哪个时点要让它参与脚本?抱歉,我对问题有点不清楚。 - famousgarkin
总的想法是能够在接受stdin(在这种情况下,通过sys.stdin)的脚本中启动调试器。我宁愿不拆开脚本并使其接受文件输入,因为我正在测试确定脚本是否读取stdin的逻辑。实际上,我只是尝试做我在问题中描述的事情,而PDB抛出的错误对我来说没有多大意义。 - whereswalden
1
@Paul Sweatte。不是重复的。 - Charles Merriam
1
我会选择使用PyCharm社区版来进行调试。如果您可以手动输入或粘贴输入,则可以使用标准本地调试。如果您需要从管道或重定向获取调试输入,则可能需要使用远程调试,这需要稍微复杂一些的设置。 - Preston Landers
显示剩余2条评论
4个回答

20

另一个选项是创建您自己的Pdb对象,并在其中设置stdin和stdout。我的概念验证涉及2个终端,但肯定可以将一些工作合并成某种非常不安全的网络服务器。

  1. 创建两个命名管道:

mkfifo fifo_stdin
mkfifo fifo_stdout
  • 在一个终端中,将标准输出打开到后台,并写入标准输入:

  • cat fifo_stdout &
    cat > fifo_stdin
    
  • 在您的Python代码/控制台中创建pdb对象,并使用它:

  • import pdb
    mypdb=pdb.Pdb(stdin=open('fifo_stdin','r'), stdout=open('fifo_stdout','w'))
    ...
    mypdb.set_trace()
    ...
    
  • 获利!

  • 您应该能够在第一个控制台上使用pdb。

    唯一的缺点是需要使用自定义pdb,但一些在初始化时进行的猴子补丁(例如PYTHONSTARTUP或类似方式)可以帮助解决问题:

    import pdb
    mypdb=pdb.Pdb(stdin=open('fifo_stdin','r'), stdout=open('fifo_stdout','w'))
    pdb.set_trace=mypdb.set_trace
    

    非常方便,例如当您使用Pytest调试Flask时。自Python 3.7以来,有断点(breakpoint()):breakpoint(stdin=open('fifo_stdin', 'r'), stdout=open('fifo_stdout', 'w')) - DZet

    6

    你的控制终端仍然是一个终端,对吗?使用这个而不是 pdb.set_trace

    def tty_pdb():
        from contextlib import (_RedirectStream,
                                redirect_stdout, redirect_stderr)
        class redirect_stdin(_RedirectStream):
            _stream = 'stdin'
        with open('/dev/tty', 'r') as new_stdin, \
             open('/dev/tty', 'w') as new_stdout, \
             open('/dev/tty', 'w') as new_stderr, \
             redirect_stdin(new_stdin), \
             redirect_stdout(new_stdout), redirect_stderr(new_stderr):
            __import__('pdb').set_trace()
    

    我还没有在这种情况下让readline实现自动完成。向上箭头也不起作用,也没有其他readline的好处。

    这很酷,但是执行l命令显示我们停在了tty_pdb()函数的中间,而不是调用它的地方。如果你需要检查那个地方的状态,这就有点麻烦了。 - undefined

    6

    您可以使用另一个文件描述符。在bash中,您可以使用以下命令创建新的文件描述符:

    exec 3<> test.txt
    

    然后在您的Python文件中添加类似以下内容的代码:

    #!/usr/bin/python
    
    # Use fd 3 as another stdin file.
    import os
    stdin=os.fdopen(3)
    
    while True:
        s=stdin.readline()
        import pdb; pdb.set_trace()
        print len(s)
    

    只需运行您的脚本,它会使用test.txt作为输入,并且可以在stdin上使用。如果需要,它也可以与管道一起使用。

    5
    我希望使用不需要修改工具代码的解决方案。 - whereswalden

    2
    当您使用pdb(或任何其他Python调试器)时,它会获取stdin以用于调试命令,这就是为什么您会收到“ NameError:name'test'未定义”的原因。
    例如,以下命令将在运行时的开头退出调试器,并且您不会在一个运行中遇到此错误(也不会进行交互式调试):
    (echo cont;echo "test") | python -m pdb mytool.py

    2
    虽然这解释了正在发生的事情,但它并没有提供答案。 - whereswalden
    2
    @whereswalden 实际上,“答案”是pdb从stdin获取输入时存在问题,这很愚蠢。 :/ - Otheus

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接