서브 프로세스에서 'shell = True'의 실제 의미


260

subprocess모듈로 다른 프로세스를 호출하고 있습니다. 그러나 질문이 있습니다.

다음 코드에서 :

callProcess = subprocess.Popen(['ls', '-l'], shell=True)

callProcess = subprocess.Popen(['ls', '-l']) # without shell

둘 다 작동합니다. 문서를 읽은 후 shell=True쉘을 통해 코드를 실행 한다는 것을 알게되었습니다 . 즉, 부재시 프로세스가 직접 시작됩니다.

그래서 내 경우에 선호해야 할 것은-프로세스를 실행하고 결과를 얻어야합니다. 쉘 내부 또는 외부에서 호출하면 어떤 이점이 있습니까?


21
첫 번째 명령이 잘못되었습니다 : 유닉스의 경우 프로그램 대신 (쉘)에 -l전달 됩니다 . 대부분의 경우 목록 대신 문자열 인수를 사용해야 합니다. /bin/shlsshell=Trueshell=True
jfs

1
"프로세스가 직접 시작되었습니다": Wut?
allyourcode

9
"두 작품 모두." 이 두 통화에 대한 정보가 잘못되어 오도됩니다. 통화는 다르게 작동합니다. 에서 shell=TrueFalse또는 그 반대로 전환 하는 것은 오류입니다. 에서 문서 ". 쉘 = 사실, (...) 인수는 시퀀스의 경우, 첫 번째 항목 지정하는 명령 문자열 및 추가 항목은 쉘 자체에 대한 추가 인수로 처리됩니다과에 POSIX". Windows에는 자동 변환 이 있으며 원치 않을 수 있습니다.
mbdevpl

답변:


183

쉘을 통해 전화하지 않는 이점은 '미스터리 프로그램'을 호출하지 않는다는 것입니다. POSIX에서 환경 변수 SHELL는 어떤 바이너리가 "쉘"로 호출되는지를 제어합니다. Windows에는 bourne shell 하위 항목이 없으며 cmd.exe 만 있습니다.

따라서 쉘을 호출하면 사용자가 선택한 프로그램이 호출되며 플랫폼에 따라 다릅니다. 일반적으로 쉘을 통한 호출을 피하십시오.

쉘을 통해 호출하면 쉘의 일반적인 메커니즘에 따라 환경 변수 및 파일 글로브를 확장 할 수 있습니다. POSIX 시스템에서 쉘은 파일 glob을 파일 목록으로 확장합니다. Windows에서는 파일 glob (예 : "*. *")이 셸에 의해 확장되지 않습니다 (그러나 명령 행의 환경 변수 cmd.exe에 의해 확장 됨).

환경 변수 확장 및 파일 글로브를 원한다고 생각 ILS되면 쉘을 통해 서브 프로그램 호출을 수행 한 네트워크 서비스에 대한 1992 년 의 공격을 조사하십시오 . 여기에는 다양한 sendmail백도어가 포함 ILS됩니다.

요약하면을 사용하십시오 shell=False.


2
답변 해주셔서 감사합니다. 나는 실제로 익스플로잇에 대해 걱정해야 할 단계에 있지는 않지만, 당신이 무엇을 얻고 있는지 이해합니다.
user225312

55
처음에 부주의 한 경우 나중에 걱정하지 않아도됩니다. ;)
Heath Hunnicutt

하위 프로세스의 최대 메모리를 제한하려면 어떻게합니까? stackoverflow.com/questions/3172470/…
Pramod

8
에 대한 진술 $SHELL이 올바르지 않습니다. subprocess.html을 인용하려면 : "로 유닉스 shell=True에서 쉘의 기본값은 /bin/sh입니다." (not $SHELL)
marcin

1
@ user2428107 : 예, Perl에서 백틱 호출을 사용하면 쉘 호출을 사용하고 동일한 문제가 발생합니다. open프로그램을 호출하고 출력을 캡처하는 안전한 방법을 원하면 3+ 인수를 사용하십시오 .
ShadowRanger

