C언어 특정 단어를 찾아서 그 라인을 지울수는 없나요?

msms772의 이미지

안녕하세요. C린이입니다 ㅠㅠ
학교에서 받은 과제 제출기한을 넘기고도 일주일동안 생각도 안나고 검색해도 안나와서 질문드려봅니다.
일종의 텍스트 파일 기반의 데이터 베이스 프로그램으로,
txt에서 특정 단어를 키보드로 받아 검색후, 그 단어가 있는 라인을 지우고 새로 받아 수정해줘야합니다.
가령 텍스트 파일에
1)유야호/48/muhan@dojun
2)정대리/43/doni@doni
3)???/??/???@???
이런 내용이 있다면, 정대리를 검색하여 2번줄을 새로 노광기/42/no@madness로 바꿔줘야하는것입니다.

현재 코드는 이렇습니다.

(파일 포인터 fp1 지정, 메인함수에서 void형태로 실행 후 반환)
void modify()
{
char temp[256], *p;
int find_pos;

if (fopen("list.txt", "r+")) //txt파일 읽고 쓰기 모드로 불러오기
{
while (fgets(temp, 256, fp1) != NULL) //temp 배열에 파일 내용저장
{
p = strstr(temp, "정대리"); //단어찾기
if (p != NULL)
{
find_pos = strlen(temp) - (p - temp) + 1; // 단어 위치로 이동
fseek(fp1, (-1)*find_pos, SEEK_CUR);
fwrite("노광기", 3, 1, fp1); // 단어수정
fseek(fp1, find_pos - 4, SEEK_CUR); // fgets로 읽은 위치로 복귀
}
}
}
fclose(fp1);
}

위 코드도 어딘가에서 긁고 수정한겁니다 ㅠ 새벽3시에 갑갑해서 질문드려봅니다...

세벌의 이미지

님께서 쓴 글 그대로 인용.

가령 텍스트 파일에
1)유야호/48/muhan@dojun
2)정대리/43/doni@doni
3)???/??/???@???
이런 내용이 있다면, 정대리를 검색하여 2번줄을 새로 노광기/42/no@madness로 바꿔줘야하는것입니다.
노광기/42/no@madness 는 어디서 튀어나온 건가요?

주의. kldp에서는 소스 코드는 code 태그로 감싸야 제대로 보입니다.

msms772의 이미지

항상 글을 보기만해서 몰랐는데 code태그로 감싸야하는건가보군요.
위의 코드는 완성된 코드가 아니고, 단지 지정된 단어를 검색,수정하는 단계에서 막힌 흔적입니다.
궁극적으로는 키보드로 입력해서 2번째줄에 넣어주는것이 목표임으로,
수정할 사람을 입력하세요)정대리(enter) -> 노광기(enter)42(enter)no@madness(enter)
이렇게 구현되어야합니다..

chanik의 이미지

파일 중간의 내용 일부를 삭제/삽입하면 알아서 뒤쪽 내용이 당겨지거나 밀려나가는 기본기능이 제공되면 편하겠지만, 그런 기능은 없으므로 스스로 구현해야 합니다.

[1] 새 파일을 쓰기모드로 열어서 기존파일의 이전줄/삭제대상줄/이후줄을 새 파일에 이전줄/새줄/이후줄 식으로 쓰고, 성공하면 새 파일로 기존파일을 교체하는 식이 무난할 겁니다. [2] 원본파일을 직접 수정하려면 삭제대상줄 위치로부터 새줄/이후줄 전체를 다시 쓰고 파일 총길이가 줄어들 경우 truncate로 보정하면 되겠는데, 이 과정에 기존 내용물(이후줄 내용)을 잃지 않도록 신경써야겠죠. 메모리/별도파일로 임시보관하거나 색다른 교묘한 기법을 생각해내든가.. 어쨌든 원본 수정방식은 다소 복잡하고 중간에 오류가 나면 원본이 꼬여버리는 위험은 감수하는 셈입니다.

[3] 처음부터 줄 길이를 일정길이로 통일해서 쓴다면 특정줄 교체는 간편해질 수 있겠습니다. 그냥 기존줄 위치에 새 줄 내용을 쓰기 하면 덮어쓰기가 되니까요.

익명 사용자의 이미지

프로그래밍을 처음 공부하는 사람들은 대개 그런 기능이 기본적으로 제공되지 않는다는 사실에 당혹스러워 하곤 합니다.

각종 워드프로세서 및 텍스트 편집기 프로그램들이 너무 당연하다는 듯이 텍스트 한가운데에서 삽입/삭제를 지원하니 헷갈리는 것입니다.

잘 생각해보면, 그게 원래 당연한 게 아닌데 말이지요. :)

