renameを使って、mvコマンドを作る

ファイルを移動するAPIはrename()である。
/* mymv.c */

#include<stdio.h> /* fprintf, perror, rename */
#include<stdlib.h> /* exit */
#include<unistd.h>

/*
#inclde<stdio.h>
int rename(const char *old, const char *new);
*/

int main(int argc, char *argv[])
{
        if(argc != 3){
                fprintf(stderr, "%s: wrong arguments\n", argv[0]);
                exit(1);
        }
        if(rename(argv[1], argv[2]) < 0){
                perror(argv[1]);
                exit(1);
        }
        exit(0);
}

#実行例
$ gcc -o mymv mymv.c
$ touch testfile
$ mymve

./mymv: wrong arguments
$ mv testfile TEST
$ ls
TEST mymv mymv.c

opendir, readdir, closedirを駆使して、lsコマンドを作る


ディレクトリ操作の基本であるopendir, readdir, closedirの基本的な使い方について勉強しました。



/* myls.c */

#include<stdio.h> /* fprintf */
#include<stdlib.h> /* exit */
#include<sys/types.h> /* opendir readdir closedir */
#include<dirent.h> /* opendir readdir closedir */

/*
#include<sys/types.h>
#include<dirent.h>
DIR *opendir(const char *path);

#include<sys/types.h>
#include<dirent.h>
struct dirent *readdir(DIR *d);

#include<sys/types.h>
#include<dirent.h>
int closedir(DIR *d);

*/

static void do_ls(char *path);

int main(int argc, char *argv[])
{
int i;
/*コマンドライン引数が無いときは、stderrにコメントを出力*/
if(argc <2){
fprintf(stderr, "%s: no arguments\n", argv[0]);
exit(1);
}

for(i = 1; i< argc; i++){
do_ls(argv[i]);
}
exit(0);
}


static void do_ls(char *path)
{
DIR *d;
struct dirent *ent;
/* pathにあるディレクトリを読み込むために開く。戻り値はDIRという型へのポインタ。*/
d = opendir(path);
if(!d){
perror(path);
exit(1);
}
/*ディレクトリストリームdからエントリを一つずつ読み込む。
読み込んだエントリをstruct direntで返す。
ディレクトリエントリは、ディレクトリの中身の情報を含んだ構造体。
 */
while(ent = readdir(d)){
fprintf(stdout,"%s  ", ent->d_name);
}
/*ディレクトリストリームを閉じる*/
closedir(d);
fprintf(stdout,"\n");
}

いろんなHello, world!

システムコールのwriteyやstdio.hでお馴染みのprintfや、指定したストリームに文字列を書き出すfprintfを使って、Hello, worldのプログラムを書きます。



/* hello.c */
/* using unix sytem call */
#include<unistd.h> /* write */
#include<stdlib.h> /* exit */

/*
#include <unistd.h>
size_t write(int fildes, const void *buf, size_t nbytes);
*/

int main()
{
/*When a program starts, it usually has three of these descriptors
already opened.
Standard intput : 0, Standard Output : 1, Standard error : 2
*/

write(1,"Hello, world!\n",14);
exit(0);
}



/* hello1.c */
#include<stdio.h> /* printf */
#include<stdlib.h> /* exit */

int main()
{
printf("Hello, world!\n");
exit(0);
}




/*hello2.c*/
#include<stdio.h> /* FILE, fopen, fprintf */
#include<stdlib.h>/* exit */

/*
#inclde<stdio.h>
int fprintf(FILE *stream, const char *format,...);
 
出力書式文字列*formatのもとに続く実引数を変換し、ストリーム*streamに出力する。
 */

int main()
{
/*stdoutに出力*/
fprintf(stdout, "Hello, world!\n");
exit(0);
}

stio.hを用いてファイルコピープログラムを作る


システムコールはとにかく実行するのに時間がかかるのがネックです。
stdio.hで実現される一連の仕組みは、バッファという空間を作ってそこにデータを貯めていき、いっぱいになったら、システムをコールを呼び出すというようなものです。
システムコールを呼び出す回数を減らせるため、はるかに高速なプログラムを書くことができるそうです。

/* simple_filecopy_2.c*/
/*  character−by−character copy is accomplished using
calls to the functions referenced in stdio.h. */
#include<stdio.h>  /* fopen, fgetc, fputc*/
#include<stdlib.h> /* exit */

