文字列の先頭又は最後にある任意の文字を取り除く

在り来たりな C 言語の課題、ポインタ、、、

ポインタ(*)はまだ少しは分かりますが、ポインタのポインタ(**)になると、世界が一気に広がるような気がして私は使わないようにしていました。ポインタのポインタについて難しく考えないように以下のように言われたことがありました。

ポインタのポインタとかって言葉に惑わされるのがよくない。あるポインタの指している中身にアドレスが入ってて、そのアドレスに値があるってだけ。マトリョーシカ人形みたいなもんだ。

ポインタに纏わる操作を具合的にどのようにコードに実装していけば良いか(どう記述をすれば良いか)が私はよく分かっていませんでした。その後もこの言葉がずっと頭の中に残っていました。今、C 言語で開発する機会が出てきたので、前よりは少し理解できるようになってきました。以下のような小さなサンプルコードを書いて、概念や動作を確認してみました。C 言語はコンパイルエラーになったり、セグメンテーションフォールトしたりするので根気が必要ですね。

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

void
my_func(char *str1, char *str2, char **str3)
{
    printf("%2d: str1: %s, str2: %s, str3: %s\n", __LINE__, str1, str2, *str3);

    str1[1] = 'e';
    str2 = strdup("two");
    *str3 = strdup("3rd");

    printf("%2d: str1: %s, str2: %s, str3: %s\n", __LINE__, str1, str2, *str3);

    *(*str3 + 1) = 'a';
    (*str3 + 2)[0] = 'b';
}

int
main(void)
{
    char str1[4] = { 'a', 'b', 'c', '\0' };
    char *str2 = "ttt", *str3 = "sss";

    printf("%2d: str1: %s, str2: %s, str3: %s\n", __LINE__, str1, str2, str3);
    my_func(str1, str2, &str3);
    printf("%2d: str1: %s, str2: %s, str3: %s\n", __LINE__, str1, str2, str3);

    free(str3);
    return EXIT_SUCCESS;
}

実行結果。

26: str1: abc, str2: ttt, str3: sss
 8: str1: abc, str2: ttt, str3: sss
14: str1: aec, str2: two, str3: 3rd
28: str1: aec, str2: ttt, str3: 3ab

このコードを解説しようかと思いましたが、まだ私の言葉で説明できるほど理解が足らないので、こちらのリファレンス*1を見て頂いた方が良いです(- -#

閑話休題。文字列の先頭又は最後から任意の文字を取り除く strip 処理です。ユーザからディレクトリの最後に "/" があると、ログの見栄えが悪いから取ってくれと要求されたので実装してみました。ディレクトリを表す文字列から重複する "/" を除去する(ディレクトリの正規化? 例) /var/tmp///detarame/ => /var/tmp/detarame)こともした方が良いんじゃないかと茶々を入れられましたが、それはまた別のお話ということでお願いします。

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

#define OK      1
#define FAIL    0

#define NORMAL  0
#define REVERSE 1

int
nstrip_str(char **dst, int len, char ch)
{
    int pos = 0, i;

    while (pos < len) {
        if (*(*dst + pos) != ch) {
            break;
        }
        pos += 1;
    }

    for (i = 0; i < len - 1; i++) {
        *(*dst + i) = *(*dst + i + pos);
    }
    *(*dst + i) = '\0';

    return OK;
}

int
rstrip_str(char **dst, int len, char ch)
{
    while (--len >= 0) {
        if (*(*dst + len) != ch) {
            break;
        }
        *(*dst + len) = '\0';
    }

    return OK;
}

void usage()
{
    fprintf(stderr, "usage: ./strip_str /detarame/tekitou/ / r\n");
}

int
strip_str(char *src, char **dst, char ch, int flag)
{
    int ret, len;

    len = strlen(src);
    *dst = strdup(src);
    if (dst == NULL) {
        fprintf(stderr, "cannot strdup()\n");
        return FAIL;
    }

    switch(flag) {
        case NORMAL:
            ret = nstrip_str(dst, len, ch);
            break;
        case REVERSE:
            ret = rstrip_str(dst, len, ch);
            break;
        default:
            ret = OK;
            break;
    }
    return ret;
}

int
main(int argc, char *argv[])
{
    int     ret, strip_flag;
    char    *src, *dst, ch;

    if (argc < 3) {
        usage();
        return EXIT_SUCCESS;
    }

    src = argv[1];
    ch =  argv[2][0];
    strip_flag = NORMAL;
    if (argc == 4 && strcasecmp(argv[3], "r") == 0) {
        strip_flag = REVERSE;
    }

    ret = strip_str(src, &dst, ch, strip_flag);
    if (ret != OK) {
        fprintf(stderr, "something error\n");
        return EXIT_FAILURE;
    }
    printf("stripped: %s\n", dst);

    free(dst);
    return EXIT_SUCCESS;
}

実行結果。

$ ./strip_str /var/tmp/detarame/ /
stripped: var/tmp/detarame/

$ ./strip_str /var/tmp/detarame/ / r
stripped: /var/tmp/detarame