디렉토리 내용 읽어들이기
#.개요
디렉터리를 읽으면 디렉터리에 담긴 파일들의 정보를 얻을 수 있다. 파일 한개당 하나의 구조체에 대응되어, 디렉터리를 읽으면, 구조체의 배열을 얻을 수 있다. 즉, 디렉터리는 바이트 배열임과 동시에 구조체의 배열인 것이다. 이 구조체를 디렉터리 엔트리라고 한다.
#.디렉토리 열기
설명 : name 으로 지정한 디렉터리를 읽기 위해 open 하고 DIR 타입에 대한 포인터를 반환한다.
DIR 타입은 디렉터리를 읽어 들이기 위한 스트림을 관리하는 구조체로 파일을 읽을때 사용한 FILE 타입에 대응하는 개념이다.
헤더 : dirent.h
형태 : DIR *opendir(const char *name);
인수 : char *name (열기 대상 디렉토리)
반환 : DIR 열기에 성공하면 디렉토리 정보 구조체인 DIR 포인터를 반환하고 실패하면 NULL을 반환.
#.디렉토리 읽기
설명 : 디렉터리 스트림 dir로 부터 엔트리를 하나씩 읽어 들여 struct dirent 타입으로 반환한다. 더 읽을 엔트리가 없거나 읽어 들이는데 실패하면 NULL을 반환한다.
struct dirent *readdir(DIR *dir);
반환 값: 실패 시 NULL
#.디렉토리 닫기
int closedir(DIR *dir);
반환 값: 실패 시 -1, 성공 시 0
예제
결과
디렉터리 만들기
#.mkdir
int mkdir( const char *dirname );
dirname : 생성할 디렉토리 경로와 이름
반환값 : 정상 일 때 0, 에러 시 -1
Error
만들려는 폴더가 이미 존재하면 EEXIST,
상위디렉터라가 없을경우 ENOENT
path로 지정한 상위디렉터라가 디렉터리가 아닐경우 ENOTDIR
상위 디렉터리에 대한 변경 권한이 없다 EPERM
#.umask
#include <sys/types.h>
#include <sys/stat.h>
mode_t umask(mode_t mask);
mkdir 이나 open 을 사용할 때 만들어질 파일의 권한을 지정할 수 있지만, 두 경우 모두 지정한 값이 그대로 사용되는 것은 아니다. umask를 사용해서 변경된 값이 사용된다. umask는 프로세스의 속성중 하나로, 가장 일반적인 값은 8진수 022다. open 이나 mkdir 에서 실제로 사용되는 권한은 c언어로 표현하면, 'mode & ~ umask로 계산된다. 즉, 인자로 지정한 mode 로부터 umask에 포함되는 비트를 빼는 것이다. 예를 들어 인자로 지정한 mode가 0777이고 mask가 022라면 실제 권한은 0755가 된다. 참고
#.디렉터러 생성 예제
예제
결과
디렉터리 삭제하기
#.rmdir
rmdir 은 path로 지정한 디렉터리를 삭제한다.
디렉터리는 반드시 비어 있어야 한다. 성공하면 0을 반환 하고 실패하면 -1을 반환하고 errno를 설정한다.
디렉토리 내에 파일이 존재하거나 사용중이면 삭제할 수 없다.
삭제 전에 디렉토리 존재유무 확인 후 삭제해야 한다.
int rmdir( const char *dirname );
//dirname : 삭제할 디렉토리 경로
//반환값 : 정상 일 때 0, 에러 시 -1
예제
결과
하드링크
리눅스에서는 하나의 파일에 두개 이상의 이름을 지정할 수 있다. 이를 위해 링크라는 개념이 존재하는데 이를 하드링크라고 한다. 링크란, 파일에 새로운 이름을 붙이는 것이다. 예를 들면 a 라는 파일을 만들었다고 하자.
* a(이름) ====> this is file(실체)
여기서 파일 a의 새로운 이름 b를 붙여 보자. 이때 사용하는 명령어가 ln 명령어이다.
* ln a b
그럼 파일의 이름과 실체의 관계는 아래와 같다.
a(이름) , b(이름) =====> this is file (실체)
이 작업을 파일 a를 가리키는 하드링크 b를 만든다고 말한다. 이후, a의 내용을 변경하면 b의 내용도 같이 변경된다. 왜냐면 a와 b는 같은 것을 가리키고 있기 때문이다.
파일에 부여된 이름의 개수는 ls -l 를 사용해서 확인할 수 있다. 출력물의 왼쪽에서 두번째란이 이름의 개수를 의미한다. 이를 링크 카운터라고 한다.
*참고: rm 명령어는 파일을 제거하는 명령이 아니라 파일의 이름을 제거하는 명령어이다. 실체를 가리키는 이름이 모두 없어진 시점에서, 즉 링크 카운트가 0이 되었을때 비로소 실체가 삭제된다.
#.link 함수
헤더 : unistd.h
형태 : int link( const char *oldpath, const char *newpath)
인수 : char *oldpath(이미 존재하는 파일 이름) , char *newpath (만들고자하는 링크 이름)
반환 : int 성공:0 실패:-1
주의 : 존재하는 파일 이름과, 만들고자 하는 링크 이름이 하나의 파일 시스템에 있어야 한다. 또한 인자로 디렉터리는 사용할 수 없다.
예제
결과
심볼릭 링크
하드링크는 이름과 실체를 연결하는 구조지만, 심볼릭 링크는 이름에 이름을 연결하는 구조다. 그리고 실제로 심볼릭 링크에 엑세스 (open, link) 가 있을때 비로소 이름의 실체를 갖는다.
특징
- 심볼릭 링크에 대응하는 실체가 존재하지 않아도 된다. 심볼릭 링크는 실제로 액세스 할때가 아니면 이름과 실체의 매핑을 하지 않기 때문에 실체가 없어도 만들 수 있다.
-파일 시스템의 경계를 뛰어넘어 별명을 만들 수 있다. 하드링크는 하나의 파일 시스템 내에서만 만들 수 있는 제약이 있다. 그러나 심볼릭 링크는 파일 시스템의 경계와 관계없이 만들 수 있다.
-디렉터리에도 별명을 붙일 수가 있다. 디렉터리에 대해서는 하드링크는 만들 수 없지만 심볼릭 링크는 만들 수 있다.
-원본 파일이 삭제되면 심볼릭 링크로 원본파일에 접근할 수 없다.(고아링크) , 하드링크는 가능하다.
심볼릭 링크 함수
#include <unistd.h>
int symlink(const char *oldpath, const char *newpath);
oldpath 파일에 대한 심볼릭 링크 newpath 를 만든다.
만일 심볼릭 링크 newpath가 이미 존재한다면 이를 덮어쓰지 않는다.
성공할경우 0을 실패했을경우에는 -1을 반환하며, 적당한 errno 값을 설정한다.
#include <unistd.h>
ssize_t readlink(const char *path, char *buf, size_t bufsiz);
readlink()는 심볼릭 링크 path 가 가리키는 이름을 buf에 담아준다. 이때 최대 bufsize 바이트를 담아주며, bufsize는 보통 buf의 크기로 지정한다. 또한 readlink()는 문자열의 마지막에 \0을 기록하지 않기 때문에 주의해야 한다. 호출에 성공하면 buf에 포함된 바이트 수를 반환한다. 실패했을경우에는 -1을 반환하며, 적당한 errno 값을 설정한다.
예제
결과
파일 삭제 및 이동
리눅스에서 파일을 삭제한다는 것은 실체에 붙인 이름의 개수를 줄인다는 뜻이다. 이를 위해 사용하는 시스템콜이 unlink() 이다. 이것은 link() 와 반대되는 작업을 수행한다. unlink()로 디렉터리를 삭제할 수 는 없다. 또한, 심볼릭 링크를 unlink() 하면 심볼릭 링크만 삭제되고 심볼릭 링크가 가리키는 실제 파일은 삭제되지 않는다.
#.unlink 함수
#include <unistd.h>
int unlink(const char *pathname);
파일을 삭제하는 system call 함수이다. 정확하게는 unlink는 hard link의 이름을 삭제하고 hard link가 참조하는 count를 1감소시킨다. hard link의 참조 count가 0이 되면, 실제 파일의 내용이 저장되어 있는 disk space를 free하여 OS가 다른 파일을 위해서 사용할 수 있도록 한다. 따라서 hard link를 생성하지 않은 파일은 바로 disk space를 해제하여 사실상 파일 삭제한다.출처
예제
#.파일이동
리눅스에서 파일을 이동한다는 것은 하나의 실체에 대한 이름을 변경한다는 것과 대체로 동일하다. 별도의 하드 링크를 만들고 나서 원래 이름을 지운다. 그래서 다음의 두가지가 같은 작업을 수행하는 셈이 된다.
* mv a b
* ln a b
* rm a
단, 최종적인 결과는 같지만 작업 과정에는 중대한 차이가 있다 ln과 rm을 사용하는 경우에는 작업 도중에 이름 a, b가 모두 존재하는 순간이 존재하지만, mv는 그렇지 않다. 하나의 명령어로 이름이 변경되기 때문이다.
참고로 디렉터리에 대해서는 link(2)를 사용할 수 없기 때문에 mv만 사용할 수 있다.
함수
int rename( const char *oldname, const char *newname );
*oldname : 파일 또는 디렉토리의 경로나 이름
*newname : 변경할 파일 또는 디렉토리의 이름
*반환값 : 성공 시 0, 실패 시 errno ( EACCES, ENOENT, EINVAL )
*어떤 종류의 파일이라도 이동할 수 있지만, 파일 시스템을 넘어서 이동할 수는 없다. oldname, newname이 존재하는 파일 시스템이 서로 다른 경우 rename은 실패하고, errno에 상수 EXDEV가 설정된다.
예제
결과
메타 정보 획득하기
유닉스에서 파일은 파일명과 inode, 데이터 블록으로 구성됩니다. 파일명은 사용자가 파일에 접근할 때 사용합니다. inode는 외부적으로는 번호로 표시되며, 파일의 소유자나 크기 등 파일에 관한 정보와 파일의 실제 데이터를 저장하고 있는 데이터 블록의 위치를 나타내는 주소들이 저장되어 있습니다. 데이터 블록은 실제로 데이터가 저장되는 하드 디스크의 공간입니다.
유닉스에서는 파일 정보를 inode에서 읽어옵니다. inode로부터 검색할 수 있는 정보는 파일의 종류, 접근 권한, 하드 링크 개수, 소유자의 UID와 GID, 파일의 크기, 파일 접근 시각, 수정 시각, 파일의 inode 변경 시각 등입니다. inode 정보는 <sys/stat.h> 파일에 정의되어 있는 stat 구조체에 저장됩니다. inode 정보를 검색하고 파일 접근 권한을 확인하고 변경하는 데 사용할 수 있는 함수를 보여줍니다.
파일 정보 검색
int stat(const char *restrict path, struct stat *buf);
int fstat(int fd, struct stat *buf);
파일 접근 권한 확인
int access(const char *path, int amode);
파일 접근 권한 변경 - path의 파일모드를 mode로 변경한다. 성공하면 0, 실패하면 -1 반환.
int chmod(const char *path, mode_t mode);
int fchmod(int fd, mode_t mode);
예제
결과
#.homework
1.인자로 전달 받은 디렉터리를 재귀적으로 순회하면서 발견된 모든 파일의 경로를 표시하는 프로그램을 작성하라.(심볼릭 링크를 따라가지 않도록 주의 한다.)
2.파일을 open() 하고, close() 하기 전에 그 파일을 rename() 하면 어떤 일이 일어나는가? 또한 unlink()를 하면 어떻게 되는지, 다른 파일을 rename()하면 어떻게 되는지 직접 실험해 보자.
3.인자로 지정한 경로의 디렉터리를 만들되, 상위 디렉터리가 없으면 차례대로 만드는 명령어를 작성하라 (mkdir -p 에 해당)
1.인자로 전달 받은 디렉터리를 재귀적으로 순회하면서 발견된 모든 파일의 경로를 표시하는 프로그램을 작성하라.(심볼릭 링크를 따라가지 않도록 주의 한다.)
풀이
결과
2.파일을 open() 하고, close() 하기 전에 그 파일을 rename() 하면 어떤 일이 일어나는가? 또한 unlink()를 하면 어떻게 되는지, 다른 파일을 rename()하면 어떻게 되는지 직접 실험해 보자.
풀이
결과
3.인자로 지정한 경로의 디렉터리를 만들되, 상위 디렉터리가 없으면 차례대로 만드는 명령어를 작성하라 (mkdir -p 에 해당)
풀이
결과
참고:모두를 위한 리눅스 프로그래밍
'컴퓨터 기초 > 운영체제 실습' 카테고리의 다른 글
[유닉스 프로그래밍] 파일관련 연습문제 (0) | 2020.07.14 |
---|---|
15.프로세스와 하드웨어 (0) | 2020.07.12 |
13.grep 명령어 실습 (0) | 2020.07.04 |
12.head 명령어 실습 (0) | 2020.07.04 |
[운영체제 실습] 11.시그널 (0) | 2020.07.03 |