이 글은 shell에서 환경 변수를 참조하는 방법과 env에서 쉘 변수를 어떻게 지정하는지에 대해 다루고 있습니다.
env 명령어
minishell 과제에서 built-in 명령어들은 모두 환경변수와 관련된 명령어 입니다.
env 명령어는 옵션을 제외하고 다음과 같이 3가지 방식으로 사용할 수 있습니다.
1. 환경변수 목록 출력
# 입력
env
# 출력
TERM_SESSION_ID=w0t0p0:4A2AC993-C7C8-4745-9387-F181921C1CC8
SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.bGePyIUzEC/Listeners
LC_TERMINAL_VERSION=3.4.23
# ...
2. 하나 이상의 쉘 변수를 포함한 환경변수 목록을 출력
# 입력
env TEST=test
# 입력
TERM_SESSION_ID=w0t0p0:4A2AC993-C7C8-4745-9387-F181921C1CC8
SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.bGePyIUzEC/Listeners
# ...
TEST=test # 쉘 변수
3. 하나 이상의 쉘 변수를 환경 변수로 지정하여 특정 명령어 실행
# 입력
env {key=value} other_command # other_command 명령어 실행
즉, env 명령어를 사용해서 출력되는 변수들의 목록은 환경변수 뿐만 아니라 env 명령어의 매개변수로 전달한 쉘 변수를 포함합니다.
하지만 해당 쉘 변수로 환경변수로 지정해서 특정 명령어로 실행을 하면
해당 쉘 변수는 해당 명령어에서는 환경변수로 사용되지만 현재 쉘 에서는 환경변수로 저장되지 않습니다.
쉘 변수
쉘 변수란 해당 쉘 스크립트 혹은 쉘 세션에서 임시로 사용하는 변수를 의미합니다.
쉘 스크립트에서 정의되는 변수로서 쉘 세션에서도 사용될 수 있습니다.
쉘 변수를 할당하는 방법은 다음과 같습니다.
쉘 변수는 문자열이나 숫자 혹은 배열도 가능합니다.
# 문자열 할당
name="John"
# 숫자 할당
number=10
# 배열 할당
my_array=(apple banana cherry)
환경 변수
환경변수란 프로세스가 컴퓨터에서 동작하는 방식에 영향을 미치는, 동적인 값들의 모임입니다.
쉽게 말해서 PATH
, HOME
등 프로세스가 동작하는데 사용되는 운영체제의 전역 변수라고 생각하면 됩니다.
쉘에서 환경변수를 조작하는 방법은 다음과 같이 3가지가 있습니다.
envp
매개변수environ
전역변수getenv()
함수
환경 변수를 담은 문자열 배열
이들이 가져오는 환경 변수가 정확히 어디에 저장되어있는지 확인하기 위해
다음과 같이 각각의 방식으로 환경변수를 주소와 함께 출력하는 테스트를 작성했습니다.
// env_test.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern char **environ;
int main(int argc, char **argv, char **envp)
{
int i;
char *str;
printf("+===========================+\n");
printf("| environ |\n");
printf("+===========================+\n");
i = 0;
while (environ[i])
{
if (strncmp(envp[i], "TEST", 4) == 0)
printf("[%p] %s\n", environ[i], environ[i]);
i++;
}
printf("\n");
printf("+===========================+\n");
printf("| envp |\n");
printf("+===========================+\n");
i = 0;
while (envp[i])
{
if (strncmp(envp[i], "TEST", 4) == 0)
printf("[%p] %s\n", envp[i], envp[i]);
i++;
}
printf("\n");
printf("+===========================+\n");
printf("| getenv |\n");
printf("+===========================+\n");
str = getenv("TEST");
printf("[%p] %s\n", str, str);
if (str)
printf("[%p] %s\n", str - 5, str - 5);
return (0);
}
테스트 코드를 실행하기 전에 다음과 같이 환경 변수를 설정해야합니다.
export TEST=test
테스트 결과는 다음과 같습니다.
+===========================+
| environ |
+===========================+
[0x16bb4fe56] TEST=test
+===========================+
| envp |
+===========================+
[0x16bb4fe56] TEST=test
+===========================+
| getenv |
+===========================+
[0x16bb4fe5b] test
[0x16bb4fe56] TEST=test
테스트 결과 모두 같은 주소를 가리키고 있습니다.
즉, envrion
과 envp
는 모두 같은 문자열 배열을 가리키고 있고
getenv()
함수를 사용할 경우 해당 문자열 배열의 value 부분("key=value
" 형식의 문자열에서 =
뒷부분)을 가리키고 있음을 알 수 있습니다.
envp 매개변수에 문자열 배열이 할당되는 시점
그렇다면 envp
변수에 환경변수 문자열 배열이 언제 할당이 되는 것일까요?
이를 확인하기 위해 execve()
함수를 사용하여 위의 테스트 코드를 실행하는 exec_test_01 테스트를 작성했습니다.
// exec_test_01.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
char *strjoin(char *s1, char *s2)
{
char *str;
size_t s1_len;
size_t s2_len;
s1_len = strlen(s1);
s2_len = strlen(s2);
str = (char *)malloc(sizeof(char) * (s1_len + s2_len + 1));
if (!str)
return (NULL);
memcpy(str, s1, s1_len);
memcpy(str + s1_len, s2, s2_len);
str[s1_len + s2_len] = '\0';
return (str);
}
int main(int argc, char **argv, char **envp)
{
char *pwd;
char *cmd;
pwd = getcwd(NULL, 0);
cmd = strjoin(pwd, "/env_test.out");
execve(cmd, NULL, NULL);
free(pwd);
return (0);
}
만약 execve()
에서 3번째 매개변수(envp
)에 NULL
을 전달하면
쉘에서 환경변수가 설정되어 있더라도 다음과 같이 전달되지 않는 모습을 볼 수 있습니다.
+===========================+
| environ |
+===========================+
+===========================+
| envp |
+===========================+
+===========================+
| getenv |
+===========================+
[0x0] (null)
다른 테스트 케이스를 확인하기 위해 직접 문자열 배열을 만들어서 전달하는 exec_test_02를 작성했습니다.
// exec_test_02.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
char *strjoin(char *s1, char *s2)
{
char *str;
size_t s1_len;
size_t s2_len;
s1_len = strlen(s1);
s2_len = strlen(s2);
str = (char *)malloc(sizeof(char) * (s1_len + s2_len + 1));
if (!str)
return (NULL);
memcpy(str, s1, s1_len);
memcpy(str + s1_len, s2, s2_len);
str[s1_len + s2_len] = '\0';
return (str);
}
int main(int argc, char **argv, char **envp)
{
char *pwd;
char *cmd;
pwd = getcwd(NULL, 0);
cmd = strjoin(pwd, "/env_test.out");
execve(cmd, NULL, NULL);
free(pwd);
return (0);
}
태스트 결과 다음과 같이 출력됩니다.
+===========================+
| environ |
+===========================+
[0x16f843e50] TEST=test
+===========================+
| envp |
+===========================+
[0x16f843e50] TEST=test
+===========================+
| getenv |
+===========================+
[0x16f843e55] test
[0x16f843e50] TEST=test
즉, 환경변수 문자열 배열은 execve()
와 마찬가지로 main()
이 실행되기 전에 environ
변수에 저장이 되고,
해당 값이 envp
매개변수로 전달된 것이라고 예상할 수 있습니다.
env 명령어로 쉘 변수를 환경변수로 지정하기
env 명령어를 사용하면 특정 쉘 변수를 환경변수로 지정해서 다른 명령어를 실행할 수 있습니다.
이 때 환경변수로 지정된 쉘 변수는 다른 쉘 세션이나 자식 프로세스에 영향을 끼치지 않습니다.
이를 알아보기 위해 첫 번째 테스트(env_test)를 실행할 때 다음과 같은 명령어를 사용해서 테스트를 했습니다.
unset TEST; env TEST=test ./env_test.out
위 명령어를 실행하면 다음과 같은 결과가 나옵니다.
+===========================+
| environ |
+===========================+
[0x16d2ffe55] TEST=test
+===========================+
| envp |
+===========================+
[0x16d2ffe55] TEST=test
+===========================+
| getenv |
+===========================+
[0x16d2ffe5a] test
[0x16d2ffe55] TEST=test
하지만 다음 명령어를 사용해서 확인해보면 환경변수나 쉘 변수로 등록되어 있지 않다는 것을 알 수 있습니다.
export | grep TEST # 환경변수로 등록되지 않고
echo $TEST # 해당 세션의 쉘 변수로도 등록되지 않는다.
'Computer Science > Linux' 카테고리의 다른 글
[Linux] Flex와 Bison (0) | 2024.02.28 |
---|---|
[Linux] SSH가 무엇인가요? (0) | 2023.11.28 |
[Linux] 접근 통제 방법 DAC와 MAC (0) | 2023.11.28 |
[Linux] Debian과 Rocky Linux (0) | 2023.11.28 |