msms772의 이미지

지난주에 이 과제를 시작한 이후 프로그래머들이 분명 이 기능을 함수 하나로 구현하지 않았을까..하는 헛된 희망에 빠져있었는데 아주 명확하게 부셔주시는군요 ㅠㅠ
저도 A, B, C.를 두고 스왑하는 방법을 생각하고 작성해봤었는데, 해당 라인을 삭제하고 새로 넣는 부분이 문제입니다.
한 기점을 두고 나눠서 끼워넣는건 많이 나오던데 삭제하고 새로넣는건 나오질 않더라구요...
어떤 함수를 써야할지 감도 안와서 질문드립니다

익명 사용자의 이미지

전통적인 방법을 쓰면 됩니다.

원고지에 한가득 글을 썼는데, 그 한가운데에 문장을 삭제하거나 추가해야 할 때는 어떻게 하나요?

취소선으로 찍 긋거나 여백에 써서 추가하는 꼼수도 있지만 컴퓨터 메모리에 그런 짓을 할 수는 없으니까요.

======

별 수 없이 새 원고지를 가져와 통째로 옮겨 써야 합니다. 구체적으론...

1. 수정해야 할 지점까지의 글을 고스란히 옮겨 씁니다.
2. 수정해야 할 부분을 적절히 고쳐 씁니다.
3. 끝까지 마찬가지로 옮겨 씁니다.

그러고 나서 원본 원고지는 버리고, 새 원고지를 그 자리에 놓으면 끝이지요.

======

이걸 알아서 다 해 주는 함수 같은 것은 없습니다. 직접 조립해야 합니다.

다행히도 문제가 별로 어렵지 않으니까요.
원문 텍스트를 한 줄씩 읽어서, 치환해야 하면 치환해서 출력하고, 아니면 그냥 출력하고, 그 뿐이죠.

vagabond20의 이미지

수정할 대상이 되는 레코드의 data type 이나 length, 이러한 특성을 특정해야 하므로 이런경우 대부분 커스텀방식으로 함수를 만들어 실행합니다.

컴퓨터로 프로그래밍을 하는 직업을 갖게 되면 (혹은 학교에서 전산과목 숙제할때) 처음에 흔하게 하는 프로그래밍 작업입니다.
(1) 원본을 읽어들인다.
(2) 수정해야 할 레코드까지 읽고 거기까지 새로운 화일에 쓴다.
(3) 수정하거나 삭제할 내용을 그 새로운 화일에 반영한다. (수정, 삭제 혹은 새 레코드 삽입)
(4) 원본으로부터 그 다음 레코드에서 다시 (2) & (3) 의 출력화일에 마저 쓴다.
(5) 확인: 원본화일과 새로운 화일을 diff 등의 유틸리티 프로그램으로 확인하여 마무리 한다.

뭐, 대충 이런식일겁니다.
위의 단계는 일반적인 sequential file 을 가정한것임을 잘 아실겁니다.

여의도자바

msms772의 이미지

원본을 1에 넣고, 수정 기점까지 읽은후 거기까지 2에다가 넣고, 수정할 내용을 3에다가 넣은후
3을 2에다 넣고 기점 이후의 1에 담긴 내용을 다시 넣으란 말씀이신건가요?
머리가 핑핑 도네요 ㅠㅠ 답변 감사합니다

chanik의 이미지

위의 다른 분들 설명이 자세하지만, 사용할 함수를 물으시니..
아래와 같이 fgets()/fputs() 반복하시면 되지 않나요.
컴파일되는 코드는 아니고 대충 적은 것입니다.
오류확인/처리 코드도 보충해야 하고요.

#defile MAXLINE 1024
char line[MAXLINE+1];
char line_new[] = "노광기/42/no@madness";
FILE *fin  = fopen(..., "r");
FILE *fout = fopen(..., "w");
while(fgets(line, MAXLINE, fin) != NULL) {
    if(strstr(line, "정대리"))   // 삭제할 줄을 발견하면, 새 줄로 대체 출력
        if(fputs(line_new, fout) == EOF)
            break;
    else
        if(fputs(line, fout) == EOF)
            break;
}
if(ferror(fin)) {   // fgets()가 오류에 의해 NULL 리턴한 경우
    perror("fin");
    ...  // 오류처리
}
if(ferror(fout)) {  // fputs() 오류 발생으로 루프 빠져나온 경우
    perror("fout");
    ...  // 오류처리
}
fclose(fin);
fclose(fout);
 
// 위 과정에 오류 없었으면 원본 삭제후 fout 파일을 원본파일로 이름 변경
// 원본 삭제 대신 원본.bak 식으로 보관해둬도 무방
msms772의 이미지