/*
In many ways, you use this library in the same way
that you use low−level file descriptors.
We need to open a file to establish an access path.
This returns a value that is used as a parameter to other I/O library functions.
The equivalent of the low−level file descriptor is called a stream
and is implemented as a pointer to a structure, a FILE *.
*/

/* Three file streams are automatically opened when a program is started.
   They are stdin, stdout and stderr. */


/*
#include <stdio.h>
FILE *fopen(const char *filename, const char *mode);

//The fgetc function returns the next byte, as a character, from a file stream.
#include <stdio.h>
int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar();


//The fputc function writes a character to an output file stream.
//It returns the value it has written, or EOF on failure.
//As with fgetc/getc, the function putc is equivalent to fputc, but you may implement it as a macro.
//The putchar function is equivalent to putc(c,stdout),
//writing a single character to the standard output
#include <stdio.h>
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);

*/

int main()
{
int c;
FILE *in, *out;

in = fopen("file.in", "r");
out = fopen("file.out", "w");

while((c = fgetc(in)) != EOF)
fputc(c, out);

exit(0);
}


#以下実行例
$ gcc -o simple_filecopy_2 simple_filecopy_2.c

$ echo "She loves you" > file.in
$ cat file.in
She loves you
$ ./simple_filecopy_2
$ cat file.out
She loves you




















システムコールwriteを用いた簡単なプログラム


/* simple_filecopy.c*/
#include<unistd.h> /* read, write */
#include<sys/stat.h> /* open */
#include<fcntl.h> /**/
#include<stdlib.h> /* exit */

/*
#include <unistd.h>
  size_t read(int fildes, void *buf, size_t nbytes);

#include <unistd.h>
  size_t write(int fildes, const void *buf, size_t nbytes);

#include <stdlib.h>
  void exit(int status);

#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
  int open(const char *path, int oflags);
  int open(const char *path, int oflags, mode_t mode);
 

#include <unistd.h>
 int close(int fildes);
*/
int main()
{
char c;
int in, out;
/*open establishes an access path to a file or device. If successful, it returns a file descriptor
that can be used in read, write and other system calls. */
in = open("file.in", O_RDONLY);
/*When we create a file using the O_CREAT flag with open, we must use the three parameter form. mode, the third parameter, is made from a bitwise OR of the flags defined in the header file sys/stat.h
 */
/*S_IRUSR : Read permission, owner. S_IWUSR : Write permission, owner. */
out = open("file.out", O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR);

while(read(in,&c,1) == 1)
write(out, &c,1);

/* When a program exits, all open file descriptors are automatically closed,
 so we don't need to close them explicitly */
exit(0);
}


#以下実行例

$ echo "We are the world" > file.in
$ cat file.in
We are the world
$ ./simple_filecopy
$ cat file.out
We are the world



システムコールreadを使った簡単なプログラミング


/* simple_read.c*/
#include<unistd.h> /* read, write */
#include<stdlib.h> /* exit */
int main()
{
/*
#include <unistd.h>
  size_t read(int fildes, void *buf, size_t nbytes);

#include <unistd.h>
  size_t write(int fildes, const void *buf, size_t nbytes);

#include <stdlib.h>
  void exit(int status);
*/

/*When a program starts, it usually has three of these descriptor
 already opened. 1:Standard input, 2:Standard output, 3:Standard error
 */
char buffer[128];
int nread;
/*copies the first 128 bytes of the standard input to the standard output. It copies
  all of the input if there are less than 128 bytes.*/
nread = read(0,buffer, 128);
if(nread == -1)
write(2, "A read error has occurred\n", 26);
if((write(1,buffer,nread)) != nread)
write(2, "A write error has occurred\n", 27);

/* When a program exits, all open file descriptors are automatically closed,
 so we don't need to close them explicitly */
exit(0);
}

#以下実行例
$ gcc -o simple_read simple_read.c


$ ./simple_read
Hello goodbye
Hello goodbye



$ echo "Hello, goodbye" | ./simple_read
Hello, goodbye


システムコールwriteを使った簡単なプログラミング



