저장된 사용자 ID - Saved UID

Saved UID를 이해하기 위해서는 실제 사용자 ID(Real UID)와 유효 사용자 ID(Effective UID)에 대한 이해가 깔려있어야합니다. 아직 개념이 안잡혀있다면 아래의 포스팅을 먼저 보고 오시면 좋겠습니다. 

https://reakwon.tistory.com/228

 

[리눅스] 코드로 알아보는 uid(real uid, effective uid, saved uid) 관계

아래 포스팅보다 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요. https://reakwon.tistory.com/233 리눅스 프로그래밍 note 배포 티스토리에 리눅

reakwon.tistory.com

 

Saved UID가 없다고 생각하고 Real UID와 Effective UID만 있다고 가정 해보세요. 그리고 setresuid 역시 suid를 설정하는 함수이니 없다고 가정해보세요. 권한이 확장된 프로그램에서의 euid를 권한이 확장된 euid라고 부르겠습니다. 다른 사용자의 권한을 얻은 상태에서 다시 권한을 축소 시켜야할때, 그러니까 실사용자 ruid로 돌려야할 상황이 생길 때 단순히 ruid로만 돌리면 나중에 다시 유효 사용자 권한이 필요할 때 돌아올 방법이 없습니다. 그러니까 euid, ruid를 왔다리 갔다리 유도리있게 스위칭할수가 없다는 뜻입니다.

  • setuid를 통해서 권한이 확장된 euid로 다시 바꿀 수 있을까요? 현재 ruid=euid입니다. suid는 없다고 가정했으니, ruid로만 변경될 수 있는데, 이는 권한이 확장된 euid가 아니잖아요. setuid로는 바꿀 수 없습니다.
  • seteuid 역시 마찬가지인데요. ruid 혹은 suid로 돌아갈 수 있는데 지금은 suid가 없다고 가정했으니 역시 setuid의 결과와 같습니다. 바꿀 수 없죠.  
  • setreuid도 마찬가지입니다. ruid가 현재 euid이기 때문이죠. setreuid는 현재의 ruid 혹은 euid로만 변경되는데, 지금 상황은 ruid가 euid와 같죠.

즉, 저장된 사용자 ID가 없이 ruid와 euid만 존재하면 현재 ruid = euid가 생길 때 다시 확장된 권한의 euid로 돌아갈 방법이 없다는 뜻입니다. 그래서 따로 그러한 확장된 권한의 euid를 저장해 놓아야하는데, 이를 위한 uid가 바로 saved-user id입니다.

그래서 이전 지난 포스팅에 suid가 초기의 euid와 같다는 점을 주시하라고 한겁니다. 저의 큰 그림 아시겠어요? 이제 saved라는 의미가 왜 붙었는지 아시겠죠?

예를 들어, root 권한의 프로그램에서 잠시 root 권한을 뺄 때를 생각해봅시다. 위와 같은 상황은 발생하겠죠. 아무리 root가 프로그램 사용권한을 자신의 것으로 쓰게 끔 허락했어도 특정 파일에 대한 접근 권한을 막을 상황이 생길겁니다. 이런 경우 seteuid를 사용자의 ruid로 돌려줍니다. 현재 ruid = 실 사용자 id, 현재 euid = 실 사용자 id 그러다가 그 후 다시 root 권한으로 작업해야할 때는 seteuid를 다시 root로 돌려야하겠죠. 그런데 현재 상황에서 ruid는 실 사용자의 id, 그리고 euid도 실 사용자 id인데, 어떻게 돌릴 수 있죠? suid는 아까 봤듯이 euid와 같았죠. 네, suid 덕분에 다시 유효사용자 id를 root로 돌릴 수 있게 됩니다.  

아래의 코드는 root.txt라는 root만든 텍스트 파일을 euid를 변경해가며 읽는 소스 코드입니다. 실행하면서 어떤 현상이 발생하는지 관찰해보면 suid가 왜 쓰이는지 알 수 있을 겁니다.

//savedUID.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