이 코드를 고쳐서 만져봤는데 기존에 담겨있어야 할 전체 내용이 사라집니다 시무룩...
조만간 기말 프로젝트도 하나 해야하는데...자신감이 없어지네요 ㅠㅠ
그래도 도와주셔서 감사합니다.

세벌의 이미지

뭔가 예상한 결과가 안 나올 때는 중간 중간 printf 함수를 넣어보셔요.
파일에 쓸 내용을 화면에 출력해보는 거죠.
그리고 아! 내가 생각한 내용이 아니라 다르게 써지는구나.
어떻게 하면 되지?
이런 고민을 하면서 실력이 느는 겁니다.
포기하지 말고 성공하시길.

아. 안 된다고만 하면 도와드리기 어려워요.
안 되면 안 되는 대로 에러나는 소스라도 보여주셔요.

msms772의 이미지

현재 조언해주신대로 printf를 넣어가며 점검했는데 strstr함수가 있는 곳까지만 작동하고 해당 라인이 삭제되지 않습니다.
여기다 살을 붙여서 수정까지 해야하는데...갑갑하네요 ㅠㅠ

#include <stdio.h>
#include <string.h>
#define _CRT_SECURE_NO_WARNINGS
#define MAXLINE 10240
char name[50];
char mail[50];
int age;
 
void modifycopy()	//수정
{
	int count = 0;
	char *key="정대리";	//수정할 내용 키
	//char *infile, char *outfile, 
	//char buf[MAXLINE];
	char line[MAXLINE + 1];
	char line_new[] = "유야호/48/muhan@dojun\n";
	FILE *fin, *fout;
	fopen_s(&fin,"list.txt", "r");
	fopen_s(&fout,"cache.txt", "w");
 
	while (fgets(line, MAXLINE, fin) != NULL)//list.txt파일을 끝까지 내용읽어서 line배열에 저장
	{
		if (strstr(line, key)) {  // 삭제할 줄을 키를 통해 검색
			if (fputs(line_new, fout) == EOF)	//cache.txt에 새로 넣을 문장을 저장
	fprintf(stdout, "이름:");
	fscanf(stdin, " %[^\n]s", name); //[^\n]s 공백까지 입력,세 문장 다 한칸 띄우고
	fprintf(stdout, "나이:");
	fscanf(stdin, " %d", &age);
	fprintf(stdout, "메일주소:");
	fscanf(stdin, " %[^\n]s", mail);
	fprintf(fp2, "%s/%d/%s\n", name, age, mail); //텍스트 파일에 내용을 입력하는 fprintf
				while ((count = fgetc(fout)) != EOF);	//전체 출력, 수업 예제
			{
				putchar(count);
			}
				break;
		}
		else {
			if (fputs(line, fout) == EOF)
				printf("check\n");
				break;
		}
	}
	/*while ((count = fread(line, sizeof(char), 100, fin)) != 0) 수정된 파일을 복사해주는 함수
	{
		fwrite(line, sizeof(char), count, fout);
	}*/
	/*if (ferror(fin)) {   // fgets()가 오류에 의해 NULL 리턴한 경우
		perror("fin");
		return -1; // 오류처리
	}
	if (ferror(fout)) {  // fputs() 오류 발생으로 루프 빠져나온 경우
		perror("fout");
		return -1; // 오류처리
	}*/
	fclose(fin);
	fclose(fout);
}
 
int main()
{
	modifycopy();
	return 0;
}
vagabond20의 이미지

이름을 한글로 하지 않은 차이만 있을뿐입니다.

#include <stdio.h>
#include <string.h>
#define _CRT_SECURE_NO_WARNINGS
#define MAXLINE 10240
 
void modifycopy()
{
	int count = 0;
	char *key="NNN";
 
	char line[MAXLINE + 1];
	char line_new[] = "AAA/42/no@madness\n";
	FILE *fin, *fout;
	fopen_s(&fin,"list.txt", "r");
	fopen_s(&fout,"insert.txt", "w");
	while (fgets(line, MAXLINE, fin) != NULL) 
	{
		if (strstr(line, key)) {  
			fputs(line_new, fout);
		}
		else {
			fputs(line, fout);
		}
	}
 
	fclose(fin);
	fclose(fout);
}
int main()
{
	modifycopy();
	return 0;
}

여의도자바

msms772의 이미지

정말 처음에 개념도 모르겠고 막막했는데 위에서 긴 코드들에 당황하다가
후...며칠동안 노려보면서 이 코드를 기반으로 기능을 쌓아올리니 되네요.
지금 기능 한개 말고 다 구현했습니다. 정말 감사합니다!