/* simple_write.c*/
#include<unistd.h> /* write */
#include<stdlib.h> /* exit */
int main()
{
/*
#include <unistd.h>
  size_t write(int fildes, const void *buf, size_t nbytes);
 */
/*
#include <stdlib.h>
  void exit(int status);
*/

/*When a program starts, it usually has three of these descriptor
 already opened. 1:Standard input, 2:Standard output, 3:Standard error
 */

/* Simply prints a pessage on the standard output(filediscriptor is 1) */
if((write(1, "Here is some data\n", 18)) != 18 ){
write(2, "A write error has occurred on file descriptor 1 \n", 46);
exit(1);}
/* When a program exits, all open file descriptors are automatically closed,
 so we don't need to close them explicitly */
exit(0);
}

Macにwgetをソースコードからインストール

Macにはデフォルトではwgetコマンドがなくファイルのダウンロードはcurlコマンドを使います。
しかし、Linuxユーザーとしては、やはり慣れ親しんだwgetコマンドを使いたいと願います。
そこでGNUの公開しているwgetコマンドのソースコードをコンパイルしてみました。

#curlを用いてソースコードをGNUからダウンロード
$ curl -O wget-1.9.tar.gz ftp.gnu.org/pub/gnu/wget/wget-1.9.tar.gz


#ファイルを解凍
$ tar xzvf wget-1.9.tar.gz

#解凍して生成されたディレクトリに移動
$ cd wget-1.9

#インストールマニュアル読む
$ less INSTALL

$ ./configure
$ make
#インストールは管理者権限が必要なディレクトリにバイナリファイルを配置することに他ならないため、sudoで行う
$ sudo make install

#試しにwgetのひとつ前のバージョンをダウンロードしてみます

$ wget ftp.gnu.org/pub/gnu/wget/wget-1.8.tar.gz


scpを使ってファイルをサーバーへアップロード(RSA認証鍵使用)

scpを使ってファイルをサーバーへアップロードを行います。

scpとはsecure copyの略称でsshの機能を用いて安全にファイルの転送を行うことのできるプログラムだそうです。

sshの機能を使うので、当然sshと同じポート番号を使用します。

サーバー側のsshのポート番号がデフォルトの22ままでRSA方式による通信を行っていない場合、基本フォーマットは以下の通りです。

$ scp ファイル名 サーバーのユーザ名@IPアドレス:パス

ポート番号とRSAの秘密鍵を指定してフィイルの転送を行う場合は以下の通りです。

$ scp -P ポート番号   -i 秘密鍵  ファイル名  サーバーのユーザ名@IPアドレス:パス



*sshのポート番号がデフォルトでRSA方式も採用していない場合、クラッカーのアタックによりサーバーのアカウントを乗っ取られる危険性があります。

Macのbashの設定ファイル

Macの場合は、bashを起動したときに
~/.bash_profile
を読みにいきます。

ubuntuの場合は
~/.bashrc
のようです。

Macでlocateのデータベースを更新

sudo /usr/libexec/locate.updatedb

です。

headコマンドを作る


今回は、headコマンドの原型の作り方について勉強しました。
headコマンドとは指定したテキストファイルの内容を、頭から指定した行数だけ表示するプログラムです。
行数指定は本来、-nのオプションを伴って記述しますが、今回は簡単のため一つ目のコマンドライン引数を行数としています。


/* myhead.c */
#include<stdio.h>
#include<stdlib.h>

static void do_head(FILE *f, long nlines); /* do_headのプロトタイプ宣言 */
/*ファイルに接続されたストリームから与えられた行数だけ読み込む関数*/
int main(int argc, char *argv[])
{
long nlines;

if(argc <  2){
        fprintf(stderr, "Usage: %s n [file file ...]\n", argv[0]); /*コマンドライン引数が無い時はエラーメッセージを表示*/
                exit(1);
 }
nlines = atol(argv[1]); /* 二番目のコマンドライン引数をlong型に変換 */
if(argc == 2){
do_head(stdin, nlines); /*コマンドライン引数が一つの時は標準入力を処理*/
} else{
int i;
for(i = 2; i< argc; i++){
FILE *f; /* ファイルディスクリプタのラッパーのFILE型ポインタを宣言 */
f = fopen(argv[i], "r");
if(!f){ /*f!はf!=NULLと同じ*/
perror(argv[i]);
exit(1);
}
do_head(f, nlines);
fclose(f);
}
}
exit(0);
}