void readfile(){
        int n;
        char buf[32]={0,};
        int fd = open("root.txt", O_RDWR);
        if(fd < 0){
                printf("open error(%s)\n", strerror(errno));
                return;
        }


        n = read(fd, buf, 32);
        if(n < 0){
                printf("read error(%s)\n", strerror(errno));
                close(fd);
                return;
        }
        printf("%s", buf);
        close(fd);


}
void pr_resuid(){
        int ruid, euid, suid;
        if(getresuid(&ruid, &euid, &suid) < 0){
                printf("setresuid error\n");
                exit(0);
        }
        printf("ruid:%d, euid:%d, suid:%d\n",
                ruid, euid, suid);
}
int main(){

        printf("초기 uid\n");
        pr_resuid();

        printf("file 읽기 시도 > ");
        readfile();
        printf("\n");

        printf("euid를 %d로 전환\n", getuid());
        if(seteuid(getuid()) < 0){
                printf("seteuid error\n");
                return 1;
        }
        pr_resuid();

        printf("file 읽기 시도 > ");
        readfile();
        printf("\n");

        printf("euid를 root로 전환\n");
        seteuid(0);

        printf("file 읽기 시도 > ");
        readfile();
        pr_resuid();
}

우선 root의 유효사용자 id를 root로 돌려야하기 때문에 권한을 줘야겠군요. gcc 경고에 대한 문구는 가볍게 쌩까도록 합시다. 아참, 그리고 root권한의 파일을 하나 만들어야겠네요. root.txt를 읽어야하잖아요.

