복붙노트

[PYTHON] Subprocess.Popen에서 "source"명령 호출

PYTHON

Subprocess.Popen에서 "source"명령 호출

나는 the_script.sh 소스로 호출하는 .sh 스크립트를 가지고있다. 이것을 정기적으로 부르는 것이 좋습니다. 그러나, 나는 파이썬 스크립트에서 subprocess.Popen을 통해 호출하려고합니다.

Popen에서 호출하면 다음과 같은 두 가지 시나리오 호출에서 다음 오류가 발생합니다.

foo = subprocess.Popen("source the_script.sh")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.7/subprocess.py", line 672, in __init__
    errread, errwrite)
  File "/usr/lib/python2.7/subprocess.py", line 1213, in _execute_child
    raise child_exception
OSError: [Errno 2] No such file or directory


>>> foo = subprocess.Popen("source the_script.sh", shell = True)
>>> /bin/sh: source: not found

뭐라 구요? 파이썬 밖에있을 때 Popen에서 "source"를 호출 할 수없는 이유는 무엇입니까?

해결법

  1. ==============================

    1.소스는 실행 가능한 명령이 아니며 쉘 내장입니다.

    소스는 실행 가능한 명령이 아니며 쉘 내장입니다.

    소스를 사용하는 가장 일반적인 경우는 환경을 변경하고 현재 쉘에서 해당 환경을 유지하는 쉘 스크립트를 실행하는 것입니다. 이것이 바로 virtualenv가 기본 파이썬 환경을 수정하는 방법입니다.

    하위 프로세스를 만들고 하위 프로세스에서 소스를 사용하면 유용하지 않으며 상위 프로세스의 환경을 수정하지 않을 것이므로 원본 스크립트 사용의 부작용은 발생하지 않습니다.

    파이썬은 유사한 명령 인 execfile을 가지고 있습니다.이 파일은 현재의 파이썬 글로벌 네임 스페이스 (또는 다른 것을 제공한다면)를 사용하여 지정된 파일을 실행합니다.이 파일은 bash 명령 소스와 비슷한 방식으로 사용할 수 있습니다.

  2. ==============================

    2.서브 쉘에서 명령을 실행하고 결과를 사용하여 현재 환경을 업데이트 할 수 있습니다.

    서브 쉘에서 명령을 실행하고 결과를 사용하여 현재 환경을 업데이트 할 수 있습니다.

    def shell_source(script):
        """Sometime you want to emulate the action of "source" in bash,
        settings some environment variables. Here is a way to do it."""
        import subprocess, os
        pipe = subprocess.Popen(". %s; env" % script, stdout=subprocess.PIPE, shell=True)
        output = pipe.communicate()[0]
        env = dict((line.split("=", 1) for line in output.splitlines()))
        os.environ.update(env)
    
  3. ==============================

    3.Broken Popen ( "source the_script.sh")은 'source the_script.sh'프로그램을 성공적으로 시작하지 못하도록 시도하는 Popen ([ "source the_script.sh"])과 동일합니다. 찾을 수 없으므로 "No such file or directory"오류가 발생합니다.

    Broken Popen ( "source the_script.sh")은 'source the_script.sh'프로그램을 성공적으로 시작하지 못하도록 시도하는 Popen ([ "source the_script.sh"])과 동일합니다. 찾을 수 없으므로 "No such file or directory"오류가 발생합니다.

    source가 bash 내장 명령 (bash의 도움말 소스)이기 때문에 Broken Popen ( "source the_script.sh", shell = True)이 실패하지만 기본 쉘은 / bin / sh이며이를 이해하지 못합니다 (/ bin / sh는 ). the_script.sh에 다른 bash-ism이 있다고 가정하면 bash를 사용하여 실행해야합니다.

    foo = Popen("source the_script.sh", shell=True, executable="/bin/bash")
    

    @IfLoop은 부모 프로세스 환경에 영향을 줄 수 없기 때문에 서브 프로세스에서 소스를 실행하는 것은 그리 유용하지 않다고 말했다.

    the_script.sh가 일부 변수에 대해 unset을 실행하면 os.environ.update (env) 기반 메소드가 실패합니다. os.environ.clear ()를 호출하여 환경을 재설정 할 수 있습니다.

    #!/usr/bin/env python
    import os
    from pprint import pprint
    from subprocess import check_output
    
    os.environ['a'] = 'a'*100
    # POSIX: name shall not contain '=', value doesn't contain '\0'
    output = check_output("source the_script.sh; env -0",   shell=True,
                          executable="/bin/bash")
    # replace env
    os.environ.clear() 
    os.environ.update(line.partition('=')[::2] for line in output.split('\0'))
    pprint(dict(os.environ)) #NOTE: only `export`ed envvars here
    

    @unutbu가 제안한 env -0 및 .split ( '\ 0')을 사용합니다.

    os.environb에서 임의의 바이트를 지원하기 위해 json 모듈을 사용할 수 있습니다 ( "json.loads가 구문 분석 할 수없는 json.dumps"문제가 수정 된 Python 버전을 사용한다고 가정).

    파이프를 통해 환경을 넘기는 것을 피하기 위해 파이썬 코드를 서브 프로세스 환경에서 호출하도록 변경할 수 있습니다 (예 :

    #!/usr/bin/env python
    import os
    import sys
    from pipes import quote
    from pprint import pprint
    
    if "--child" in sys.argv: # executed in the child environment
        pprint(dict(os.environ))
    else:
        python, script = quote(sys.executable), quote(sys.argv[0])
        os.execl("/bin/bash", "/bin/bash", "-c",
            "source the_script.sh; %s %s --child" % (python, script))
    
  4. ==============================

    4.source는 bash 특정 쉘이 내장되어 있습니다 (비대화 형 쉘은 대개 bash 대신 가벼운 대쉬 쉘입니다). 대신, / bin / sh를 호출하면됩니다.

    source는 bash 특정 쉘이 내장되어 있습니다 (비대화 형 쉘은 대개 bash 대신 가벼운 대쉬 쉘입니다). 대신, / bin / sh를 호출하면됩니다.

    foo = subprocess.Popen(["/bin/sh", "the_script.sh"])
    
  5. ==============================

    5.환경 변수를 설정하고 다른 쉘 작업을 수행 한 다음 해당 환경을 Python 인터프리터로 전파하기 위해 (Python 파일이 아닌) 셸 스크립트를 소스화할 수있는 것이 유용한 경우가 있으므로 @xApple에 대한 변형 서브 쉘이 닫힐 때 그 정보를 잃어 버리는 것보다.

    환경 변수를 설정하고 다른 쉘 작업을 수행 한 다음 해당 환경을 Python 인터프리터로 전파하기 위해 (Python 파일이 아닌) 셸 스크립트를 소스화할 수있는 것이 유용한 경우가 있으므로 @xApple에 대한 변형 서브 쉘이 닫힐 때 그 정보를 잃어 버리는 것보다.

    변형의 이유는 "env"의 한 줄에 하나의 변수 형식을 가정한다는 것이 100 % 강력하지 않다는 것입니다. 즉, 한 변수 (쉘 함수라고 생각합니다)를 처리해야했습니다. 그 파싱을 망쳐 놓은 개행 문자. 여기 약간 더 복잡한 버전이 있습니다.이 버전은 Python 자체를 사용하여 환경 사전을 강력한 방식으로 포맷합니다.

    import subprocess
    pipe = subprocess.Popen(". ./shellscript.sh; python -c 'import os; print \"newenv = %r\" % os.environ'", 
        stdout=subprocess.PIPE, shell=True)
    exec(pipe.communicate()[0])
    os.environ.update(newenv)
    

    아마 더 좋은 방법이 있을까요? 또한 누군가가 echo 문을 소스에있는 스크립트에 넣는 경우 환경 구문 분석이 엉망이되지 않도록합니다. 물론 여기에 임원이있어 신뢰할 수없는 입력에주의해야합니다. 그러나 임의의 쉘 스크립트를 소스 / 실행하는 방법에 대한 토론에 암시 적이라고 생각합니다. ;-)

    UPDATE : @ unutbu의 @xApple 응답에 대한 env 출력의 줄 바꿈을 처리하는 대안 (아마도 더 멋진 방법)에 대한 설명을 참조하십시오.

  6. ==============================

    6.다른 스크립트 또는 실행 파일에 source 명령을 적용하려면 다른 래핑 스크립트 파일을 작성하고 필요한 추가 논리를 사용하여 "source"명령을 호출 할 수 있습니다. 이 경우,이 소스 명령은 실행중인 로컬 컨텍스트, 즉 subprocess.Popen이 작성하는 서브 프로세스를 수정합니다.

    다른 스크립트 또는 실행 파일에 source 명령을 적용하려면 다른 래핑 스크립트 파일을 작성하고 필요한 추가 논리를 사용하여 "source"명령을 호출 할 수 있습니다. 이 경우,이 소스 명령은 실행중인 로컬 컨텍스트, 즉 subprocess.Popen이 작성하는 서브 프로세스를 수정합니다.

    프로그램이 실행중인 파이썬 컨텍스트를 수정해야하는 경우에는 작동하지 않습니다.

  7. ==============================

    7.이것에 대한 답이 많은 것 같고, 모두 읽지 않았기 때문에 이미 지적했을 수도 있습니다. 그러나 이런 쉘 명령을 호출 할 때, popen 호출에 shell = True를 전달해야합니다. 그렇지 않으면 Popen (shlex.split ())을 호출 할 수 있습니다. shlex를 가져 오십시오.

    이것에 대한 답이 많은 것 같고, 모두 읽지 않았기 때문에 이미 지적했을 수도 있습니다. 그러나 이런 쉘 명령을 호출 할 때, popen 호출에 shell = True를 전달해야합니다. 그렇지 않으면 Popen (shlex.split ())을 호출 할 수 있습니다. shlex를 가져 오십시오.

    사실 파일을 소싱하고 현재 환경을 수정하기 위해이 함수를 사용합니다.

    def set_env(env_file):
        while True:
            source_file = '/tmp/regr.source.%d'%random.randint(0, (2**32)-1)
            if not os.path.isfile(source_file): break
        with open(source_file, 'w') as src_file:
            src_file.write('#!/bin/bash\n')
            src_file.write('source %s\n'%env_file)
            src_file.write('env\n')
        os.chmod(source_file, 0755)
        p = subprocess.Popen(source_file, shell=True,
                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        (out, err) = p.communicate()
        setting = re.compile('^(?P<setting>[^=]*)=')
        value = re.compile('=(?P<value>.*$)')
        env_dict = {}
        for line in out.splitlines():
            if setting.search(line) and value.search(line):
                env_dict[setting.search(line).group('setting')] = value.search(line).group('value')
        for k, v in env_dict.items():
            os.environ[k] = v
        for k, v in env_dict.items():
            try:
                assert(os.getenv(k) == v)
            except AssertionError:
                raise Exception('Unable to modify environment')
    
  8. from https://stackoverflow.com/questions/7040592/calling-the-source-command-from-subprocess-popen by cc-by-sa and MIT license