vagabond20의 이미지

이쪽 '바닥' 에서 하는 말이 있습니다. (참, 미국에서요.)
지금은 은퇴한 저의 매니저 래리 아저씨가 자주 하던 말이기도 합니다.
KISS: Keep It Simple, Stupid. - 원래 유래는 1960 년대에 미 해군에서 컴퓨터일 (프로젝트) 할때 디자인을 최대한 간단하게, 군더더기없이 하라는 뜻으로 쓰이던 말입니다. 직역하면 한국사람 정서에는 좀 공격적인 말이 되겠지만, 사실 이 '스튜피드'는 농담식으로 자주 쓰이는 친근한 말이기도 합니다.

일이 잘 안 풀릴땐 핵심이 무엇인지 파악하는게 제일 중요합니다.

여의도자바

라스코니의 이미지

퀵으로 봤을 때 이상한 점 말씀드리겠습니다.

1. if (fputs(line_new, fout) == EOF) 뒤에 { } 가 필요하지 않는가?
2. 왜 line_new를 파일에 쓴 다음에 또다시 입력값을 받는가?
3. fprintf(fp2,..) 에서 fp2는 어디에서 온 것인가?
4. while ((count = fgetc(fout)) != EOF); 의 끝에 ; 는 불필요?
5. while ((count = fgetc(fout)) != EOF)에서 이미 fout는 파일의 끝은 가리키고 있을 수 있음. fseek(fout, 0, SEEK_SET) 필요?

확인해 보세요.

chanik의 이미지

아래와 같이 if if else if 가 연속되는 곳에 { } 를 두 곳에 넣어주면 될겁니다. 처음에 fputs() 반환값 검사 생략했다가 너무 심한 것 같아서 if를 끼워 수정하면서 오류를 만들었습니다. 어설프게 돕다가 혼란을 일으켰네요.

while(fgets(line, MAXLINE, fin) != NULL) {
    if(strstr(line, "정대리")) {  // 삭제할 줄을 발견하면, 새 줄로 대체 출력
        if(fputs(line_new, fout) == EOF)
            break;
    }
    else {
        if(fputs(line, fout) == EOF)
            break;
    }
}

대체문자열 끝에 '\n'도 추가해야 하는군요.

char line_new[] = "노광기/42/no@madness\n";




아래와 같이 샘플 만들어 실행해보니 동작하는 것 같습니다. 이 코드는 변경된 사본 만드는것까지만 하므로, 성공시 원본파일을 사본으로 대체하는 코드는 짜넣으셔야 합니다. (그리고, modifycopy() 내에서 fopen() 오류검사도 해야 하고, return -1로 대충 처리한 부분도 fin/fout에 대해 fclose()를 실행하고 리턴하도록 보완해야 합니다)

$ cat sample.c
#include <stdio.h>
#include <string.h>
 
#define MAXLINE 1024
 
int modifycopy(char *infile, char *outfile, char *key, char *line_new)
{
    char line[MAXLINE+1];
    //char line_new[] = "노광기/42/no@madness";
    FILE *fin  = fopen(infile,  "r");
    FILE *fout = fopen(outfile, "w");
    while(fgets(line, MAXLINE, fin) != NULL) {
        if(strstr(line, key)) {  // 삭제할 줄을 발견하면, 새 줄로 대체 출력
            if(fputs(line_new, fout) == EOF)
                break;
        }
        else {
            if(fputs(line, fout) == EOF)
                break;
        }
    }
    if(ferror(fin)) {   // fgets()가 오류에 의해 NULL 리턴한 경우
        perror("fin");
        return -1; // 오류처리
    }
    if(ferror(fout)) {  // fputs() 오류 발생으로 루프 빠져나온 경우
        perror("fout");
        return -1; // 오류처리
    }
    fclose(fin);
    fclose(fout);
 
    return 0;
}
 
int main()
{
    modifycopy("test.txt", "test_new.txt", "정대리", "노광기/42/no@madness\n");
    return 0;
}
$ gcc sample.c -o sample
$ cat test.txt
유야호/48/muhan@dojun
정대리/43/doni@doni
김삿갓/40/got@kim
$ ./sample
$ cat test_new.txt
유야호/48/muhan@dojun
노광기/42/no@madness
김삿갓/40/got@kim

msms772의 이미지

일부터 90까지 다 떠먹여주셨는데 자꾸 질문드려서 죄송합니다.
정수형으로 반환하셨던데 함수를 재정의한다고 오류가 나서 보이드 형으로 수정하였습니다.
비쥬얼 스튜디오로 하고있는데 수정할 내용을 입력하려고 했지만 아무 반응이 없이 종료됩니다.
리눅스로 하신것같아서 리눅스로도 해봤는데 segemtation fault(core dumped)오류가 떠서 수정하니 오히려 더 망친것같습니다.

