子进程中‘shell=True’的实际含义
- 2024-11-18 08:41:00
- admin 原创
- 11
问题描述:
我正在用模块调用不同的进程subprocess
。但是,我有一个问题。
在下面的代码中:
callProcess = subprocess.Popen(['ls', '-l'], shell=True)
和
callProcess = subprocess.Popen(['ls', '-l']) # without shell
两种方法都可以。阅读文档后,我才知道这shell=True
意味着通过 shell 执行代码。也就是说,在没有 shell 的情况下,进程会直接启动。
那么对于我的情况我应该选择什么 - 我需要运行一个进程并获取其输出。从 shell 内部还是外部调用它有什么好处?
解决方案 1:
不通过 shell 调用的好处是,您不会调用“神秘程序”。在 POSIX 上,环境变量SHELL
控制哪个二进制文件被调用为“shell”。在 Windows 上,没有 bourne shell 后代,只有 cmd.exe。
因此,调用 shell 会调用用户选择的程序,并且与平台相关。一般来说,应避免通过 shell 进行调用。
通过 shell 调用确实允许您根据 shell 的常用机制扩展环境变量和文件全局变量。在 POSIX 系统上,shell 将文件全局变量扩展为文件列表。在 Windows 上,shell 无论如何都不会扩展文件全局变量(例如“.”)(但命令行上的环境变量由 cmd.exe扩展)。
如果您认为需要环境变量扩展和文件全局,请研究ILS
1992 年左右对网络服务的攻击,这些攻击通过 shell 执行子程序调用。示例包括sendmail
涉及 的各种后门ILS
。
总之,使用shell=False
。
解决方案 2:
>>> import subprocess
>>> subprocess.call('echo $HOME')
Traceback (most recent call last):
...
OSError: [Errno 2] No such file or directory
>>>
>>> subprocess.call('echo $HOME', shell=True)
/user/khong
0
将 shell 参数设置为 true 值会导致 subprocess 生成一个中间 shell 进程,并告诉它运行该命令。换句话说,使用中间 shell 意味着在运行命令之前会处理命令字符串中的变量、glob 模式和其他特殊 shell 功能。在此示例中,$HOME 在 echo 命令之前被处理。实际上,这是带有 shell 扩展的命令的情况,而命令 ls -l 被视为简单命令。
来源:子流程模块
解决方案 3:
这里显示了 Shell=True 可能出错的示例
>>> from subprocess import call
>>> filename = input("What file would you like to display?
")
What file would you like to display?
non_existent; rm -rf / # THIS WILL DELETE EVERYTHING IN ROOT PARTITION!!!
>>> call("cat " + filename, shell=True) # Uh-oh. This will end badly...
在此处查看文档:subprocess.call()
解决方案 4:
通过 shell 执行程序意味着传递给程序的所有用户输入都将根据所调用 shell 的语法和语义规则进行解释。在最好的情况下,这只会给用户带来不便,因为用户必须遵守这些规则。例如,必须对包含引号或空格等特殊 shell 字符的路径进行转义。在最坏的情况下,这会导致安全漏洞,因为用户可以执行任意程序。
shell=True
有时使用特定的 shell 功能(如分词或参数扩展)会很方便。但是,如果需要这样的功能,请使用提供给您的其他模块(例如os.path.expandvars()
用于参数扩展或shlex
分词)。这意味着更多的工作,但可以避免其他问题。
简而言之:shell=True
一定要避免。
解决方案 5:
这里的其他答案充分解释了文档中提到的安全注意事项subprocess
。但除此之外,启动 shell 来启动要运行的程序的开销通常是不必要的,而且对于您实际上不使用任何 shell 功能的情况来说绝对是愚蠢的。此外,额外的隐藏复杂性应该会让您感到害怕,特别是如果您不太熟悉 shell 或它提供的服务。
当与 shell 的交互并不简单时,您现在需要 Python 脚本的读者和维护者(可能是也可能不是您自己)了解 Python 和 shell 脚本。请记住 Python 的座右铭“显式优于隐式”;即使 Python 代码比等效(通常非常简洁)的 shell 脚本稍微复杂一些,您最好还是删除 shell 并用本机 Python 构造替换功能。尽量减少在外部进程中完成的工作并尽可能在您自己的代码中保持控制通常是一个好主意,因为它可以提高可见性并降低出现(想要的或不想要的)副作用的风险。
通配符扩展、变量插值和重定向都很容易用原生 Python 结构替换。如果某个复杂的 shell 管道的部分或全部无法用 Python 合理地重写,那么也许您可以考虑使用 shell。您仍应确保了解性能和安全影响。
在简单情况下,为了避免shell=True
,只需替换
subprocess.Popen("command -with -options 'like this' and\\ an\\ argument", shell=True)
和
subprocess.Popen(['command', '-with','-options', 'like this', 'and an argument'])
请注意,第一个参数是传递给的字符串列表execvp()
,以及引用字符串和反斜杠转义的 shell 元字符通常不是必需的(或有用的,或正确的)。也许还可以参阅何时将引号括在 shell 变量周围?
如果您不想自己弄清楚,该shlex.split()
函数可以为您完成此操作。它是 Python 标准库的一部分,但当然,如果您的 shell 命令字符串是静态的,您可以在开发过程中运行一次,然后将结果粘贴到脚本中。
另外,Popen
如果包中较简单的包装器之一subprocess
满足您的要求,您通常希望避免这种情况。如果您的 Python 版本足够新,则可能应该使用subprocess.run
。
check=True
如果您运行的命令失败,它也会失败。它将
stdout=subprocess.PIPE
捕获命令的输出。使用
text=True
(或者有点晦涩,使用同义词universal_newlines=True
),它会将输出解码为正确的 Unicode 字符串(bytes
否则,在 Python 3 上,它只是系统编码)。
如果不是,对于许多任务来说,您希望check_output
获取命令的输出,同时检查它是否成功,或者check_call
是否没有要收集的输出。
最后,我想引用 David Korn 的一句话:“编写可移植的 shell 比编写可移植的 shell 脚本更容易。” 即使subprocess.run('echo "$HOME"', shell=True)
无法移植到 Windows。
解决方案 6:
上面的答案解释得没错,但不够直接。让我们使用ps
命令看看会发生什么。
import time
import subprocess
s = subprocess.Popen(["sleep 100"], shell=True)
print("start")
print(s.pid)
time.sleep(5)
s.kill()
print("finish")
运行它,并显示
start
832758
finish
然后你可以使用ps -auxf > 1
before finish
,然后是ps -auxf > 2
after finish
。以下是输出
1
cy 71209 0.0 0.0 9184 4580 pts/6 Ss Oct20 0:00 | _ /bin/bash
cy 832757 0.2 0.0 13324 9600 pts/6 S+ 19:31 0:00 | | _ python /home/cy/Desktop/test.py
cy 832758 0.0 0.0 2616 612 pts/6 S+ 19:31 0:00 | | _ /bin/sh -c sleep 100
cy 832759 0.0 0.0 5448 532 pts/6 S+ 19:31 0:00 | | _ sleep 100
sleep 100
看到了吗?它实际上运行的是,/bin/sh
而不是直接运行。pid
它打印出来的实际上是pid
。/bin/sh
之后,如果你调用s.kill()
,它会被杀死/bin/sh
,但sleep
仍然存在。
2
cy 69369 0.0 0.0 533764 8160 ? Ssl Oct20 0:12 _ /usr/libexec/xdg-desktop-portal
cy 69411 0.0 0.0 491652 14856 ? Ssl Oct20 0:04 _ /usr/libexec/xdg-desktop-portal-gtk
cy 832646 0.0 0.0 5448 596 pts/6 S 19:30 0:00 _ sleep 100
那么下一个问题是,它能做什么/bin/sh
?每个 Linux 用户都知道它、听说过它并使用它。但我敢打赌,有很多人并不真正了解它shell
到底是什么。也许你也听说过/bin/bash
,它们很相似。
shell 的一个明显功能就是方便用户运行 linux 应用程序。因为有像sh
或 这样的 shell 程序bash
,您可以直接使用像 这样的命令ls
而不是/usr/bin/ls
。它会搜索 在哪里ls
并为您运行它。
另一个功能是它将后面的字符串解释$
为环境变量。你可以比较这两个 python 脚本来自己找出答案。
subprocess.call(["echo $PATH"], shell=True)
subprocess.call(["echo", "$PATH"])
最重要的是,它可以将 Linux 命令作为脚本运行。例如if
else
由 shell 引入的命令。它不是原生的 Linux 命令
解决方案 7:
假设您使用 shell=False 并以列表形式提供命令。一些恶意用户尝试注入“rm”命令。您将看到,“rm”将被解释为参数,并且实际上“ls”将尝试查找名为“rm”的文件
>>> subprocess.run(['ls','-ld','/home','rm','/etc/passwd'])
ls: rm: No such file or directory
-rw-r--r-- 1 root root 1172 May 28 2020 /etc/passwd
drwxr-xr-x 2 root root 4096 May 29 2020 /home
CompletedProcess(args=['ls', '-ld', '/home', 'rm', '/etc/passwd'], returncode=1)
如果您没有正确控制输入,则默认情况下 shell=False 并不安全。您仍然可以执行危险的命令。
>>> subprocess.run(['rm','-rf','/home'])
CompletedProcess(args=['rm', '-rf', '/home'], returncode=0)
>>> subprocess.run(['ls','-ld','/home'])
ls: /home: No such file or directory
CompletedProcess(args=['ls', '-ld', '/home'], returncode=1)
>>>
我在容器环境中编写了大多数应用程序,我知道哪个 shell 被调用,并且我没有接受任何用户输入。
因此,在我的使用案例中,我没有看到任何安全风险。而且创建长串命令要容易得多。希望我没有错。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件