ページ 11

promptコマンドの実装について

Posted: 2011年9月02日(金) 22:49
by whity
今回は文字列処理について質問させていただきます。

僕のパソコンは管理者権限などでコマンドプロンプトが使えなかったのですが、プログラム内からならsystem()を実行できることがわかりました。そこで、次のようなプログラムを作り、コマンドプロンプトを模倣してみました。

コード:

#include "func.h"

int main(){

char buf [MAX_PATH];
char str[256], *path,*p,cmd[32],set[16],*loc;
unsigned char i;
unsigned long err;
bool flag,echo=true;

while(true){
memset(cmd, 0, sizeof(cmd)); //cmd初期化
memset(set, 0 ,sizeof(set));
memset(str, 0 ,sizeof(str));

if(echo){
	GetCurrentDirectory(MAX_PATH,buf);
	printf("\n%s>",buf);
}

fgets(str, sizeof(str), stdin);


//改行文字除去処理
p = strchr( str, '\n' );
/* 改行文字があった場合 */
if ( p != NULL )*p = '\0';

if(strlen(str)==0) continue;

Trim(str);//前と後ろのスペース除去
i=0;

if(strchr(str,' ')!=NULL){//空白が入っている場合(引数ありの場合)
	while (str[i]!=' ') { i++; } //空白がくるまでカウンタを進める
	strncpy(cmd,str,i);
	while (str[i]==' ') { i++; } //次の空白以外の文字までカウンタを進める
	path = &str[i];

	i=0;//カウンタ初期化

	if(!strpcmp(cmd,"exit")){
	  if(!strpcmp(path,"/?")){system("exit /?");
	  }else{
	   break;
          }
	}  
	if(!strpcmp(cmd,"cd") || !strpcmp(cmd,"chdir")){
		if(!strpcmp(path,"/?")){
		  system("cd /?");
		}else{
		  flag=SetCurrentDirectory(path);
		  if(!flag){
		    err= GetLastError();
		    fnDisplayError(err);
		  }
		}
	}else if(!strptncmp(cmd,".:")){
		flag=SetCurrentDirectory(cmd);
		if(!flag){
		  err= GetLastError();
		  fnDisplayError(err);
		}	
	}else if(!strpcmp(cmd,"set")){
		if(strchr(path,'=')!=NULL){
		  while(path[i]!='='){ i++; }
	  	  strncpy(set,path,i);
	  	  while (path[i]==' ') { i++; }
	  	  loc=&path[i];
		  sprintf(loc,"%s=%s",set,loc);
		  putenv(loc);
		}else{
		  system(str);
		}
		
	}else if(!strpcmp(cmd,"echo")){
		i=strlen(path);
		if(!strpcmp(path,"on"))echo=true;
		else if(!strpcmp(path,"off"))echo=false;
		else if(path[0]=='%' && path[i-1]=='%'){
		  loc=&path[1];
		  loc[i-2]='\0';
		  path=getenv(loc);
		  printf("%s\n",path);	
		}
		else printf("%s\n",path);
	}/*else if(!strpcmp(cmd,"prompt")){
		
	}*/else{           
		system(str);
	}
	
}else{//引数なしの場合
	
	if(strchr(str,'=')!=NULL){
	  while(str[i]!='='){ i++; }
	  strncpy(set,str,i);
	  while (str[i]=='=') { i++; }
	  loc=&str[i];
	}
	
	if(!strpcmp(str,"exit")) break;
	
	if(!strpcmp(str,"cd..")){
		flag=SetCurrentDirectory("..");
		if(!flag){
		    err= GetLastError();
		    fnDisplayError(err);
		}    
	}else if(!strpcmp(str,"cd..\..")){
		flag=SetCurrentDirectory("..\..");
		if(!flag){
		    err= GetLastError();
		    fnDisplayError(err);
		}
	}else if(!strptncmp(str,".:")){
		flag=SetCurrentDirectory(str);
		if(!flag){
		    err= GetLastError();
		    fnDisplayError(err);
		}  
	}else if(!strpcmp(set,"path")){
	  sprintf(str,"path=%s",loc);
	  putenv(str);
	}else if(!strpcmp(str,"echo")){
		if(echo)printf("ECHO は <ON> です。\n");
		else    printf("ECHO は <OFF> です。\n");
	}else{
		system(str);
	}
}

}	
return 0;
}

func.hは内部で必要なヘッダファイルが#includeされ、
void fnDisplayError( DWORD dwErrorMsgId );
int Trim( char * );
int strpcmp( const char* , const char* );
bool strptncmp(const char*,const char*);
のプロトタイプ宣言をしています。

fnDisplayError()は引数のエラーコードを文字列に直し、画面に出力する関数です。
Trim()は前後のスペースを削除します。
strpcmp()は大文字小文字を無視して文字列比較をする関数です。
strptncmp()は第二引数に.が入っていると任意の一文字にマッチさせることができる文字列比較関数です。

さて、本題に入りますが、cdやsetなどsystem()では使えないコマンドがあり、それを実現するため改良してきました。
そして今回、promptコマンドも使えないことがわかりました。
このコマンドの使用頻度は高いとは言えませんが、文字列処理について詳しく知りたかったので質問させていただきました。