root# gcc savedUID.c 
savedUID.c: In function ‘pr_resuid’:
savedUID.c:32:12: warning: implicit declaration of function ‘getresuid’; did you mean ‘setreuid’? [-Wimplicit-function-declaration]
   32 |         if(getresuid(&ruid, &euid, &suid) < 0){
      |            ^~~~~~~~~
      |            setreuid
root# chmod u+s a.out
root# echo "this is a file made by root" > root.txt

 

계정을 ubuntu로 전환하고 파일을 실행하면 어떤 결과가 나올까요?

root# su ubuntu
ubuntu$ ./a.out 
초기 uid
ruid:1000, euid:0, suid:0
file 읽기 시도 > this is a file made by root

euid를 1000로 전환
ruid:1000, euid:1000, suid:0
file 읽기 시도 > open error(Permission denied)

euid를 root로 전환
file 읽기 시도 > this is a file made by root
ruid:1000, euid:0, suid:0

 

euid를 잠시 ubuntu로 전환하고 파일을 읽을 땐 파일을 읽을 수 없습니다. 여기서 알 수 있는 것은 permission denied로 권한이 축소되었음을 알 수 있습니다. 그 후에는 euid를 root로 전환하여 파일을 읽을 수 있게 되었습니다. euid를 0으로 되돌릴 수 있는 이유는 suid가 0이기 때문이고 만약 suid 마저도 다른 uid로 변경되었다면 seteuid는 에러를 발생시킵니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨서 문서로 배포하도록 결정했습니다. 

차례대로 설명이 되어 있으므로, 찾아보셔도 되고, 입문자분들께서는 부족하지만 처음부터 보셔도 됩니다.  

현재 초기단계의 배포라서 오타, 오류 등이 있을텐데, 차차 수정하면서 버전을 올려서 다시 배포하도록 하겠습니다. 제가 원래는 좋아요와 댓글 구걸은 하지 않는데, 얼마나 문서를 다운 받으셨는지, 오류 내용이 무엇인지 확인하기 위해서  좋아요! 댓글!  남겨주시면 감사하겠습니다.

아래의 구글 드라이브를 통해서 다운받으실 수 있습니다. 

linux_note_v1.0

 

목차는 아래와 같습니다.

리눅스

1 리눅스 역사

          1.1 System V

1.2 BSD

1.3 LINUX

1.4 POSIX

1.5 GNU

2 리눅스 배포판

          2.1 Debian

2.2 Ubuntu

2.3 CentOS

2.4 Kali

3 리눅스 특징

4

          4.1

          4.2

          4.3

5 리눅스 운영체제의 구조

5.1 어플리케이션 실행 흐름

 

 

시스템 다루기

1 한계

          1.1 컴파일 시점 한계

1.2 실행 시점 한계 - sysconf

1.3 실행 시점 한계 – pathconf, fpathconf

1.4 예제한계 알아보기

2

          2.1

2.2

                    2.2.1 export

                   2.2.2

          2.3

                    2.3.1 setenv, unsetenv

                   2.3.2 putenv

                   2.3.3 getenv

2.3.4 하기

 

파일

1.1 파일입출력

          1.1 open

1.2 close

1.3 read

1.4 write

1.5 lseek

1.6 예제-파일 기, 읽기

1.7 리다이렉션

         1.7.1 입력 Redirection

         1.7.2 출력 Redirection

         1.7.3 에러 Redirection

         1.7.4 파일로 Redirection

                    1.7.5 Append

                    1.7.6 Redirection

1.8 파이프

2 파일제어

2.1 파일 권한

2.1.1 특수 권한 SUID, SGID

2.1.2 특수 권한 Stiky

2.1.3 umask

2.2 링크

2.2.1 하드 링크

2.2.2 심볼릭 링크

2.2.3 링크 커맨드 구현

2.3 파일 상태

2.3.1 파일 상태 확인

2.4 파일 컨트롤 - fcntl

2.4.1 파일 속성 조회, 변경

2.4.2 비차단(NON-Block) 모드

2.4.3 예제 – CLOEXEC 이해

2.5 파일 서술자 복제

2.5.1 dup

2.5.2 dup2

2.5.3 파일 서술자 복제1

2.5.4 예제파일 서술자 복제2

2.6 디렉토리 다루기

2.6.1 opendir

2.6.2 readdir

2.6.3 closedir

2.6.4 디렉토리 파일 리스트 출력

2.6.5 예제하위 디렉토리 읽기

3 러리

3.1 일,

3.2  

          3.3 C

                    3.3.5 기,

                    3.3.8 기,

                    3.3.9 근(Random Access)

                              3.3.9.4 오기

          3.4

                    3.4.1

                    3.4.2

                              3.4.2.4 오기

                              3.4.2.5 – setbuf

                              3.4.2.6 - setvbuf

 

유저

1 사용자관리

          1.1 /etc/passwd

1.2 /etc/shadow

1.3 /etc/group

1.4 사용자 정보 조회 – pwd API

1.4.1 getpwuid, getpwnam

1.4.2 setpwent, getpwent, endpwent

1.4.3 예제사용자 정보 조회

1.5 비밀 관련 API

1.5.1 getspnam, setspent, getspent, endspent

2 사용자 UID(RUID, EUID, SUID) 그룹 ID(RGID, EGID, SGID)

          2.1 uid 조회 함수들

2.1.1 getuid, geteuid, getresuid

2.1.2 예제사용자 UID 조회

2.2 uid 설정 함수들

2.2.1 setuid, getgid

2.2.2 예제 – setuid 특징

2.2.3 setreuid, setregid

2.2.4 setresuid, setresgid

2.2.5 예제 – uid 설정 함수들 관찰

2.3 Saved User ID 쓰임새

2.3.1 예제 – Saved User ID 활용

2.4 실습간단 login 프로그램 구현

 

프로세스

1 프로세스 상태

1.1 리눅스 프로세스 상태

1.2 실습프로세스 상태 확인

2 프로세스 실행

3 프로세스 종료exit 함수

          3.1 exit 함수

3.1.1 파일 디스크립터 방출

3.2.2 atexit 호출

3.2.2 _exit 호출

4 비국소(nonlocal) 분기

          4.1 setjmp, longjmp

          4.2 예제 – setjmp, longjmp 활용

5 프로세스 생성

          5.1 자식 프로세스 생성 – fork

         5.1.1 고아 프로세스

5.1.2 wait

5.1.3 좀비 프로세스

5.1.4 SIGCHLD 이용한 wait

5.1.5 실습다수의 자식 프로세스 wait

5.1.6 자식 프로세스 종료 상태 알아오기

5.1.7 waitpid

             5.1.7.1 예제 – waitpid 사용

             5.1.7.2 예제비차단 waitpid

6 exec 함수들

          6.1 exec 기본 사용방법

6.2 exec 특징

           6.2.1 CLOEXEC

6.3 예제간단 shell

7 프로세스 그룹

          7.1 프로세스 그룹 Id 가져오는 API

          7.1.1 getpgrp

7.1.2 getpgid

7.2 프로세스 그룹 사례들

7.2.1 사례 1 – 스크립트

7.2.2 사례 2 – 자식 프로세스

7.2.3 사례 3 – 파이프

7.3 프로세스 그룹 ID 설정 API

7.3.1 예제프로세스 그룹 ID 설정 현상

8 세션

         

 

데몬 프로세스

1 데몬 프로세스 특징

2 데몬 프로세스 구현

3 로그

3.1 로그 API

         3.1.1 openlog

         3.2.2 syslog

        3.3.3 예제로그 별도 저장

4 시스템 데몬

    4.1 시스템 데몬 사용

4.1.1 시스템 데몬 실행 파일 생성

4.1.2 시스템 데몬 Unit 작성

4.1.2.1 시스템 데몬 unit 섹션

4.1.2.2 시스템 데몬 service 섹션

4.1.2.3 시스템 데몬 install 섹션

4.1.3 systemctl이용한 데몬 제어

4.1.3.1 데몬 실행

4.1.3.2 데몬 상태 확인

4.1.3.3 데몬 중지

4.1.3.4 데몬 재시작

4.1.3.5 부팅 데몬 실행

 

신호

1 신호보내기

1.1 kill 명령어

1.2 kill, raise API

2 신호 종류

3 signal 함수 시그널 특징

3.1 SIGINT

3.2 SIGTSTP

3.3 SIGSTOP

3.4 SIGCHLD

3.5 SIGSHUP

3.6 SW interrupt 특징

3.7 느린 시스템 콜에서 신호

4 시그널 제어

4.1 시그널 집합

4.1.1 sigemptyset, sigfillset

4.1.2 sigaddset, sigdelset

4.1.3 sigismember

4.2 시그널 차단

4.2.1 sigprocmask

4.2.2 – sigprocmask1

4.2.3 – sigprocmask2 시그널 차단

4.2.4 sigpending

4.2.5 sigsuspend

4.3 sigaction

4.3.1 – sigaction으로 signal 구현

4.3.2 예제 – sigaction으로 SIGCHLD 제어

 

 

스레드

1 리눅스 스레드

1.1 pthread_create

1.2 pthread_join

1.3 pthread_detach

2 동기화

2.1 임계영역

2.2 뮤텍스(Mutex)

2.1.1 pthread_mutex_init

2.1.2 pthread_mutex_lock, pthread_mutex_unlock

2.1.3 pthread_mutex_destroy

2.1.5 예제 – system v 공유메모리 사용

2.3 세마포어(Semaphore)

2.3.1 P연산

2.3.2 V 연산

2.3.3 세마포어 API

2.3.3.1 semget

2.3.3.2 semctl

2.3.3.3 semop

2.3.3.4 예제세마포어 적용 쓰레드

2.4 조건변수

2.4.1 조건변수 API

2.4.1.1 pthread_cond_init

2.4.1.2 pthread_cond_wait

2.4.1.3 pthread_cond_signal

2.4.1.4 예제조건 변수 API 사용

2.4.1.5 생산자소비자 문제

3 교착상태(DeadLock)

3.1 교착상태 발생 조건

          3.1.1 상호 배제

          3.2.2 점유와 대기

          3.2.3 비선점 조건

          3.1.2 환형 대기

3.2 예제교착상태 발생 프로그램

3.3 교착상태 해결

          3.3.1 교착상태 예방

          3.3.2 교착상태 회피

          3.3.3 교착상태 회복

           

 

IPC

1 파이프(Pipe)

1.1 예제파이프 사용1

1.2 예제파이프 사용2

2 공유메모리

2.1 System V 공유 메모리

2.1.1 shmget

2.1.2 shmat

2.1.3 shmdt

2.1.4 shmctl

2.1.5 예제 – system v 공유메모리 사용

2.2 POSIX 공유 메모리

2.2.1 shm_open

2.2.2 ftruncate

2.2.3 shm_unlink

2.2.4 예제 – POSIX 공유메모리 사용

3 메시지

3.1 System V 메시지

3.1.1 msgget

3.1.2 msgsnd

3.1.3 msgrcv

3.1.4 msgctl

3.1.5 예제 – System V 메시지큐 사용

3.2 POSIX 메시지

3.2.1 mq_open

3.2.2 mq_send, mq_timedsend

3.2.3 mq_receive, mq_timedreceive

3.2.4 mq_close

3.2.5 mq_unlink

3.2.6예제 – POSIX 메시지 사용

 

네트워크

1 OSI 7 계층

2 TCP/IP

2.1 전송 프로토콜 – TCP, UDP

2.2 네트워크 프로토콜 - IP

3  소켓

3.1 네크워크 관련 API

3.1.1 socket

3.1.2 bind

3.1.3 listen

3.1.4 accept

3.1.5 connect

3.1.6 send

3.1.7 recv

3.1.8 shutdown

3.1.9 바이트 순서 변환

3.1.10 인터넷 주소 변환

3.2 서버클라이언트 통신 구현

3.2.1 단일 클라이언트 처리 서버, 클라이언트 코드

3.2.2 멀티 프로세스 서버

3.2.3 멀티 스레드 서버

3.2.4 다중입출력 – select 활용

3.2.5 다중입출력 – poll 활용

3.2.6 다중입출력 epoll 활용

                 3.2.6.1 Level Trigger

                 3.2.6.2 Edge Trigger

                 3.2.6.3 epoll_create

                 3.2.6.4 epoll_ctl

                 3.2.6.5 epoll_wait

                 3.2.6.6 Level Trigger 서버

                 3.2.6.7 Edge Trigger 서버

3.3 Datagram 소켓 서버

 

반응형
블로그 이미지

REAKWON

와나진짜

,

리눅스 시스템의 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

환경 변수

컴퓨터 시스템에서 사용되는 동적인 값을 저장하는 변수, 프로그램들이 시스템 설정과 상호작용하는 데 사용됩니다. 환경변수는 텍스트 형식으로 저장되며, 특정 이름과 그에 해당하는 값으로 구성됩니다.

이름 =  

시스템 전체에서 공유되거나 특정 프로세스 내에서만 유효한 변수들이 있을 수 있습니다. 환경변수는 주로 다음과 같은 용도로 사용됩니다.

  1. 시스템 설정 및 구성 정보 저장 시스템의 설정 정보를 저장하여 다른 프로그램들이 해당 정보를 활용할 수 있게 합니다. 예를 들어, 시스템의 언어, 시간대, 기본 디렉토리 등을 환경변수로 설정하여 시스템 전반에 적용할 수 있습니다. 
  2. 프로그램 실행 시 동작 옵션 설정 프로그램이 동작할 때 필요한 옵션을 환경변수로 설정하여 해당 프로그램이 이를 인식하고 적용하게 할 수 있습니다. 이를 통해 특정 프로그램의 동작을 조정하거나 사용자 맞춤 설정을 지원할 수 있습니다.
  3. 실행 환경 설정 프로그램이 실행되는 환경을 설정하는 데 사용됩니다. 예를 들어, 실행 파일들을 찾는 경로를 설정하거나 라이브러리의 위치를 지정하는데 환경변수를 활용할 수 있습니다.

 우리는 현재 쉘에서 환경 변수를 설정한적은 없죠. 그런데 로그인 할 때 기본적으로 설정되어 있는 환경 변수들이 있습니다(여기서는 bash 쉘만 보겠습니다.) printenv 명령(혹은 env)을 통해서 현재 쉘에 어떤 환경변수가 설정되어있는지 확인해볼까요?

# printenv
SHELL=/bin/bash
PWD=/root
LOGNAME=ubuntu
//...
HOME=/root
LANG=en_US.UTF-8
//...
USER=ubuntu
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
MAIL=/var/mail/root
SSH_TTY=/dev/pts/0

제가 있는 현재 bash 쉘의 환경변수는 이렇게 설정되어있습니다. 환경 변수도 변수이기 때문에 그 값을 변경하거나 없애거나 할 수 있습니다. 그런데 보통 기본적으로 설정되어있는 환경 변수는 건드리지 않는 편이 좋습니다. 예를 들어 PATH를 건드리게 되면 명령어 실행을 못할 수도 있습니다. 여기서는 알아두면 좋을 환경 변수들을 설명하고 환경 변수를 설정하는 방법과 프로그램에서 어떻게 환경 변수를 읽어오고 설정하는지 확인해보도록 하겠습니다.

1. 일반적인 환경 변수들

아래의 환경 변수들은 기본적으로 현재 쉘에 설정되어있을 수 있는 환경 변수들입니다.

환경변수  설명
USER 현재 로그인한 사용자입니다. UID(숫자)가 아닌 계정명으로 나타납니다.
HOME 현재 사용자의 홈 디렉토리입니다. 로그인하면 이 디렉토리로 자동으로 위치가 됩니다.
EDITER 사용할 기본 파일 편집기입니다. 터미널에 edit를 입력할 때 사용할 편집기입니다.
LANG 현재 로컬 언어 설정을 보여줍니다.
TERM 현재 터미널 에뮬레이션입니다.
MAIL 현재 사용자의 메일이 저장되는 위치입니다.
PATH  명령어를 실행할 때 검색할 디렉토리들입니다. 여러 디렉토리가 있을 수 있기 때문에 :(콜론)으로 구분이 됩니다.
여러분이 명령어를 아무데서나 칠 수 있는 이유는 바로 이 PATH변수 때문입니다. 이 변수가 없다면 ls 명령을 칠 때 전체 경로를 넣어서 /bin/ls를 쳐야하지만 PATH/bin 디렉토리가 있기 때문에 ls만 쳐서 명령을 칠 수 있는 것입니다.
SHELL 현재 사용자의 로그인 쉘입니다.
PWD 현재 사용자가 있는 디렉토리입니다

 

2.  환경 변수 설정

우선 일반적으로 쉘에서 단순 변수는 아래와 같이 설정할 수 있습니다.

변수명= 

그 변수를 참조할 때는 변수명 앞에 $를 사용하여 확인할 수 있습니다. 며칠전에 달라로 500만원을 환전했는데 개떡락을 하더군요 --;;

# VAL="hello, world"
# echo $VAL
hello, world

그런데 이건 이 쉘에서만 유효한 변수일 뿐이지 환경 변수는 아닙니다. printenvenv 명령을 통해서 현재 쉘에 설정된 환경 변수확인해보세요. 환경변수는 아니라는 것을 알 수 있습니다.

# printenv | grep VAL
아무 결과도 나오지 않음

대신에 현재 쉘 환경의 환경 변수를 포함한 모든 변수, 함수 등을 출력하는 set 명령을 통해 확인하면 변수가 설정되어있는 것은 알 수 있습니다.

# set | grep VAL
VAL='hello world'

그렇다면 환경 변수를 설정하려면 어떻게 해야 할까요?

 export 명령

export 명령, 일명 내보내기 명령을 통해서 현재 쉘의 환경 변수로 변수를 등록할 수 있습니다. 이때 변수명 앞 $는 생략해야합니다.

export 변수명

# VAL="hello world"
# export VAL
# printenv | grep VAL
VAL=hello world

혹은 한번에 값을 지정해서 내보낼 수도 있습니다.

export 변수명=

# export APPLE="Mac Book"
# printenv | grep APPLE
APPLE=Mac Book

이제 환경 변수로 등록되었다는 것을 알 수 있습니다.  

환경 변수나 변수를 해제하려면 unset 명령어를 통해서 해제할 수 있습니다.

unset 변수명

# printenv |grep VAL
VAL=hello world
# unset VAL
# printenv | grep VAL
# printenv | grep APPLE
APPLE=Mac Book
# unset APPLE
# printenv | grep APPLE

이런 환경 변수는 현재 수행되는 쉘이 유지되는 동안만 유효(사실 더 정확하게는 환경변수를 설정한 프로세스가 유지되는 동안 유효)합니다. 무슨 말이냐구요?

3. 환경변수의 특성

계속 주구장창 현재 쉘, 현재 쉘이라는 말을 썼는데요. 여러분들이 놓치실까봐 초록색으로 표시했습니다. 그림으로 보면 더욱 이해가 빠르실텐데요.  

처음 로그인 쉘인 (1) /bin/bash에서 export를 이용해서 HELLO를 환경 변수로 등록하고 sh로 새로운 쉘을 시작합니다. 이때 (2)/bin/sh로 실행되는 환경이 바뀌게 됩니다. 여기서도 export를 이용해서 WORLD를 환경 변수로 등록하고 다시 새로운 (3)bash 쉘을 실행시켜서 실행 환경을 바꿉니다. 여기서 BYE라는 환경 변수를 등록합니다.

그러면 맨 오른쪽 (3)bash 쉘을 사용하고 있을때 HELLO, WORLD, BYE라는 환경변수는 다 살아있을까요? , 살아있습니다. 맨처음 (1)/bin/bash, 그 다음 (2)/bin/sh도 실행중이니까요. 이제 exit으로 (3)bash를 끝내게 되어도 세 환경 변수 HELLO, WORLD, BYE는 살아있을까요? BYE(3)bashexit을 종료했기 때문에 더 이상 존재하지 않습니다. 그래서 HELLO, WORLD만 남아있게 됩니다. (2)/bin/sh를 끝냈을때, 이때 (1)/bin/bash만 수행되는 상태겠죠. 그러면 HELLO만 환경 변수로 유효하게 되는 겁니다.

아래는 테스트 결과입니다. 헷갈리지 않도록 앞에 프롬프트를 추가했습니다. 참고로 전 쉘에서 설정한 변수를 다음 쉘에서 이용하려면 export를 이용해 환경변수로 등록이 되어야 가능합니다. 그러니까 (1)/bin/bash에서 설정한 HELLO(2)/bin/shecho로 출력하려면 export되어있어야 된다는 겁니다.

(1)/bin/bash# export HELLO="hello!"
(1)/bin/bash# sh
(2)/bin/sh# export WORLD="world!"   
(2)/bin/sh# bash
(3)/bin/bash# export BYE="GOOD BYE~"
(3)/bin/bash# echo $HELLO $WORLD $BYE
hello! world! GOOD BYE~
(3)/bin/bash# exit
exit
(2)/bin/sh# echo $HELLO $WORLD $BYE
hello! world!
(2)/bin/sh# exit
(1)/bin/bash# echo $HELLO $WORLD $BYE
hello!

이런 특성을 모른다면 이런 실수 많이들 하실겁니다.  아래는 간단한 bash 쉘 스크립트를 작성한겁니다.

#!/bin/bash

#현재 프로세스 ID 출력 
echo $$

export HELLO="hello "
export WORLD="world!"
export BYE="good bye!"

echo $HELLO $WORLD $BYE

스크립트를 몰라도 맨위의 첫줄은 bash 스크립트를 알려주는 한줄이니까 그냥 그렇게 쓰는구나 묻지도 따지지지도 말고 흡수하시면 됩니다.

#!/bin/bash

그 다음 줄을 좀 눈여겨 봐야하는데, $$는 현재 프로세스의 ID를 의미합니다. 그래서 echo를 통해서 출력하는 것이구요. 그냥 간단히 현재 실행되는 프로그램의 ID라고 생각하세요.

#현재 프로세스 ID 출력 
echo $$

그리고 우리가 배운 export와 명령을 출력하는 echo가 다입니다.  위 스크립트의 의도는 $HELLO, $WORLD, $BYE를 환경 변수로 설정해서 편하게 쓰려는 의도입니다.  실행권한을 주고 실행하면 쉘 스크립트는 잘 실행되지만 스크립트가 끝난 이후 환경 변수를 불러오고자 한다면 이렇게 빈 공백만 출력이 됩니다.

# chmod 777 setmyenv.sh
# ./setmyenv.sh 
6968
hello world! good bye!
# echo $$
3196
# echo $HELLO

# echo $WORLD

# echo $BYE

그런데 제가 명령어를 수행하는 프로그램의 ID3196이고, , ./setmyenv.sh라는 쉘 스크립트가 실행되는 프로그램 ID6968로 다르네요. 결국 이런 상황입니다.

방금 전 언급한 그 상황이 발생했습니다. 그러면 새로운 실행을 낳지 않고, 현재 있는 쉘에서 그대로 스크립트를 수행하면 될 것 같습니다. 그런 목적을 달성할 명령이 바로 source 명령입니다.

 위의 스크립트를 source를 이용해서 실행해봅시다.

# source setmyenv.sh 
3196
hello world! good bye!
# echo $HELLO
hello
# echo $WORLD
world!
r# echo $BYE
good bye!
# echo $$
3196

보세요. 쉘 스크립트가 그대로 3196에서 실행이 되었죠? 그리고 환경 변수가 설정되어서 현재 쉘에서 사용할 수도 있습니다. 우리가 원하는 목적을 달성할 수 있었습니다. source는 쉘 스크립트를 현재 쉘에서 실행하게 해주는 명령어입니다.

4. 환경 변수 함수

이젠 리눅스에서 프로그램이 어떻게 환경 변수를 다룰 수 있는 지 볼까요? 환경 변수를 설정하는 함수는 setenv, putenv가 있으며 환경 변수를 해제하려면 unsetenv함수를 사용할 수 있습니다. 환경 변수의 어떤 값을 읽어올 경우에는 getenv 함수를 이용하면 됩니다.

4.1 setenv, unsetenv

#include <stdlib.h>
int setenv(const char *name, const char *value, int overwrite);
int unsetenv(const char *name);

setenv 함수는 name에 해당하는 value를 갖는 환경 변수를 설정해줍니다. overwrite1이라면 기존에 환경 변수값이 있을 때 현재 값으로 덮어쓰고, 0이라면 덮어쓰지 않습니다.

unsetenv 함수는 name에 해당하는 환경 변수를 해제합니다.

성공시 0, 실패시 -1을 반환합니다.

4.2 putenv

#include <stdlib.h>
int putenv(char *string);

putenv이름=형식으로 넘겨줌으로써 환경 변수를 설정할 수 있습니다. 이때 무조건 덮어쓰기가 되니 주의하세요.

성공시 0, 실패시 0이 아닌 값을 반환합니다.

4.3 getenv

#include <stdlib.h>
char *getenv(const char *name);

getenv함수는 name에 해당하는 환경변수를 읽어옵니다. 그래서 그 값을 반환하죠. 만약에 해당하는 환경 변수가 없다면 NULL을 반환합니다.

4.4 환경 변수 함수 이용의 예

네 함수(setenv, putenv, getenv unsetenv)를 다 사용하는 예제를 한번 볼까요.

//setenv.c
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]){

        char *name = NULL;
        char *value = NULL;
        char name_val_pair[128]={0,};
        if(argc != 3){
                printf("Usage %s NAME VALUE\n", argv[0]);
                return 1;
        }

        name = argv[1];
        value = argv[2];

        //overwrite 허용
        //setenv(name, value, 1);
        //overwrite 불가
        //setenv(name, value, 0);
        sprintf(name_val_pair, "%s=%s",name,value);
        putenv(name_val_pair);

        printf("%s=%s\n",name,getenv(name));

        unsetenv(name);
}

