C언어 특정 단어를 찾아서 그 라인을 지울수는 없나요?
안녕하세요. 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시에 갑갑해서 질문드려봅니다...
문제 요구사항이 뭐죠?
님께서 쓴 글 그대로 인용.
노광기/42/no@madness 는 어디서 튀어나온 건가요?주의. kldp에서는 소스 코드는 code 태그로 감싸야 제대로 보입니다.
세벌 https://sebuls.blogspot.kr/
아. 똑같이 키보드로 다시 입력받아야합니다.
항상 글을 보기만해서 몰랐는데 code태그로 감싸야하는건가보군요.
위의 코드는 완성된 코드가 아니고, 단지 지정된 단어를 검색,수정하는 단계에서 막힌 흔적입니다.
궁극적으로는 키보드로 입력해서 2번째줄에 넣어주는것이 목표임으로,
수정할 사람을 입력하세요)정대리(enter) -> 노광기(enter)42(enter)no@madness(enter)
이렇게 구현되어야합니다..
파일 중간의 내용 일부를 삭제/삽입하면 알아서 뒤쪽
파일 중간의 내용 일부를 삭제/삽입하면 알아서 뒤쪽 내용이 당겨지거나 밀려나가는 기본기능이 제공되면 편하겠지만, 그런 기능은 없으므로 스스로 구현해야 합니다.
[1] 새 파일을 쓰기모드로 열어서 기존파일의 이전줄/삭제대상줄/이후줄을 새 파일에 이전줄/새줄/이후줄 식으로 쓰고, 성공하면 새 파일로 기존파일을 교체하는 식이 무난할 겁니다. [2] 원본파일을 직접 수정하려면 삭제대상줄 위치로부터 새줄/이후줄 전체를 다시 쓰고 파일 총길이가 줄어들 경우 truncate로 보정하면 되겠는데, 이 과정에 기존 내용물(이후줄 내용)을 잃지 않도록 신경써야겠죠. 메모리/별도파일로 임시보관하거나 색다른 교묘한 기법을 생각해내든가.. 어쨌든 원본 수정방식은 다소 복잡하고 중간에 오류가 나면 원본이 꼬여버리는 위험은 감수하는 셈입니다.
[3] 처음부터 줄 길이를 일정길이로 통일해서 쓴다면 특정줄 교체는 간편해질 수 있겠습니다. 그냥 기존줄 위치에 새 줄 내용을 쓰기 하면 덮어쓰기가 되니까요.
프로그래밍을 처음 공부하는 사람들은 대개 그런 기능이
프로그래밍을 처음 공부하는 사람들은 대개 그런 기능이 기본적으로 제공되지 않는다는 사실에 당혹스러워 하곤 합니다.
각종 워드프로세서 및 텍스트 편집기 프로그램들이 너무 당연하다는 듯이 텍스트 한가운데에서 삽입/삭제를 지원하니 헷갈리는 것입니다.
잘 생각해보면, 그게 원래 당연한 게 아닌데 말이지요. :)
구체적으로 어떤 함수가 쓰이는지 질문드릴수 있을까요?
지난주에 이 과제를 시작한 이후 프로그래머들이 분명 이 기능을 함수 하나로 구현하지 않았을까..하는 헛된 희망에 빠져있었는데 아주 명확하게 부셔주시는군요 ㅠㅠ
저도 A, B, C.를 두고 스왑하는 방법을 생각하고 작성해봤었는데, 해당 라인을 삭제하고 새로 넣는 부분이 문제입니다.
한 기점을 두고 나눠서 끼워넣는건 많이 나오던데 삭제하고 새로넣는건 나오질 않더라구요...
어떤 함수를 써야할지 감도 안와서 질문드립니다
전통적인 방법을 쓰면 됩니다.
전통적인 방법을 쓰면 됩니다.
원고지에 한가득 글을 썼는데, 그 한가운데에 문장을 삭제하거나 추가해야 할 때는 어떻게 하나요?
취소선으로 찍 긋거나 여백에 써서 추가하는 꼼수도 있지만 컴퓨터 메모리에 그런 짓을 할 수는 없으니까요.
======
별 수 없이 새 원고지를 가져와 통째로 옮겨 써야 합니다. 구체적으론...
1. 수정해야 할 지점까지의 글을 고스란히 옮겨 씁니다.
2. 수정해야 할 부분을 적절히 고쳐 씁니다.
3. 끝까지 마찬가지로 옮겨 씁니다.
그러고 나서 원본 원고지는 버리고, 새 원고지를 그 자리에 놓으면 끝이지요.
======
이걸 알아서 다 해 주는 함수 같은 것은 없습니다. 직접 조립해야 합니다.
다행히도 문제가 별로 어렵지 않으니까요.
원문 텍스트를 한 줄씩 읽어서, 치환해야 하면 치환해서 출력하고, 아니면 그냥 출력하고, 그 뿐이죠.
그런 함수는 없습니다.
수정할 대상이 되는 레코드의 data type 이나 length, 이러한 특성을 특정해야 하므로 이런경우 대부분 커스텀방식으로 함수를 만들어 실행합니다.
컴퓨터로 프로그래밍을 하는 직업을 갖게 되면 (혹은 학교에서 전산과목 숙제할때) 처음에 흔하게 하는 프로그래밍 작업입니다.
(1) 원본을 읽어들인다.
(2) 수정해야 할 레코드까지 읽고 거기까지 새로운 화일에 쓴다.
(3) 수정하거나 삭제할 내용을 그 새로운 화일에 반영한다. (수정, 삭제 혹은 새 레코드 삽입)
(4) 원본으로부터 그 다음 레코드에서 다시 (2) & (3) 의 출력화일에 마저 쓴다.
(5) 확인: 원본화일과 새로운 화일을 diff 등의 유틸리티 프로그램으로 확인하여 마무리 한다.
뭐, 대충 이런식일겁니다.
위의 단계는 일반적인 sequential file 을 가정한것임을 잘 아실겁니다.
여의도자바
그러니까
원본을 1에 넣고, 수정 기점까지 읽은후 거기까지 2에다가 넣고, 수정할 내용을 3에다가 넣은후
3을 2에다 넣고 기점 이후의 1에 담긴 내용을 다시 넣으란 말씀이신건가요?
머리가 핑핑 도네요 ㅠㅠ 답변 감사합니다
위의 다른 분들 설명이 자세하지만, 사용할 함수를
위의 다른 분들 설명이 자세하지만, 사용할 함수를 물으시니..
아래와 같이 fgets()/fputs() 반복하시면 되지 않나요.
컴파일되는 코드는 아니고 대충 적은 것입니다.
오류확인/처리 코드도 보충해야 하고요.
앗..늦게봤습니다
이 코드를 고쳐서 만져봤는데 기존에 담겨있어야 할 전체 내용이 사라집니다 시무룩...
조만간 기말 프로젝트도 하나 해야하는데...자신감이 없어지네요 ㅠㅠ
그래도 도와주셔서 감사합니다.
중간 중간에 printf 함수를 넣어보셔요.
뭔가 예상한 결과가 안 나올 때는 중간 중간 printf 함수를 넣어보셔요.
파일에 쓸 내용을 화면에 출력해보는 거죠.
그리고 아! 내가 생각한 내용이 아니라 다르게 써지는구나.
어떻게 하면 되지?
이런 고민을 하면서 실력이 느는 겁니다.
포기하지 말고 성공하시길.
아. 안 된다고만 하면 도와드리기 어려워요.
안 되면 안 되는 대로 에러나는 소스라도 보여주셔요.
세벌 https://sebuls.blogspot.kr/
현재 조언해주신대로 printf를 넣어가며 점검했는데
현재 조언해주신대로 printf를 넣어가며 점검했는데 strstr함수가 있는 곳까지만 작동하고 해당 라인이 삭제되지 않습니다.
여기다 살을 붙여서 수정까지 해야하는데...갑갑하네요 ㅠㅠ
단순화시키는것도 한 방법입니다.
이름을 한글로 하지 않은 차이만 있을뿐입니다.
여의도자바
이 코드가 제일 기본이 되었습니다.
정말 처음에 개념도 모르겠고 막막했는데 위에서 긴 코드들에 당황하다가
후...며칠동안 노려보면서 이 코드를 기반으로 기능을 쌓아올리니 되네요.
지금 기능 한개 말고 다 구현했습니다. 정말 감사합니다!
도움이 되었다니 다행입니다.
이쪽 '바닥' 에서 하는 말이 있습니다. (참, 미국에서요.)
지금은 은퇴한 저의 매니저 래리 아저씨가 자주 하던 말이기도 합니다.
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) 필요?
확인해 보세요.
아래와 같이 if if else if 가 연속되는
아래와 같이 if if else if 가 연속되는 곳에 { } 를 두 곳에 넣어주면 될겁니다. 처음에 fputs() 반환값 검사 생략했다가 너무 심한 것 같아서 if를 끼워 수정하면서 오류를 만들었습니다. 어설프게 돕다가 혼란을 일으켰네요.
대체문자열 끝에 '\n'도 추가해야 하는군요.
아래와 같이 샘플 만들어 실행해보니 동작하는 것 같습니다. 이 코드는 변경된 사본 만드는것까지만 하므로, 성공시 원본파일을 사본으로 대체하는 코드는 짜넣으셔야 합니다. (그리고, modifycopy() 내에서 fopen() 오류검사도 해야 하고, return -1로 대충 처리한 부분도 fin/fout에 대해 fclose()를 실행하고 리턴하도록 보완해야 합니다)
후..
일부터 90까지 다 떠먹여주셨는데 자꾸 질문드려서 죄송합니다.
정수형으로 반환하셨던데 함수를 재정의한다고 오류가 나서 보이드 형으로 수정하였습니다.
비쥬얼 스튜디오로 하고있는데 수정할 내용을 입력하려고 했지만 아무 반응이 없이 종료됩니다.
리눅스로 하신것같아서 리눅스로도 해봤는데 segemtation fault(core dumped)오류가 떠서 수정하니 오히려 더 망친것같습니다.
삭제할 줄을 만나면 미리 정해진 내용으로 교체하는
삭제할 줄을 만나면 미리 정해진 내용으로 교체하는 대신, printf()/scanf() 반복하며 새 줄 내용 입력을 받아 출력하는 의도라 짐작하고 손댔습니다.
{ }로 조건문 적용범위를 명확히 한 것과, 불필요해보이는 코드 제거한 것이 전부입니다. 손 댄 부분만 올립니다. 하루이틀쯤 시간을 내어 C언어 입문서를 앞부분만이라도 다시 한 번 읽어보시면 도움될 것입니다. 지금은 기본적인 실수 몇 개 때문에 시간상 큰 손해를 보시는 상황입니다. 이번 과제 수행중 잘 느끼셨을테니 꼭 읽어보시길..
지금 컴 환경이 열악하고 비주얼스튜디오도 없어서, cygwin64 설치해서 mingw로 테스트했습니다. UTF-8로 저장하고 빌드하면 도스창에서 실행시 깨지고 소스/데이터 모두 EUC-KR로 저장/빌드하니까 되네요. 아마 비주얼스튜디오에서도 되겠죠.
이렇게 적어주신 기본 골자 덕분에 해결되었습니다!
다른 분들이 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입니다.컴파일러들이 나름대로 어떻게 동작하는지 명시해 놓은 게 있긴 한데, 아마 원하는 동작이 아닐걸요.
결국 이렇게 됬네요 ㅎㅎ;;
origin, temp를 만들어서
원본을 버퍼에 저장후, 검색하여 기능 구현, temp에 옮겨쓰고
각각 종료후 다시 반대로 불러와서 temp를 origin에 저장. 해보니 되네요 이게..
말만 들으니 이해가 안됬는데 막상 부딛혀보니 됩니다 이게(?)
음 ..
strstr() 은 substring 을 검색해 주는 거라, 지금 같은 상황에서는 정확한 검색 결과를 보장하기 어렵습니다.
각 구분자를 기준으로 명확히 검색할 수 있는 방법을 사용하셔야 합니다.
명색이 주제가 text db 인 만큼, 필드 구분자, 레코드 구분자를 명확히 정의하고, 규칙에 맞게 각각의 데이터를 파싱하는게 나을 것 같습니다.
원본 파일을 직접적으로 수정하는 것은, 이미 많은 분들이 언급하셨듯이 바람직하지 않습니다.
중간에 문제가 생겨 중단되었을 때, 원본 파일의 무결성을 지킬 방법이 없기 때문이죠.
그래서 전통적으로 사본을 만든 후에, 원본에 덮어 쓰는 방식을 사용합니다.
사본을 만들 때, 레코드를 추가하거나, 레코드 하나를 건너 뛰거나(삭제), 레코드 내용을 바꿔서 저장하는 식으로 해서, 원본 파일을 편집한 것과 동일한 효과를 얻는 거죠.
이 과제에서 기대하는 바가, 아마도 위 두 가지가 아닐까 생각합니다.
이 과제가 재밌는게, unix 시스템에서 가장 오래된 부분이면서 지금도 사용되고 있는 부분과 같은 기능을 요구하고 있습니다.
바로 /etc/passwd 나 /etc/shadow 와 같은 파일들이죠.
passwd 파일을 예로 들면, setpwent(), getpwent(), endpwent() 와 같이 레코드를 조회할 수 있는 함수를 제공하고 있고, putpwent() 와 같이 레코드를 파일에 저장할 수 있는 함수도 제공하고 있습니다.
이를 이용해서 getpwnam() 이나 getpwuid() 등과 같이, 특정 필드로 검색할 수 있는 함수도 이미 만들어 제공하고 있습니다.
뭔가 답이 안 보일 때에는, 남이 먼저 만들어 놓은걸 분석해 보는 것도 도움이 됩니다.
간단히 위 함수들을 참고해서 최대한 간단하게 구현해 보면, 다음과 같은 형태가 될 수 있을겁니다.
(file lock, tmp file, perm, owner 등과 같이 추가로 고려해야 할 부분 많음)
기본적으로 레코드를 조회할 수 있는 함수들이 만들어졌으니, 이제 하나씩 다른 파일에 옮겨 쓰면서 원하는 대로 데이터를 조작하면 되는거죠.
아래 몇 가지 샘플 코드 덧붙입니다. 로직만 참고하세요.
되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』
우와..어질어질 하네요 ㅎㅎ;;
관심과 정성을 이렇게 쏟아주져서 감사합니다.
사실 적어주신 이 코드들은 봐도 저에게 너무 어려워서 이해하지못했는데,
결국 제출에 대한 욕심을 버리고 찬찬히 살펴보니 조금 눈에 들어오긴 하네요 ㅎㅎ..
덕분에 과제 9할은 완성했습니다. 내일까지 제출인데 예외 처리가 추가가 안되서 혹시나 있나 살펴보러왔는데 이렇게 긴 장문을 ...고수분들 감사합니다
이게 이렇게 길어질 내용이었나 싶습니다.
답변 다신 분들은 충분히 설명하신거지만 질문자님이 너무 어렵게 접근하는 법만 배우시는게 아닌가 싶습니다.
죄대한 단순하게 그냥 파일을 그대로 읽어서 다른 파일에 쓰면서 수정하세요
이름앞에 No 를 두자리로 부여 한다거한 하는식의 약간의 수정도 어차피 같은 방식이니까요
------------------------------------------------------------
ProgrammingHolic
가끔, 아무 예고도 없이, 딱히 어렵거나 특별한 점
가끔, 아무 예고도 없이, 딱히 어렵거나 특별한 점 없는 과제성 질문에 답변이 쏟아지는 경우가 있습니다.
물론 그런 행운을 누리려면 잘 정의된, 숙련자라면 심심풀이 삼아 10~20분이면 답할 수 있는 그런 질문이어야 하죠.
사람들이 이번에 유난히 심심했나봐요.
정보가 막 쏟아지니까 이게 좋으면서도 힘드네요 ㅎㅎ;;
다들 많은 답변을 해주셨는데 제가 정신이 없어서 받아들일 준비가 안되어있었습니다.
어느시점부터 한 고수분이 불러주신 코드를 유심히...며칠동안 살펴보니 해결법이 나와버렸네요.
분에 넘치는 관심들이였는데 떠먹여줘도 받아먹지를 못하는 며칠전의 나 ㅠㅠ
하하 - 그랬나 봅니다.
이곳이 보통은 이렇게까지는 친절하게, 끝까지 살펴주지는 않는데 말이죠. :)
여의도자바
댓글 달기