僕が考えた実装の方法は、
(1)promptコマンドが入力されると、引数の各要素を分割し、char型ポインタ配列などにそれをコピーする。
(2)

コード:

if(echo){ 
     GetCurrentDirectory(MAX_PATH,buf);  
     printf("\n%s>",buf);
}
の部分を変更し、引数の要素が入っている配列のアドレスを受け取り、文字列を返す関数を自作し、
それを出力する。

といったものです。
(1)の「promptコマンドが入力されると」に関しては上のコードのコメント部にあるようにelse if(!strpcmp(cmd,"prompt"))でいいと思うのですが、
「各要素を分割、コピー」の部分をどうすればいいかわかりません。
promptコマンドの引数は$を使うことで特殊コードを使えるのですが、普通の文字列と区別してどうやって分割、代入するのでしょうか。
例えば、引数に"$sHello$g"という文字列が与えられたときには、配列の要素は順番に、
"$s","Hello","$g"
といったようになるようにしたいです。

(2)は特殊コードを置き換えていけばいいと思いますが、どうやって特殊コードを見分けるかわかりません。
ヒントなど教えていただけるとありがたいです。

できるだけ詳しく書くよう心がけましたが、まだ分かりにくい箇所もあるかもしれません。
また既存のコードにバグなどがあった場合は、教えていただけるとありがたいです。

ご回答よろしくお願いします。

Re: promptコマンドの実装について

Posted: 2011年9月03日(土) 05:45
by shiro4ao
とても興味深いです

(1)「各要素を分割、コピー」の部分について
strtok関数というものがあります。例えば
「hoge,piyo,fuga,」という文字列があったとすると
コンマ区切りで切り出してくれます(区切りは自由に決められます)
スレッドセーフではないのでマルチスレッドの場合は注意が必要ですが。

"$sHello$g"の場合に"Hello"部分の切り出しがstrtok一発では
できないので、構文のルールをかえるか?自力で切り出しを実装するか?
のどちらかを選ぶ形になるかと思います。

(2)cdやsetなどsystem()では使えないコマンドがあり、それを実現するため改良してきました。
system関数はcmd.exeを呼び出しているに過ぎないので操作の結果を引き継げません。
例えば、仮にc:\users\Aliceにいたときに

コード:

system("cd  c:\\users\\Bob\r\n");
system("dir\r\n");
としても、c:\users\Aliceのディレクトリ一覧がでてきます。
前のsystem()の結果が引き継げないためディレクトリ移動がなかったとこにされてしまいます。
(cdコマンドだけはべつにSetCurrentDirectory関数をつかうという方法あるかもしれませんが・・・)

最後に、実際に動くのかどうか自分の環境では確認ができないのでなんともわからないのが申し訳ないです。
コマンドプロンプトが使えないが、使う必要があるというお話ですが、
どのような手段によって使えなくなっているかによって対策が取れる場合と難しい場合があります。
単純に、コマンドプロンプトがアクセサリから消えていて、cmd.exe起動そのものは可能な場合は
プロセス間通信という手段で対応することができます。

cmd.exeの起動そのものが不可にされている場合はどうしたらいいのか・・・・

Re: promptコマンドの実装について

Posted: 2011年9月03日(土) 23:15
by whity
strtokの解説ページを見ていると、ひらめいたのですが、"$sHello$g"を"$s,Hello,$g"と加工する関数などを作れば
strtokを使えるようになるのではないでしょうか。
そこで以下の関数を作りテストしました。

コード:


char* insert(char* str,const char* substr, const int index)
{
    const int substrLen = strlen(substr);
    char* insertPos = &str[index];

    memmove(insertPos + substrLen, insertPos, strlen(insertPos) + 1);
    strncpy(insertPos, substr, substrLen);

    return str;
}
void DivElement( char* string){
	unsigned i=0;
	
	while(string[i]!='\0'){
	
	  if(string[i]=='$'){
	    if(i!=0){
	      insert(string, "," ,i);
	    }  
	    insert(string,",",i+2);
	  }
	  i++;
	}	
}

int main(){

unsigned char i=0;
char str[256],*prompt[10],*p,*tok ;

memset(str,0,sizeof(str));
printf(">");
fgets(str,sizeof(str),stdin);

//改行文字除去処理
p = strchr( str, '\n' );
/* 改行文字があった場合 */
if ( p != NULL )*p = '\0';

memset(&prompt,0 ,sizeof(prompt));


if(strchr(str,'$')!=NULL){
	DivElement(str);
	printf("分割後str:%s\n\n",str);
	
	tok=strtok(str,",");
	strcpy(prompt[i],tok);
	i++;
	
	while(1){
	printf("%s\n",tok);
	  if(tok==NULL)break;
	  tok=strtok(NULL,",");

	  strcpy(prompt[i],tok);
	  i++;
	};
	
}else{
	strcpy(prompt[i],str);
}
return 0;
}
しかし、このDivElement()でプログラムが止まってしまいます。
どこがおかしいのでしょうか。

Re: promptコマンドの実装について

Posted: 2011年9月04日(日) 11:46
by whity
どうやら無限ループしているようです。