#include <stdio.h>
#include <string.h>
#define _CRT_SECURE_NO_WARNINGS
#define MAXLINE 10240
char name[50];
char mail[50];
int age;
 
void modifycopy()	//수정
{
	int count = 0;
	char *key="정대리";	//수정할 내용 키
	//char *infile, char *outfile, 
	//char buf[MAXLINE];
	char line[MAXLINE + 1];
	char line_new[] = "유야호/48/muhan@dojun\n";
	FILE *fin, *fout;
	fopen_s(&fin,"list.txt", "r");
	fopen_s(&fout,"cache.txt", "w");
 
	while (fgets(line, MAXLINE, fin) != NULL)//list.txt파일을 끝까지 내용읽어서 line배열에 저장
	{
		if (strstr(line, key)) {  // 삭제할 줄을 키를 통해 검색
			if (fputs(line_new, fout) == EOF)	//cache.txt에 새로 넣을 문장을 저장
	fprintf(stdout, "이름:");
	fscanf(stdin, " %[^\n]s", name); //[^\n]s 공백까지 입력,세 문장 다 한칸 띄우고
	fprintf(stdout, "나이:");
	fscanf(stdin, " %d", &age);
	fprintf(stdout, "메일주소:");
	fscanf(stdin, " %[^\n]s", mail);
	fprintf(fp2, "%s/%d/%s\n", name, age, mail); //텍스트 파일에 내용을 입력하는 fprintf
				while ((count = fgetc(fout)) != EOF);	//전체 출력, 수업 예제
			{
				putchar(count);
			}
				break;
		}
		else {
			if (fputs(line, fout) == EOF)
				printf("check\n");
				break;
		}
	}
	/*while ((count = fread(line, sizeof(char), 100, fin)) != 0) 수정된 파일을 복사해주는 함수
	{
		fwrite(line, sizeof(char), count, fout);
	}*/
	/*if (ferror(fin)) {   // fgets()가 오류에 의해 NULL 리턴한 경우
		perror("fin");
		return -1; // 오류처리
	}
	if (ferror(fout)) {  // fputs() 오류 발생으로 루프 빠져나온 경우
		perror("fout");
		return -1; // 오류처리
	}*/
	fclose(fin);
	fclose(fout);
}
 
int main()
{
	modifycopy();
	return 0;
}

chanik의 이미지

삭제할 줄을 만나면 미리 정해진 내용으로 교체하는 대신, printf()/scanf() 반복하며 새 줄 내용 입력을 받아 출력하는 의도라 짐작하고 손댔습니다.

{ }로 조건문 적용범위를 명확히 한 것과, 불필요해보이는 코드 제거한 것이 전부입니다. 손 댄 부분만 올립니다. 하루이틀쯤 시간을 내어 C언어 입문서를 앞부분만이라도 다시 한 번 읽어보시면 도움될 것입니다. 지금은 기본적인 실수 몇 개 때문에 시간상 큰 손해를 보시는 상황입니다. 이번 과제 수행중 잘 느끼셨을테니 꼭 읽어보시길..

    while (fgets(line, MAXLINE, fin) != NULL)//list.txt파일을 끝까지 내용읽어서 line배열에 저장
    {
        if (strstr(line, key)) {  // 삭제할 줄을 키를 통해 검색
            fprintf(stdout, "이름:");
            fscanf(stdin, " %[^\n]s", name); //[^\n]s 공백까지 입력,세 문장 다 한칸 띄우고
            fprintf(stdout, "나이:");
            fscanf(stdin, " %d", &age);
            fprintf(stdout, "메일주소:");
            fscanf(stdin, " %[^\n]s", mail);
            fprintf(fout, "%s/%d/%s\n", name, age, mail); //텍스트 파일에 내용을 입력하는 fprintf
        }
        else {
            if (fputs(line, fout) == EOF) {  // if문 쓸 때 {}로 범위 명확히.. {} 안하면 if가 printf()에만 적용되므로 break가 항상 실행됨
                printf("check\n");
                break;
            }
        }
    }

지금 컴 환경이 열악하고 비주얼스튜디오도 없어서, cygwin64 설치해서 mingw로 테스트했습니다. UTF-8로 저장하고 빌드하면 도스창에서 실행시 깨지고 소스/데이터 모두 EUC-KR로 저장/빌드하니까 되네요. 아마 비주얼스튜디오에서도 되겠죠.

c:\test>type list.txt
유야호/48/muhan@dojun
정대리/43/doni@doni
김삿갓/40/got@kim
 
