빌드노트
echo로 보이는 변수를 자식 프로그램이 읽지 못할 때
Bash에서 echo로 확인한 값이 자식에게 없는 이유를 셸 변수와 환경의 차이로 설명한다. 합성 변수의 대입·명령 앞 대입·export를 비교하고, 자식의 변경이 부모로 돌아오지 않는 실제 출력으로 전달 범위를 고른다.
Bash에서 echo "$CB_EXPORT_LAB"는 값을 보여 주는데, 같은 셸에서 실행한 프로그램은 변수가 없다고 할 수 있다. 셸에 값을 대입한 것과 자식 프로세스의 환경에 넣은 것은 다르기 때문이다. 한 번 실행할 프로그램에만 필요하면 명령 앞에 이름=값을 붙이고, 이 셸에서 앞으로 실행할 프로그램에도 필요하면 export한다.
셸의 출력과 자식의 입력을 따로 확인한다
임시 Linux 컨테이너에서 합성 변수 CB_EXPORT_LAB 하나로 비교했다. 환경은 Debian 12, Bash 5.2.15, Python 3.12.15였다. 아래 입력은 Bash와 python3가 있는 별도 실습용 Linux 셸에서 위에서부터 이어 실행한다. PowerShell 구문이 아니다. set +a는 자동 export를 끄고, unset은 같은 이름에 남아 있을 값과 export 표시를 지운다. 프로필 파일은 수정하지 않는다.
set +a
unset CB_EXPORT_LAB
CB_EXPORT_LAB=shell
echo "$CB_EXPORT_LAB"
python3 -c 'import os; print(repr(os.getenv("CB_EXPORT_LAB")))'
declare -p CB_EXPORT_LAB
실제 출력은 다음과 같았다.
shell
None
declare -- CB_EXPORT_LAB="shell"
셸에는 shell이 있지만 Python 자식에는 변수가 없다. 여기서 None은 빈 문자열이 아니라 해당 이름이 없다는 뜻이다. Python의 os.getenv 문서 (새 탭에서 열림)에서 반환 규칙을 확인할 수 있다. 같은 실험에서 명령 앞에 빈 값 CB_EXPORT_LAB=을 주면 출력은 ''였다.
echo "$CB_EXPORT_LAB"는 자식의 환경을 검사하는 명령이 아니다. 현재 Bash가 $CB_EXPORT_LAB을 먼저 펼쳐 인수로 넘긴다. 반면 위 Python 코드는 작은따옴표 안에 변수 이름을 적었고, 실행된 Python이 자기 환경에서 그 이름을 찾는다. 이 차이를 보려면 프로그램 안에서 읽은 결과가 필요하다. Bash의 명령 확장 순서 (새 탭에서 열림)에도 인수 확장이 설명돼 있다.
이번 실행에만 줄지, 이후 실행에도 줄지 고른다
같은 셸에서 다음 입력을 이어 실행했다.
CB_EXPORT_LAB=once python3 -c 'import os; print(repr(os.getenv("CB_EXPORT_LAB")))'
echo "$CB_EXPORT_LAB"
python3 -c 'import os; print(repr(os.getenv("CB_EXPORT_LAB")))'
export CB_EXPORT_LAB
declare -p CB_EXPORT_LAB
python3 -c 'import os; print(repr(os.getenv("CB_EXPORT_LAB")))'
'once'
shell
None
declare -x CB_EXPORT_LAB="shell"
'shell'
명령 앞 대입으로 실행한 첫 자식은 once를 받았다. 명령이 끝난 뒤 부모의 값은 shell이었고, 다음 자식에는 여전히 변수가 없었다. 이 방법은 해당 명령에 값을 주며, 그 프로그램이 다시 시작하는 자식에게도 전달될 수 있다.
export CB_EXPORT_LAB 뒤에는 declare -p에 -x가 붙었고, 새 Python 자식도 shell을 읽었다. Bash 매뉴얼의 Environment 절 (새 탭에서 열림)은 export한 변수와 명령 앞 대입을 실행 프로그램의 환경에 넣는다고 설명한다. export는 이 셸에서 이후 실행할 명령에 전달할 표시다. 다른 터미널이나 시스템 전체에 영구 저장하는 작업이 아니다.
따라서 특정 실행만 다른 값을 써야 하면 명령 앞 대입을 고른다. 같은 셸에서 시작할 여러 프로그램이 같은 값을 써야 하면 export한다. 별도 비교에서는 export한 뒤 CB_EXPORT_LAB=changed만 대입해도 다음 자식이 changed를 받았다. 매번 export를 반복할 필요가 없었다. 그 상태에서 한 명령에만 once를 주어도 다음 자식의 값은 다시 changed였다.
자식에서 바꾼 값은 부모로 돌아오지 않았다
위의 본문 입력만 이어 실행했다면 부모의 값은 아직 shell이다. 이번에는 Python 자식이 자기 환경을 바꾸게 했다.
python3 -c 'import os; os.environ["CB_EXPORT_LAB"] = "child"; print(repr(os.getenv("CB_EXPORT_LAB")))'
echo "$CB_EXPORT_LAB"
python3 -c 'import os; print(repr(os.getenv("CB_EXPORT_LAB")))'
'child'
shell
'shell'
첫 자식 안에서는 child로 바뀌었지만, 부모와 부모가 나중에 실행한 다른 자식은 shell을 유지했다. 추가 실험에서 값을 바꾼 자식이 직접 시작한 손자 프로세스는 child를 받았다. 자식에서 바뀐 값이 부모에게 합쳐지는 동작은 없었다. 이는 별도 실행 환경은 호출한 셸의 환경을 바꾸지 못한다는 Bash 설명 (새 탭에서 열림)과 맞는다.
실제 프로그램을 확인할 때도 전체 환경을 덤프하기보다 필요한 이름 하나의 존재 여부부터 본다. 자식에 이름 자체가 없다면 그 프로그램을 시작하는 부모에서 전달하도록 고친다. 자식이 이미 기대한 값을 읽고 있는데 앱 설정만 다르다면 export만 반복할 이유는 없다. 그다음은 앱의 변수 이름 매핑과 설정 우선순위를 확인할 차례다.
이 관찰은 새 Bash가 직접 시작한 Python 프로세스와 추가 Bash 자식으로 확인했다. IDE·서비스 관리자·Compose가 환경을 구성하는 경로, 다른 셸과 POSIX 모드의 특수 내장 명령은 실행하지 않았다. 참고한 Bash 매뉴얼은 유지관리자 사이트의 5.3판이며, 실행한 Bash 5.2.15와 버전을 구분했다. 실습을 마치면 사용한 별도 셸에서 exit로 나온다.