Re: promptコマンドの実装について

Posted: 2011年9月04日(日) 12:25
by whity
無限ループの原因は無限に,が挿入されていつまでも\0にたどり着けないからだとわかりました。
そこでfor文にしたら無限ループはなくなったのですが、やはり何度も,が挿入され思いどうりの結果になりません。

コード:

void DivElement( char* string){
	const unsigned len=strlen(string)+1;
	
	for(unsigned i=0;i<len;i++){
	  
	  if(string[i]=='$'){
	    if(i!=0){
	      insert(string,",",i);
	    }
	    if(string[i+2]!='\0'){  
	      insert(string,",",i+2);
	    }
	  }
	}
	
}
どのようにしたらいいでしょうか?

Re: promptコマンドの実装について

Posted: 2011年9月04日(日) 15:01
by softya(ソフト屋)
最新の動くコード(ヘッダ)とコメント書いてもらって良いですか?
あと処理してる文字列を加工するのは危険なので、普通は加工前と加工後は分けるべきです。

Re: promptコマンドの実装について

Posted: 2011年9月04日(日) 20:06
by whity
softya(ソフト屋) さんが書きました:最新の動くコード(ヘッダ)とコメント書いてもらって良いですか?
NO.3で載せたコードは、テスト用です。本体には手を加えていませんので、最新の動くコードはNO.1のコードと同じです。

加工後の文字列は分けておくべきとのことですので、テスト用のコードを変更しておきました。

コード:

#include<string.h>
#include<stdio.h>
#include<stdlib.h>

char* DivElement(const char*);

int main(){

unsigned char i=0;
char str[256],*prompt[10],*p,*tok,*edited ;

memset(str,0,sizeof(str));
printf(">");
fgets(str,sizeof(str),stdin);

//改行文字除去処理
p = strchr( str, '\n' );
/* 改行文字があった場合 */
if ( p != NULL )*p = '\0';

memset(&prompt,0 ,sizeof(prompt));


if(strchr(str,'$')!=NULL){
	strcpy(edited,DivElement(str));

	
	tok=strtok(edited,",");
	strcpy(prompt[i],tok);
	i++;
	
	while(1){
	
	  
	  tok=strtok(NULL,",");
	  if(tok==NULL)break;
	  
	  strcpy(prompt[i],tok);
	  i++;
	}
	
}else{
	strcpy(prompt[i],str);
}


return 0;
}

char* insert(char* str,const char* substr, int index)
{
    const int substrLen = strlen(substr);
    char* insertPos = &str[index];

    memmove(insertPos + substrLen, insertPos, strlen(insertPos) + 1);
    strncpy(insertPos, substr, substrLen);

    return str;
}


char* DivElement(const char* string){
    const unsigned len=strlen(string)+1;
    char *str;
    strcpy(str,string);
    
    for(unsigned i=0;i<len;i++){
      
      if(string[i]=='$'){
        if(i!=0){
          insert(str,",",i);
        }
        if(string[i+2]!='\0'){  
          insert(str,",",i+2);
        }
      }
    }
    return str;
}

Re: promptコマンドの実装について

Posted: 2011年9月04日(日) 21:51
by softya(ソフト屋)
promptなのですが、文字列ポインタの配列として定義されています。
char *prompt[10];
ですね。
これ、文字列を格納するメモリを確保せずにいきなりコピーしていますので動きません。
まず、文字列を格納する先のメモリのポインタ値をどうするか考えてください。

Re: promptコマンドの実装について

Posted: 2011年9月04日(日) 23:19
by whity
では、一番大きそうな要素の長さを見極めて、動的確保する、といったことをしたら、異常終了はしなくなりました。いかに変更点を示します。

コード:

if(strchr(str,'$')!=NULL){
    strcpy(edited,DivElement(str));
 
    tok=strtok(edited,",");
    strcpy(prompt[i],tok);
    i++;
を、

コード:

unsigned len;

//中略

if(strchr(str,'$')!=NULL){

	edited=(char*) malloc(sizeof(char) * strlen(str)+1);
	
	len= (strlen(str) - findchr(str,'$')*2);
	if(!len) len=2;
	
	printf("%d\n",len);
	
	for(int j=0;j<10;j++){
	  prompt[j]=(char*) malloc(sizeof(char) * len +1 );
	}
	
	strcpy(edited,DivElement(str));
    i++;
に変更。

コード:

char* DivElement(const char* string){
    const unsigned len=strlen(string)+1;
    char *str;
    strcpy(str,string);
    
    for(unsigned i=0;i<len;i++){
      
      if(string[i]=='$'){
        if(i!=0){
          insert(str,",",i);
        }
        if(string[i+2]!='\0'){  
          insert(str,",",i+2);
        }
      }
    }
    return str;
}

コード:

char* DivElement(const char* string){
    const unsigned len=strlen(string)+1;
    char *str;
    str= (char*) malloc(sizeof(char) * strlen(string)+1);
    strcpy(str,string);
    
    for(unsigned i=0;i<len;i++){
      
      if(string[i]=='$'){
        if(i!=0){
          insert(str,",",i);
        }
        if(string[i+2]!='\0'){  
          insert(str,",",i+2);
        }
      }
    }
    return str;
}
に変更。
関数findchrを追加。

コード:

//stringにchrは何個あるか?
unsigned findchr(const char* string,char chr){

unsigned i=0,j=0;
while(string[i]!='\0'){
	if(string[i]==chr) j++;
	i++;
}
return j;
}
しかし、このように変更した後で、"$sHello$g"という文字列を入力すると、
prompt[0]には"$s"、
prompt[1]には"Hell"、
prompt[2]には"o$g"
となってしまいました。
DivElementにバグがあるようです。いろいろ考えましたが、よくわかりません。
どなたかヒントをください。お願いします。

Re: promptコマンドの実装について

Posted: 2011年9月05日(月) 00:07
by softya(ソフト屋)
こちらと同等なプログラムですか?
こちらではハングしますが、いきなりstrtok(NULL,",");で始まっているのが原因ですが、たぶんコピペの問題だと思います。
部分コピペは無理がある(間違いやすい)ので全体を貼ってください。このままだと無駄なやり取りが増えるだけですよ。

それとDivElementでやりたいことの意味が良く分かっていないのでコメントと説明をお願いします。
私の想像とやっていることが違いすぎて、どういう意図でやっているのかお伺いしたいです。

コード:

#include<string.h>
#include<stdio.h>
#include<stdlib.h>

char* DivElement(const char*);
unsigned findchr(const char* string,char chr);

int main(){

	unsigned char i=0;
	char str[256],*prompt[10],*p,*tok,*edited ;
	unsigned len;

	memset(str,0,sizeof(str));
	printf(">");
	fgets(str,sizeof(str),stdin);

	//改行文字除去処理
	p = strchr( str, '\n' );
	/* 改行文字があった場合 */
	if ( p != NULL )*p = '\0';

	memset(&prompt,0 ,sizeof(prompt));



	if(strchr(str,'$')!=NULL){

		edited=(char*) malloc(sizeof(char) * strlen(str)+1);

		len= (strlen(str) - findchr(str,'$')*2);
		if(!len) len=2;

		printf("%d\n",len);

		for(int j=0;j<10;j++){
			prompt[j]=(char*) malloc(sizeof(char) * len +1 );
		}

		strcpy(edited,DivElement(str));
		i++;

		while(1){


			tok=strtok(NULL,",");
			if(tok==NULL)break;

			strcpy(prompt[i],tok);
			i++;
		}

	}else{
		strcpy(prompt[i],str);
	}


	return 0;
}

char* insert(char* str,const char* substr, int index)
{
	const int substrLen = strlen(substr);
	char* insertPos = &str[index];

	memmove(insertPos + substrLen, insertPos, strlen(insertPos) + 1);
	strncpy(insertPos, substr, substrLen);

	return str;
}


char* DivElement(const char* string){
	const unsigned len=strlen(string)+1;
	char *str;
	str= (char*) malloc(sizeof(char) * strlen(string)+1);
	strcpy(str,string);

	for(unsigned i=0;i<len;i++){

		if(string[i]=='$'){
			if(i!=0){
				insert(str,",",i);
			}
			if(string[i+2]!='\0'){  
				insert(str,",",i+2);
			}
		}
	}
	return str;
}

//stringにchrは何個あるか?
unsigned findchr(const char* string,char chr){

	unsigned i=0,j=0;
	while(string[i]!='\0'){
		if(string[i]==chr) j++;
		i++;
	}
	return j;
}

Re: promptコマンドの実装について

Posted: 2011年9月05日(月) 18:20
by whity
ソースコード全体を貼っておきます。

コード:

#include<string.h>
#include<stdio.h>
#include<stdlib.h>

char* DivElement(const char*);
unsigned findchr(const char*,char);

int main(){

unsigned char i=0;
char str[256],*prompt[10],*p,*tok,*edited ;
unsigned len;

memset(str,0,sizeof(str));
printf(">");
fgets(str,sizeof(str),stdin);

//改行文字除去処理
p = strchr( str, '\n' );
/* 改行文字があった場合 */
if ( p != NULL )*p = '\0';

memset(&prompt,0 ,sizeof(prompt));


if(strchr(str,'$')!=NULL){

	edited=(char*) malloc(sizeof(char) * strlen(str)+1);
	
	len= (strlen(str) - findchr(str,'$')*2);
	if(!len) len=2;
	
	for(int j=0;j<10;j++){
	  prompt[j]=(char*) malloc(sizeof(char) * len +1 );
	}
	
	strcpy(edited,DivElement(str));
	
	tok=strtok(edited,",");
	strcpy(prompt[i],tok);
	i++;
	
	while(1){
	  
	  tok=strtok(NULL,",");
	  if(tok==NULL)break;
	 
	  strcpy(prompt[i],tok);
	   i++;
	}	
	
}else{
	strcpy(prompt[i],str);
}


for(int f=0;f<10;f++){
printf("prompt[%d]%s\n",f,prompt[f]);
}
return 0;
}

char* insert(char* str,const char* substr, int index)
{
    const int substrLen = strlen(substr);
    char* insertPos = &str[index];

    memmove(insertPos + substrLen, insertPos, strlen(insertPos) + 1);
    strncpy(insertPos, substr, substrLen);

    return str;
}

//文字列に,を挿入して要素を分割します
//例
//"$p$g$g" -> "$p,$g,$g" 
//"$sHello$g" -> "$s,Hello,$g"
//"Hello$s$sHello$p$g" -> "Hello,$s,Hello,$s,$p,$g" 
char* DivElement(const char* string){
    const unsigned len=strlen(string)+1;
    char *str;
    str= (char*) malloc(sizeof(char) * strlen(string)+1);
    strcpy(str,string);
    
    for(unsigned i=0;i<len;i++){
      
      if(string[i]=='$'){
        if(i!=0){
          insert(str,",",i);
        }
        if(string[i+2]!='\0'){  
          insert(str,",",i+2);
        }
      }
    }
    return str;
}


//stringにchrは何個あるか?
unsigned findchr(const char* string,char chr){

unsigned i=0,j=0;
while(string[i]!='\0'){
	if(string[i]==chr) j++;
	i++;
}
return j;
}


DivElement関数は特殊コードを使用したpromptコマンドの引数をstrtokで分割するために,を挿入し
要素を配列にコピーできるようにするための関数です。
すいません。説明不足でした。

Re: promptコマンドの実装について

Posted: 2011年9月05日(月) 19:00
by softya(ソフト屋)
すいません。
//"Hello$s$sHello$p$g" -> "Hello,$s,Hello,$s,$p,$g" 
ここだけルールが違うんですが、ひっくり返すんですか?

それと、このルールだと結果の文字列の長さが元よりも大幅に長くなると思いますが、 malloc(sizeof(char) * strlen(string)+1);ではまずくないですか?
↓ 再確認のためにも丁寧なコメントをお願いします。

コード:

char* DivElement(const char* string){
    const unsigned len=strlen(string)+1;
    char *str;
    str= (char*) malloc(sizeof(char) * strlen(string)+1);
    strcpy(str,string);
    
    for(unsigned i=0;i<len;i++){
      
      if(string[i]=='$'){
        if(i!=0){
          insert(str,",",i);
        }
        if(string[i+2]!='\0'){  
          insert(str,",",i+2);
        }
      }
    }
    return str;
}
【追記】
あとワザワザここで","を挿入するなら、ここで配列に分けたほうが手っ取り早いです。

Re: promptコマンドの実装について

Posted: 2011年9月05日(月) 20:49
by whity
softya(ソフト屋) さんが書きました://"Hello$s$sHello$p$g" -> "Hello,$s,Hello,$s,$p,$g" 
ここだけルールが違うんですが、ひっくり返すんですか?
本当に失礼しました。
"Hello$s$sHello$p$g" -> "Hello,$s,$s,Hello,$p,$g" 
の間違いです。
softya(ソフト屋) さんが書きました:それと、このルールだと結果の文字列の長さが元よりも大幅に長くなると思いますが、 malloc(sizeof(char) * strlen(string)+1);ではまずくないですか?
いわれてみればそうですね...
malloc(sizeof(char) * strlen(string)+要素の数);
とすればうまくいきそうですが、どうやって要素の数をカウントすればいいのでしょう。
わからないことだらけです。
softya(ソフト屋) さんが書きました:【追記】
あとワザワザここで","を挿入するなら、ここで配列に分けたほうが手っ取り早いです。
どうやって配列に分けるのでしょうか?やり方を教えていただけると助かります。

Re: promptコマンドの実装について

Posted: 2011年9月05日(月) 22:18
by softya(ソフト屋)
whity さんが書きました:いわれてみればそうですね...
malloc(sizeof(char) * strlen(string)+要素の数);
とすればうまくいきそうですが、どうやって要素の数をカウントすればいいのでしょう。
わからないことだらけです。
whity さんが書きました:って配列に分けるのでしょうか?やり方を教えていただけると助かります。
配列に分けるのなら、要素の数を先にカウントする事は考えなくて良くなります。
とりあえず、$マークが出たら分けるんですよね。
(1)文字列起点(str_start)を0とする。
(2)格納する配列(prompt)の配列番号(index)を0にする。
(3)文字列長(str_lng)を0にする。
(4)$マークを見つけるまで、文字列長(str_lng)を加算する。
(5)$マークなら、str_lngが有効であれば、それまでの文字列(str_start起点)を文字列長(str_lng)+1でmallocしたメモリにコピーする。格納する配列(prompt)の配列番号(index)の位置にmallocしたアドレスを記録。配列番号(index)を++。
(6)格納する配列(prompt)の配列番号(index)+1の位置にmalloc(3)して、今の文字位置から2バイトコピーする。最後に'\0'を書き加える。ただし元の文字列が2バイトに満たない場合は$だけコピーすること。配列番号(index)を++。
(7)文字列起点(str_start)を現在の文字位置とする。
(8)以上を元の文字列が無くなるまで続ける。まだある時は(3)にもどる。

一部ガードは手を抜いているので自分で付け加えてください。

Re: promptコマンドの実装について

Posted: 2011年9月06日(火) 22:52
by whity
すいません。いくつか質問があります。

・str_startのデータ型は、char*ですか?
・(5)に「str_lngが有効なら」とありますが、具体的にどのようなことを判定するのでしょうか?
・(7)の「文字列起点(str_start)を現在の文字位置とする。」は、どうやって実現するのでしょうか?

僕はまだプログラミングを初めて1年もたたないヒヨっこなので、わかりやすく教えていただけると本当に助かります。

Re: promptコマンドの実装について

Posted: 2011年9月07日(水) 00:35
by softya(ソフト屋)
whity さんが書きました:すいません。いくつか質問があります。

(a)・str_startのデータ型は、char*ですか?
(b)・(5)に「str_lngが有効なら」とありますが、具体的にどのようなことを判定するのでしょうか?
(c)・(7)の「文字列起点(str_start)を現在の文字位置とする。」は、どうやって実現するのでしょうか?

僕はまだプログラミングを初めて1年もたたないヒヨっこなので、わかりやすく教えていただけると本当に助かります。
(a)intのつもりでした。
(b)str_lng>0ってことですね。
(c)処理した文字の位置は(4)と(6)で変わりますから、処理した文字の長さを計算しておけば良いと思います。

Re: promptコマンドの実装について

Posted: 2011年9月07日(水) 17:28
by whity
まだテストはしていないのですが、一応関数らしきものは完成しました。
ただこの関数の内容はsoftyaさんが書かれたそれとは違うような気がしてなりません。
この関数の内容を修正してもらえませんか?

コード:

int DivElement(const char* string,char* prompt[]){
   int str_start=0;
   int index=0;
   int str_lng;
   unsigned manage=0;//処理済み文字の数
   char* p;

   if(strchr(string,'$')==NULL)return -1;
   
   while(manage<strlen(string)){
      str_lng=0;
      
      while(string[str_lng]){ str_lng++; manage++;}
   
      if(str_lng>0){
        p=(char*)malloc(str_lng+1);
        prompt[index]= &p[str_start];
        index++;
      }
   
      prompt[index+1]=(char*)malloc(3);
      if(strlen(string)>=2){
         strncpy(prompt[index+1],&string[str_lng],2);
         manage+=2;
      }else{
         strcpy(prompt[index+1],"$");
         manage++;
      }
      strcat(prompt[index+1],"\0");
      index++;
   
      str_start=manage;
   }   
   return 0;
}

僕はsoftyaさんが思っていた意味を取り違えているかもしれません。
ヒヨっこに説明するのは難しいとは思いますが、詳しく説明していただけるとありがたいです。

Re: promptコマンドの実装について

Posted: 2011年9月07日(水) 17:55
by softya(ソフト屋)
まず、テストを行って出来る範囲でバグを取ってください。
バグを取れないなら、私のアルゴリズムが理解出来ていない可能性があります。

あとコメントを私の書いた内容+αでソースに書いてください。
コメントは命令文の後ろか、長すぎる時は一行前にお願いします。

これ↓の事です。
(1)文字列起点(str_start)を0とする。
(2)格納する配列(prompt)の配列番号(index)を0にする。
(3)文字列長(str_lng)を0にする。
(4)$マークを見つけるまで、文字列長(str_lng)を加算する。
(5)$マークなら、str_lngが有効であれば、それまでの文字列(str_start起点)を文字列長(str_lng)+1でmallocしたメモリにコピーする。格納する配列(prompt)の配列番号(index)の位置にmallocしたアドレスを記録。配列番号(index)を++。
(6)格納する配列(prompt)の配列番号(index)+1の位置にmalloc(3)して、今の文字位置から2バイトコピーする。最後に'\0'を書き加える。ただし元の文字列が2バイトに満たない場合は$だけコピーすること。配列番号(index)を++。
(7)文字列起点(str_start)を現在の文字位置とする。
(8)以上を元の文字列が無くなるまで続ける。まだある時は(3)にもどる。
【誤字修正】

Re: promptコマンドの実装について

Posted: 2011年9月07日(水) 21:14
by whity
softya(ソフト屋) さんが書きました:あとコメントを私の書いた内容+αでソースに書いてください。
失礼しました。これからは丁寧にコメントをつけ投稿したいと思います。

このプログラムの内容はデバッガ動かしながらなんとなく理解しできる限りバグを取り除いたのですがまだ取り除けないバグがあるようです。以下に編集後のソースを示します。

コード:

int DivElement(const char* string,char* prompt[]){
   int str_start=0; //(1)文字列起点(str_start)を0とする。
   int index=0;     //(2)格納する配列(prompt)の配列番号(index)を0にする。
   int str_lng;
   unsigned manage=0;//処理済み文字の数
   char* p;

   if(strchr(string,'$')==NULL)return -1; //'$'がないとき
   
  do{
      str_lng=0; //(3)文字列長(str_lng)を0にする。
      
      while(string[str_lng]!='$'){ 
     str_lng++;   //(4)$マークを見つけるまで、文字列長(str_lng)を加算する。
     manage++;
   }
       //(5)$マークなら、str_lngが有効であれば、それまでの文字列(str_start起点)を文字列長(str_lng)+1で
      //mallocしたメモリにコピーする。
      if(str_lng>0){
        p=(char*)malloc(str_lng+1);
        prompt[index]= &p[str_start];
        index++;
      }
   //(6)格納する配列(prompt)の配列番号(index)+1の位置にmalloc(3)して、
      prompt[index+1]=(char*)malloc(3);
      if(strlen(string)>=2){
         strncpy(prompt[index+1],&string[str_lng],2); //今の文字位置から2バイトコピーする。
         manage+=2;
      }else{//ただし元の文字列が2バイトに満たない場合は$だけコピーすること。
         strcpy(prompt[index+1],"$");
         manage++;
      }
      
      prompt[index+1][strlen(prompt[index+1])+1]='\0';//最後に'\0'を書き加える。
      index++; //配列番号(index)を++。
   
      str_start=manage;//(7)文字列起点(str_start)を現在の文字位置とする。
      
   }while(manage<strlen(string));//(8)以上を元の文字列が無くなるまで続ける。まだある時は(3)にもどる。
      
   return 0;
}
質問なのですが、(6)でどうしてindex+1をするのでしょうか?この関数に"$p$g$g"を渡すと、
prompt[0]はnull,prompt[1]は$p@,prompt[2]は$p@,prompt[3]は$p@(それ以降はすべてnull)
となってしまいました。
ためしに、index+1をindexに変えると、
prompt[0]は$p@,prompt[1]は$p@,prompt[2]は$p@(それ以降はすべてnull)
となりました。

希望では順番に$p,$g,$gとなってほしいのですが、どうして$p@となってしまうのでしょうか。
改善点を指摘してくれるとありがたいです。

Re: promptコマンドの実装について

Posted: 2011年9月07日(水) 21:30
by softya(ソフト屋)
ごめんなさい(6)は書き間違いですね。
途中でロジックを変更したときの修正もれでした。

(6)格納する配列(prompt)の配列番号(index)の位置にmalloc(3)して、今の文字位置から2バイトコピーする。最後に'\0'を書き加える。ただし元の文字列が2バイトに満たない場合は$だけコピーすること。配列番号(index)を++。

これでOKです。

あと怪しい所は、
(5) prompt[index]= &p[str_start]; ← せっかくmallocしたメモリを使っていません。

(6) strlen(string)>=2は残りの長さなのでmanageも使わないといけません。
その他ここには怪しい所が沢山あります。
・[index+1]は私の間違いなので、[index]で。
・「strncpy(prompt[index+1],&string[str_lng],2);」 は、転送元文字列の起点が間違っています。
・「prompt[index+1][strlen(prompt[index+1])+1]='\0';//最後に'\0'を書き加える。」は終端に'\0'は、strncpyの後に[2]の位置に代入するだけで良いです。

とりあえず、Hello$s$sHello$p$gでまず正常に分割されることを目指してみてください。

Re: promptコマンドの実装について

Posted: 2011年9月15日(木) 17:07
by whity
返信が遅れてしまってすいません。
最近忙しかったため、返信ができませんでした。

とりあえず下記のようなプログラムを作ったところ、"$s$g$g"は正常に分割されました。

コード:

int DivElement(const char* string,char* prompt[]){
   int str_start=0; //(1)文字列起点(str_start)を0とする。
   int index=0;     //(2)格納する配列(prompt)の配列番号(index)を0にする。
   int str_lng;
   unsigned manage=0;//処理済み文字の数
  
 
   if(strchr(string,'$')==NULL)return -1; //'$'がないとき
   
  do{
      str_lng=0; //(3)文字列長(str_lng)を0にする。
      
      while(string[str_lng]!='$'){ 
        str_lng++;   //(4)$マークを見つけるまで、文字列長(str_lng)を加算する。
        manage++;
      }
       //(5)$マークなら、str_lngが有効であれば、それまでの文字列(str_start起点)を文字列長(str_lng)+1で
      //mallocしたメモリにコピーする。
      if(str_lng>0){
	    prompt[index]=(char*)malloc(str_lng+1);
	    strncpy(prompt[index],&string[str_start],str_lng); 
	    prompt[index][str_lng]='\0';
        index++;
      }
   //(6)格納する配列(prompt)の配列番号(index)の位置にmalloc(3)して、
      prompt[index]=(char*)malloc(3);
      if(strlen(string)-manage>=2){
         strncpy(prompt[index],&string[manage],2); //今の文字位置から2バイトコピーする。
	 
	     prompt[index][2]='\0';
	 
         manage+=2;
      }else{//ただし元の文字列が2バイトに満たない場合は$だけコピーすること。
         strcpy(prompt[index],"$");
	 
	     prompt[index][2]='\0';
	 
         manage++;
      }
      
      index++; //配列番号(index)を++。
   
      str_start=manage;//(7)文字列起点(str_start)を現在の文字位置とする。
      
   }while(manage<strlen(string));//(8)以上を元の文字列が無くなるまで続ける。まだある時は(3)にもどる。
      
   return 0;
}
しかし"Hello$s$s$g"なんかだと
"Hello","$s","$s$g","€€€",(null)...
というように分割されてしまいます。

"$sHello$g"では
"$s","He","ll","o$","$",(null)...
となってしまいます。

どうやらコピーするところで間違えているようですが、それをどうやって直せばいいかわかりません。
どのように改善すればうまくいくでしょうか?

Re: promptコマンドの実装について

Posted: 2011年9月15日(木) 18:49
by softya(ソフト屋)
前のプログラムをロストしたみたいですので、申し訳ないですがプログラム全体をもう一度貼ってもらえますか?

Re: promptコマンドの実装について

Posted: 2011年9月15日(木) 22:25
by whity
現在次のようなテストプログラムでデバッグを行っています。

コード:

#include<string.h>
#include<stdio.h>
#include<stdlib.h>

int DivElement(const char* string,char* prompt[]){
   int str_start=0; //(1)文字列起点(str_start)を0とする。
   int index=0;     //(2)格納する配列(prompt)の配列番号(index)を0にする。
   int str_lng;
   unsigned manage=0;//処理済み文字の数
 
   if(strchr(string,'$')==NULL)return -1; //'$'がないとき
   
  do{
      str_lng=0; //(3)文字列長(str_lng)を0にする。
      
      while(string[str_lng]!='$'){ 
        str_lng++;   //(4)$マークを見つけるまで、文字列長(str_lng)を加算する。
        manage++;
      }
       //(5)$マークなら、str_lngが有効であれば、それまでの文字列(str_start起点)を文字列長(str_lng)+1で
      //mallocしたメモリにコピーする。
      if(str_lng>0){
	prompt[index]=(char*)malloc(str_lng+1);
	strncpy(prompt[index],&string[str_start],str_lng); 
	prompt[index][str_lng]='\0';
        index++;
      }
   //(6)格納する配列(prompt)の配列番号(index)の位置にmalloc(3)して、
      prompt[index]=(char*)malloc(3);
      if(strlen(string)-manage>=2){
         strncpy(prompt[index],&string[manage],2); //今の文字位置から2バイトコピーする。
	 
	 prompt[index][2]='\0';
	 
         manage+=2;
      }else{//ただし元の文字列が2バイトに満たない場合は$だけコピーすること。
         strcpy(prompt[index],"$");
	 
	 prompt[index][2]='\0';
	 
         manage++;
      }
      
      index++; //配列番号(index)を++。
   
      str_start=manage;//(7)文字列起点(str_start)を現在の文字位置とする。
      
   }while(manage<strlen(string));//(8)以上を元の文字列が無くなるまで続ける。まだある時は(3)にもどる。
      
   return 0;
}

int main(){
char* prompt[10];
char str[]="Hello$s$s$g",str2[]="$sHello$g",str3[]="Hello$s$sHello$p$g";


printf("strの値\n");
memset(&prompt,0,sizeof(prompt));

DivElement(str,prompt);
for(int i=0;i<10;i++){printf("%s\n",prompt[i]);}

printf("\nstr2の値ー\n");
memset(&prompt,0,sizeof(prompt));

DivElement(str2,prompt);
for(int i=0;i<10;i++){printf("%s\n",prompt[i]);}


printf("\nstr3の値\n");
memset(&prompt,0,sizeof(prompt));

DivElement(str3,prompt);
for(int i=0;i<10;i++){printf("%s\n",prompt[i]);}



return 0;
}

Re: promptコマンドの実装について

Posted: 2011年9月16日(金) 11:26
by softya(ソフト屋)
問題はstr_startを起点とすべきところでstr_startを使っていないことでしょう。
あと「//ただし元の文字列が2バイトに満たない場合は$だけコピーすること。」の処理に一部間違いがあります。

Re: promptコマンドの実装について

Posted: 2011年9月28日(水) 22:44
by whity
また返信が遅れてすいませんでした。
softya(ソフト屋) さんが書きました:問題はstr_startを起点とすべきところでstr_startを使っていないことでしょう。
というのは上のコードでいう31行目の

コード:

strncpy(prompt[index],&string[manage],2); //今の文字位置から2バイトコピーする。
というところのことでしょうか?
これを

コード:

strncpy(prompt[index],&string[str_start],2); //今の文字位置から2バイトコピーする。
というようにすればいいということですか?
softya(ソフト屋) さんが書きました:あと「//ただし元の文字列が2バイトに満たない場合は$だけコピーすること。」の処理に一部間違いがあります。
間違いとはどのような間違いでしょうか。教えていただけると大変助かります。

Re: promptコマンドの実装について

Posted: 2011年9月28日(水) 23:45
by softya(ソフト屋)
すいません時間が経ちすぎて忘れています。
明日また時間を取って考えてみます。お待ちください。

Re: promptコマンドの実装について

Posted: 2011年9月29日(木) 12:31
by softya(ソフト屋)
まず、
while(string[str_lng]!='$'){
が先頭からしか処理していません。

それと2番目の問題は、上と下で文字列の長さが違うのにprompt[index][2]='\0';は変ですよ。

コード:

		if(strlen(string)-manage>=2){
			strncpy(prompt[index],&string[manage],2); //今の文字位置から2バイトコピーする。

			prompt[index][2]='\0';

			manage+=2;
		}else{//ただし元の文字列が2バイトに満たない場合は$だけコピーすること。
			strcpy(prompt[index],"$");

			prompt[index][2]='\0';

			manage++;
		}