c:\test>sample
이름:홍길동
나이:235
메일주소:hong@there
 
c:\test>type cache.txt
유야호/48/muhan@dojun
홍길동/235/hong@there
김삿갓/40/got@kim
댓글 첨부 파일: 
첨부파일 크기
Package icon fgets.zip1023바이트
msms772의 이미지

다른 분들이 A,B를 만들어 스왑하라, 덮어써라 조언을 하나씩 해주셨는데.
제출을 포기하고 고수께서 적어주신 이 코드를 며칠동안 노려보니 구조가 이해되면서 제가 원하는 기능을 구현할수 있었습니다!
이제 9할 정도 완성하였고, 이제 예외처리만 마저하고 주석 정리하면 과제 끝입니다!
지지난주 과제를 지금까지 한다고 제가 마음이 조급했었는데, 이렇게 큰 정성과 관심을 가져주셔서 감사합니다. 며칠동안 많이 괴롭혀서 죄송하기도 하네요 ㅎㅎ;;

라스코니의 이미지

아래 순서대로 한번 해 보세요.

1) 파일을 2개 open한다. 하나는 .txt 파일 read 열기, 또 다른 하나는 temp 파일 write 열기
2) 찾을 단어를 입력받는 scanf()를 둔다. 입력 버퍼를 비우기 위해 fflush(stdin)을 실행한다
3) .txt 파일을 fgets()로 한줄씩 읽어서 입력받은 단어 "정대리"가 있는지 확인한다. 없으면 temp 파일에 똑같이 쓰기한다.
4) 끝까지 읽었는데 없으면 파일을 모두 fclose()로 종료하고 프로그램을 끝낸다.
5) 찾았으면 3개의 정보를 scanf()로 입력 받는다. 그 정보를 .txt 파일에서 읽은 line 정보 대신에 temp 파일에 쓴다. replace file flag를 1로 설정한다.
6) .txt 파일이 끝날때까지 계속 반복하고 계속 temp 파일에 쓴다.
7) 양 파일을 모두 fclose()로 종료한다.
8) replace file flag가 1이면 원래 파일을 지우고, temp 파일의 이름을 이전 .txt 파일로 변경한다.

도움이 되었길 바랍니다.

익명 사용자의 이미지

다 좋은데...

fflush(stdin)는 쓰지 맙시다. undefined behavior입니다.

컴파일러들이 나름대로 어떻게 동작하는지 명시해 놓은 게 있긴 한데, 아마 원하는 동작이 아닐걸요.

msms772의 이미지

origin, temp를 만들어서
원본을 버퍼에 저장후, 검색하여 기능 구현, temp에 옮겨쓰고
각각 종료후 다시 반대로 불러와서 temp를 origin에 저장. 해보니 되네요 이게..
말만 들으니 이해가 안됬는데 막상 부딛혀보니 됩니다 이게(?)

ymir의 이미지

strstr() 은 substring 을 검색해 주는 거라, 지금 같은 상황에서는 정확한 검색 결과를 보장하기 어렵습니다.
각 구분자를 기준으로 명확히 검색할 수 있는 방법을 사용하셔야 합니다.
명색이 주제가 text db 인 만큼, 필드 구분자, 레코드 구분자를 명확히 정의하고, 규칙에 맞게 각각의 데이터를 파싱하는게 나을 것 같습니다.

원본 파일을 직접적으로 수정하는 것은, 이미 많은 분들이 언급하셨듯이 바람직하지 않습니다.
중간에 문제가 생겨 중단되었을 때, 원본 파일의 무결성을 지킬 방법이 없기 때문이죠.
그래서 전통적으로 사본을 만든 후에, 원본에 덮어 쓰는 방식을 사용합니다.
사본을 만들 때, 레코드를 추가하거나, 레코드 하나를 건너 뛰거나(삭제), 레코드 내용을 바꿔서 저장하는 식으로 해서, 원본 파일을 편집한 것과 동일한 효과를 얻는 거죠.

이 과제에서 기대하는 바가, 아마도 위 두 가지가 아닐까 생각합니다.

이 과제가 재밌는게, unix 시스템에서 가장 오래된 부분이면서 지금도 사용되고 있는 부분과 같은 기능을 요구하고 있습니다.
바로 /etc/passwd 나 /etc/shadow 와 같은 파일들이죠.