static void do_head(FILE *f, long nlines)
{
int c; /*getcの戻り値はint型であることに注意*/
if(nlines <= 0) return; /*行数カウンタが0なら関数から抜ける*/
while((c = getc(f)) != EOF){ /* getcでファイルにつながれたストリームから一文字ずつ読み込む(EOFまで)*/
if(putchar(c) < 0) exit(1);
if(c=='\n'){
nlines--; /*改行コードに当たったら文字カウンタをデクリメント*/
if(nlines == 0) return; /*行数カウンタが0なら関数から抜ける*/
}
}
}



#コンパイル
$ gcc -o myhead myhead.c

#読み込むファイル作成
$ vi test.txt

#中身を確認
$ cat test.txt
agc
def
ghi
jkl

#

$ ./myhead 4 test.txt
agc
def
ghi
jkl

$ ./myhead  test.txt
agc
def

#エラーを出してみる
$ ./myhead
Usage: ./myhead n [file file ...]

















お好みのコマンド履歴を瞬時に実行

履歴のコマンドの実行に便利なコマンドのご紹介。

「!」に最近実行したコマンドの頭の数文字をつけて実行すると、履歴を読み込んで、最も最近のものを再度実行してくれます。

例えば、簡単なCプログラムをデバッグして再度gccを実行したいときに便利です。

$ vim hello.c

/* hello.c */
#include<stdio.h>
int main(void)
{
printf("Hello, world!!\n); /* 二重引用符を閉じるのを忘れている。 */
return 0;
}

$ gcc -o hello hello.c

hello.c: In function ‘main’:
hello.c:4: warning: missing terminating " character
hello.c:4: error: missing terminating " character
hello.c:5: error: expected expression before ‘return’
hello.c:6: error: expected ‘;’ before ‘}’ token

#vimを用いたファイル作成の履歴を簡単に参照することができる。

$ !v
vim hello.c

#gccを用いたコンパイルもすぐできる。

$ !g
gcc -o hello hello.c

$ ./hello
Hello, world!!

!を駆使すれば、再実行したい目的のコマンドを探すために、ホームポジションから離れて矢印キーを押したり、ctrl + pを連打する必要もなくなります。



標準入出力(stdio)を用いたcat


stdioでの標準入出力により、catコマンド作成します。
readやwriteといったシステムコール呼び出しは関数呼び出しよりも大変遅いようです。
そのため、標準入出力ライブラリー(stdio)に記述されている一連の関数を用いることが通例のようです。
stdioでは、ストリームからreadを用いて読み込んだデータをバッファと呼ばれる一時記憶領域に保存し、そこからユーザー指定の任意のデータを抽出するような処理方式になっているようです。
stdioはプログラムとストリームの間にバッファ(緩衝)を用意し、readやwriteの機能を修飾する役目を持っていると考えます。



/* mycat.c */
#include<stdio.h> /* fopen, fgetc, fclose, putchar */
#include<stdlib.h> /*  */

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

        for(i = 1; i<argc; i++){
                FILE *f;        /* FILE型へのポインタ */
                int c;

                f=fopen(argv[i], "r"); /* ファイルパスを指定して、そのファイルにつながるストリームを作り
                                          それを管理するFILE型へのポインタを返し、fへ格納する*/
                /*何らかの理由で失敗した場合は、NULLを返し、errnoに原因に対応する値を格納する。*/
             
                if(!f){
                        perror(argv[i]); /* エラー処理 */
                        exit(1);
                }

                while((c = fgetc(f)) != EOF){   /* fputcでストリームからバイト単位でデータを読み込む */
                                                /* ストリームの場合はEOF(-1)が返されるため、*/
                                                /*戻り値を格納する変数もint型である。*/
        if(putchar(c) < 0) exit(1); /* バイト単位の出力APIのputcharを用いる。*/
        }
        fclose(f);      /*fが参照するストリームを閉じる。*/
        }
        exit(0);
}


$ gcc -o mycat mycat.c

$ echo "It is sunny today." > test.txt

$ ./mycat test.txt
It is sunny today.

#ファイルパーミッションを変更して読み取り不可にする
$ sudo chmod -r test.txt 
$ ./mycat test.txt 
test.txt: Permission denied

#存在しないファイル名(hogehoge)を指定してプログラムを実行
$ ./mycat hogehoge
hogehoe: No such file or directory