137
>>> 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 값으로 설정하면 하위 프로세스가 중간 쉘 프로세스를 생성하고 명령을 실행하도록 지시합니다. 다시 말해서, 중간 쉘을 사용한다는 것은 명령 문자열에서 변수, glob 패턴 및 기타 특수 쉘 기능이 명령이 실행되기 전에 처리됨을 의미합니다. 이 예에서 $ HOME은 echo 명령 전에 처리되었습니다. 실제로 이것은 쉘 확장을 사용하는 명령의 경우이며 ls -l 명령은 간단한 명령으로 간주됩니다.

소스 : 서브 프로세스 모듈


16
이것이 왜 정답이 아닌지 모릅니다. 실제로 질문과 일치하는 것
Rodrigo Lopez Guerra

1
동의하다. 이것은 shell = True의 의미를 이해하기에 좋은 예입니다.
user389955

2
shell 인수를 true 값으로 설정하면 하위 프로세스가 중간 쉘 프로세스를 생성하고 명령을 실행하도록 지시합니다. 왜이 답변이 허용되지 않습니까 ??? 왜?
pouya

문제는 호출 할 첫 번째 인수가 문자열이 아니라 목록이라고 생각하지만 쉘이 False 인 경우 오류가 발생합니다. 목록에 명령을 변경하면이 작업 할 것
링컨 랜달 맥팔랜드

내가 끝나기 전에 이전의 의견이 죄송합니다. 명확하게 : 종종 shell = True와 함께 하위 프로세스 사용을보고 명령은 문자열입니다 (예 : 'ls -l') (이 오류를 피할 것으로 예상됩니다) 하위 프로세스는 목록 (및 문자열을 단일 요소 목록으로 사용) . 쉘을 호출하지 않고 실행하려면 (그리고 보안 문제 ) list subprocess.call ([ 'ls', '-l'])
Lincoln Randall McFarland

42

Shell = True에서 문제가 발생할 수있는 예는 다음과 같습니다.

>>> from subprocess import call
>>> filename = input("What file would you like to display?\n")
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 ()


6
링크는 매우 유용합니다. 링크에서 언급 한 바와 같이 : 신뢰할 수없는 소스에서 비위생 입력을 통합하는 쉘 명령을 실행하면 프로그램이 쉘 주입에 취약 해져 심각한 보안 결함으로 인해 임의의 명령이 실행될 수 있습니다. 이러한 이유로 명령 문자열이 외부 입력으로 구성된 경우 shell = True를 사용하지 않는 것이 좋습니다.
jtuki

39

쉘을 통해 프로그램을 실행한다는 것은 프로그램에 전달 된 모든 사용자 입력이 호출 된 쉘의 구문과 의미 규칙에 따라 해석됨을 의미합니다. 기껏해야 사용자가 이러한 규칙을 준수해야하기 때문에 사용자에게 불편을 초래할뿐입니다. 예를 들어, 따옴표 나 공백과 같은 특수 쉘 문자가 포함 된 경로는 이스케이프해야합니다. 최악의 경우 사용자가 임의의 프로그램을 실행할 수 있기 때문에 보안 누출이 발생합니다.

shell=True단어 분할 또는 매개 변수 확장과 같은 특정 쉘 기능을 사용하는 것이 편리한 경우가 있습니다. 그러나 이러한 기능이 필요한 경우 다른 모듈을 사용하십시오 (예 : os.path.expandvars()매개 변수 확장 또는 shlex워드 분할). 이것은 더 많은 작업을 의미하지만 다른 문제는 피합니다.

한마디로 : 피하십시오 shell=True.


16

여기에있는 다른 답변은 subprocess문서에 언급 된 보안주의 사항을 적절히 설명합니다 . 그러나 그 외에도 쉘을 시작하여 실행하려는 프로그램을 시작하는 오버 헤드는 종종 쉘 기능을 실제로 사용하지 않는 상황에서는 불필요하고 어리 석습니다. 또한, 특히 숨겨진 쉘이나 제공하는 서비스에 익숙하지 않은 경우 숨겨진 추가 복잡성이 두렵 습니다.