passwd 파일을 예로 들면, setpwent(), getpwent(), endpwent() 와 같이 레코드를 조회할 수 있는 함수를 제공하고 있고, putpwent() 와 같이 레코드를 파일에 저장할 수 있는 함수도 제공하고 있습니다.
이를 이용해서 getpwnam() 이나 getpwuid() 등과 같이, 특정 필드로 검색할 수 있는 함수도 이미 만들어 제공하고 있습니다.
뭔가 답이 안 보일 때에는, 남이 먼저 만들어 놓은걸 분석해 보는 것도 도움이 됩니다.

간단히 위 함수들을 참고해서 최대한 간단하게 구현해 보면, 다음과 같은 형태가 될 수 있을겁니다.
(file lock, tmp file, perm, owner 등과 같이 추가로 고려해야 할 부분 많음)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#define PPLENT_NAM	"pplent.txt"
#define PPLENT_OLD	"pplent.old"
#define PPLENT_TMP	"pplent.tmp"
 
struct pplent
{
	const char *name;
	int age;
	const char *email;
};
 
enum index_pplent
{
	IDX_PPL_NAME = 0,
	IDX_PPL_AGE,
	IDX_PPL_EMAIL,
	IDX_PPL_MAX
};
 
static FILE *ppl_fp = NULL;
 
void setpplent(void)
{
	if (ppl_fp == NULL)
		ppl_fp = fopen(PPLENT_NAM, "r");
	else
		fseek(ppl_fp, 0L, SEEK_SET);
}
 
static inline char *split_at(char *buf, int delim)
{
	char *s = strchr(buf, delim);
 
	if (s)
		*s++ = 0x00;
 
	return s;
}
 
struct pplent *getpplent(void)
{
	static char buf[BUFSIZ];
	static struct pplent ppl;
	char *tok, *next;
	char *toklist[IDX_PPL_MAX];
	int index;
 
	if (ppl_fp == NULL)
		return NULL;
 
	if (fgets(buf, sizeof(buf), ppl_fp) == NULL)
		return NULL;
 
	next = &buf[0];
	index = 0;
	while (index < IDX_PPL_MAX - 1)
	{
		tok = next;
		next = split_at(tok, '/');
		if (next == NULL)
			return NULL;
 
		toklist[index++] = tok;
	}
 
	tok = next;
	split_at(tok, '\n');
	toklist[index++] = tok;
 
	ppl.name = toklist[IDX_PPL_NAME];
	ppl.age = atoi(toklist[IDX_PPL_AGE]);
	ppl.email = toklist[IDX_PPL_EMAIL];
 
	return &ppl;
}
 
int putpplent(const struct pplent *p, FILE *fp)
{
	if (p == NULL || fp == NULL)
		return -1;
 
	fprintf(fp, "%s/%d/%s\n", p->name?p->name:"", p->age, p->email?p->email:"");
 
	return 0;
}
 
void endpplent(void)
{
	if (ppl_fp)
	{
		fclose(ppl_fp);
		ppl_fp = NULL;
	}
}
 
struct pplent *getpplbyname(const char *name)
{
	struct pplent *p;
 
	setpplent();
	while ((p = getpplent()))
	{
		if (!strcmp(p->name, name))
			break;
	}
	endpplent();
 
	return p;
}

기본적으로 레코드를 조회할 수 있는 함수들이 만들어졌으니, 이제 하나씩 다른 파일에 옮겨 쓰면서 원하는 대로 데이터를 조작하면 되는거죠.
아래 몇 가지 샘플 코드 덧붙입니다. 로직만 참고하세요.

int test_init_ppl(void)
{
	struct pplent ppl;
	FILE *fp = fopen(PPLENT_NAM, "w");
 
	if (fp == NULL)
		return -1;
 
	ppl.name = "malcolm";
	ppl.age = 20;
	ppl.email = "malcolm@gmail.com";
	putpplent(&ppl, fp);
 
	ppl.name = "onara";
	ppl.age = 22;
	ppl.email = "inara@gmail.com";
	putpplent(&ppl, fp);
 
	ppl.name = "zoe";
	ppl.age = 22;
	ppl.email = "zoe@gmail.com";
	putpplent(&ppl, fp);
 
	ppl.name = "hoban";
	ppl.age = 24;
	ppl.email = "hoban@gmail.com";
	putpplent(&ppl, fp);
 
	fclose(fp);
 
	return 0;
}
 
void test_list_ppl(void)
{
	struct pplent *p;
 
	setpplent();
	while ((p = getpplent()))
		printf("%s/%d/%s\n", p->name, p->age, p->email);
	endpplent();
}
 
int test_get_ppl_byname(const char *name)
{
	struct pplent *p;
 
	p = getpplbyname(name);
	if (p)
	{
		printf("%s/%d/%s\n", p->name, p->age, p->email);
		return 0;
	}
 
	printf("'%s' not found ..\n", name);
 
	return -1;
}
 