putenv를 이용해서 환경 변수를 설정해보았습니다. 만약 여러분이  setenv함수를 이용해서 덮어쓸지 말지를 결정할 수도 있습니다. 여기서는 putenv를 통해서 덮어쓰기가 가능한 환경 변수 설정을 하는 것으로 합시다. 다음은 환경 변수 USER를 바꾸는 실행화면입니다. 처음에는 ubuntu로 설정되어있고, 프로그램을 실행하니까 kali로 바뀌었습니다.

그런데 프로그램이 끝난 이후 USER를 확인해보니 ubuntu로 돌아왔네요. 왜 그렇죠!? 용(3 환경변수의 특성)을 잊지 않으셨죠?

# echo $USER
ubuntu
# ./a.out USER kali
USER=kali
# echo $USER
ubuntu

만약 USER가 바껴지는 것을 확인하고 싶다면 system함수로 다른 shell을 실행시켜셔 확인해보세요. system 함수는 다른 명령어를 실행하는 함수로 이해하시면 됩니다.

//setenv.c
//… 생략 … //

int main(int argc, char *argv[]){
	//… 생략 … //
	putenv(name_val_pair);

        printf("%s=%s\n",name,getenv(name));
        system("/bin/sh");

        unsetenv(name);
}
# ./a.out USER kali
USER=kali
# printenv | grep USER    sh쉘로 전환
USER=kali

 

로그인시 환경변수 자동 셋팅(.bashrc), 전체 사용자에 대한 환경 변수 설정(/etc/profile)에 대한 설정 방법은 아주 훌륭하신 블로거님들이 정리를 잘 해놓았으니, 여기서는 설명하지 않겠습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,