쉘과의 상호 작용이 사소한 것이 아니라면, 이제 파이썬 스크립트와 쉘 스크립트를 모두 이해하기 위해 파이썬 스크립트의 독자와 관리자 (나중에있을 수도 있고 아닐 수도 있음)가 필요합니다. "명시적인 것이 묵시적인 것보다 낫다" 라는 파이썬 모토를 기억하십시오 . 파이썬 코드가 동등한 (그리고 종종 매우 간결한) 쉘 스크립트보다 다소 복잡 할지라도 쉘을 제거하고 기능을 원시 파이썬 구문으로 대체하는 것이 좋습니다. 외부 프로세스에서 수행되는 작업을 최소화하고 가능한 한 자신의 코드 내에서 제어를 유지하는 것은 가시성을 향상시키고 원하거나 원치 않는 부작용의 위험을 줄임으로써 종종 좋은 생각입니다.

와일드 카드 확장, 변수 보간 및 리디렉션은 모두 기본 Python 구문으로 간단하게 대체 할 수 있습니다. 파이썬에서 일부 또는 전부를 합리적으로 다시 작성할 수없는 복잡한 쉘 파이프 라인은 아마도 쉘 사용을 고려할 수있는 상황입니다. 여전히 성능 및 보안 관련 사항을 이해해야합니다.

사소한 경우에는 피하기 위해 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()및 백 슬래시 이스케이프 쉘 메타 문자를 인용하는 것이 일반적으로 필요하지 않은 (유용하거나 올바른) 방법에 주목 하십시오. 어쩌면 쉘 변수를 따옴표로 묶을 때를 참조하십시오 .

따로, 패키지 Popen의 가장 간단한 래퍼 중 하나가 원하는 것을 수행하는 경우가 종종 subprocess있습니다. 최근에 충분한 파이썬을 가지고 있다면 아마도을 사용해야 subprocess.run합니다.

  • 함께 check=True명령이 실패 실행 한 경우 그것이 실패합니다.
  • stdout=subprocess.PIPE그 명령의 출력을 캡처합니다.
  • 다소 모호하게도 universal_newlines=True출력을 적절한 유니 코드 문자열로 디코딩합니다 ( bytesPython 3의 경우 시스템 인코딩에 있습니다).

그렇지 않은 경우, 많은 태스크의 경우, check_output명령이 성공했는지 확인하거나 check_call수집 할 출력이 없는지 명령에서 출력을 얻으려고합니다 .

David Korn의 인용을 인용하겠습니다. "휴대용 쉘 스크립트보다 이식 가능한 쉘을 작성하는 것이 더 쉽습니다." 심지어 subprocess.run('echo "$HOME"', shell=True)Windows로 이식 할 수 없습니다.


나는 견적이 래리 월 (Larry Wall)에서 온 것이라고 생각했지만 Google은 그렇지 않다고 말합니다.
tripleee 2016 년

그것은 대단한 이야기이지만 교체를위한 기술적 제안은 없습니다 : OS-X에서 'open'을 통해 시작한 Mac App의 pid를 얻으려고합니다 : process = subprocess.Popen ( '/ usr / bin / pgrep- n '+ app_name, shell = False, stdout = subprocess.PIPE, stderr = subprocess.PIPE) app_pid, err = process.communicate () --- shell = True를 사용하지 않으면 작동하지 않습니다. 이제 뭐?
Motti Shneor

피하는 방법 에 대한 shell=True많은 질문이 있으며 많은 사람들이 훌륭한 답변을 얻습니다. 당신은 대신 에 관한 것인지 를 선택했습니다 .
tripleee

@MottiShneor 피드백에 감사드립니다; 간단한 예를 추가 함
tripleee

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.