int test_ren_ppl_byname(const char *name, const char *replace)
{
	struct pplent *p;
	FILE *fp = fopen(PPLENT_TMP, "w");
 
	if (fp == NULL)
		return -1;
 
	setpplent();
	while ((p = getpplent()))
	{
		if (!strcmp(p->name, name))
			p->name = replace;
 
		putpplent(p, fp);
	}
	endpplent();
	fclose(fp);
 
	rename(PPLENT_NAM, PPLENT_OLD);
 
	return rename(PPLENT_TMP, PPLENT_NAM);
}
 
int test_add_ppl_entry(const char *name, int age, const char *email)
{
	struct pplent *p, ppl;
	FILE *fp;
 
	if (getpplbyname(name))	/* people exist */
		return -1;
 
	fp = fopen(PPLENT_TMP, "w");
	if (fp == NULL)
		return -1;
 
	setpplent();
	while ((p = getpplent()))
		putpplent(p, fp);
	endpplent();
 
	ppl.name = name;
	ppl.age = age;
	ppl.email = email;
	putpplent(&ppl, fp);
 
	fclose(fp);
 
	rename(PPLENT_NAM, PPLENT_OLD);
 
	return rename(PPLENT_TMP, PPLENT_NAM);
}
 
int test_del_ppl_byname(const char *name)
{
	struct pplent *p;
	FILE *fp = fopen(PPLENT_TMP, "w");
 
	if (fp == NULL)
		return -1;
 
	setpplent();
	while ((p = getpplent()))
	{
		if (!strcmp(p->name, name))
			continue;
 
		putpplent(p, fp);
	}
	endpplent();
	fclose(fp);
 
	rename(PPLENT_NAM, PPLENT_OLD);
 
	return rename(PPLENT_TMP, PPLENT_NAM);
}
 
int main(void)
{
	test_init_ppl();
	printf("* Initial people list ..\n");
	test_list_ppl();
 
	printf("\n* Lookup by name 'malcolm' ..\n");
	test_get_ppl_byname("malcolm");
 
	printf("\n* Lookup by name 'dobson' ..\n");
	test_get_ppl_byname("dobson");
 
	printf("\n* Rename 'onara' to 'inara' ..\n");
	test_ren_ppl_byname("onara", "inara");
	test_list_ppl();
 
	printf("\n* Add 'river' to people list ..\n");
	test_add_ppl_entry("river", 18, "river@gmail.com");
	test_list_ppl();
 
	printf("\n* Remove 'hoban' from people list ..\n");
	test_del_ppl_byname("hoban");
	test_list_ppl();
 
	return 0;
}

되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』

msms772의 이미지

관심과 정성을 이렇게 쏟아주져서 감사합니다.
사실 적어주신 이 코드들은 봐도 저에게 너무 어려워서 이해하지못했는데,
결국 제출에 대한 욕심을 버리고 찬찬히 살펴보니 조금 눈에 들어오긴 하네요 ㅎㅎ..
덕분에 과제 9할은 완성했습니다. 내일까지 제출인데 예외 처리가 추가가 안되서 혹시나 있나 살펴보러왔는데 이렇게 긴 장문을 ...고수분들 감사합니다

swish95의 이미지

답변 다신 분들은 충분히 설명하신거지만 질문자님이 너무 어렵게 접근하는 법만 배우시는게 아닌가 싶습니다.

죄대한 단순하게 그냥 파일을 그대로 읽어서 다른 파일에 쓰면서 수정하세요

이름앞에 No 를 두자리로 부여 한다거한 하는식의 약간의 수정도 어차피 같은 방식이니까요

------------------------------------------------------------
ProgrammingHolic

익명 사용자의 이미지

가끔, 아무 예고도 없이, 딱히 어렵거나 특별한 점 없는 과제성 질문에 답변이 쏟아지는 경우가 있습니다.

물론 그런 행운을 누리려면 잘 정의된, 숙련자라면 심심풀이 삼아 10~20분이면 답할 수 있는 그런 질문이어야 하죠.

사람들이 이번에 유난히 심심했나봐요.

msms772의 이미지

다들 많은 답변을 해주셨는데 제가 정신이 없어서 받아들일 준비가 안되어있었습니다.
어느시점부터 한 고수분이 불러주신 코드를 유심히...며칠동안 살펴보니 해결법이 나와버렸네요.
분에 넘치는 관심들이였는데 떠먹여줘도 받아먹지를 못하는 며칠전의 나 ㅠㅠ

vagabond20의 이미지

이곳이 보통은 이렇게까지는 친절하게, 끝까지 살펴주지는 않는데 말이죠. :)

여의도자바

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.