Hello i'm working on this piece of code and I have the idea how it should be. But I believe my assignment is base on boath C and shall formating command to create a menu. The code is working so far fine but in order to move on furthur. I need to know the proper syntax. I searched online and didn't find anything. I am trying to add operations menu or submenu. What is the command or system call used for this purpose.

After opening Directory , then File, I have to setup a menu in which opeitons are
Edit
Run
Change Directory
Make New Subdirectory
Remove File
Quit

#include <sys/types.h>
        #include <stdlib.h>
        #include <stdio.h>
        #include <unistd.h>
        #include <dirent.h>
        #include <string.h>
        #include <time.h>
        #include <curses.h>

        int main(void){
              ////Variables////
            ///////////////////////

            pid_t child;
            DIR * d;
            struct dirent * de;
            int i,c, k;
            char s[256], cmd[356];
            time_t t;

            //////////////////////

            while(1){
                t= time (NULL);
                printf("Time:  %s \n", ctime(&t));

                getcwd(s,200);              //why 200? what if bigger? check for errors?
                printf("\n Current Directory: %s \n", s);

                d= opendir(".");
                c = 0;
                while((de = readdir(d))){
                    if ((de->d_type) & DT_DIR)
                        printf("( %d Directory:  %s )\n", c++, de-> d_name);
                }
                closedir(d);
                d = opendir( ".");
                c = 0;
                while((de = readdir(d))){
                    if(((de->d_type) & DT_REG))
                        printf("(%d File: %s) \n", c++, de->d_name);
                    if((c % 8 )==0){
                        printf("Hit N for Next \n");
                        k = getchar();}
                }
                closedir(d);
                printf(" ____________________________\n");
                c =getchar();getchar();
                switch (c) {
                    case 'q': exit(0);  /*quit */
                    case 'e': printf("Edit what?:" );
                        scanf("%s", s);
                        strcpy(cmd, "pico");
                        strcat(cmd,s);
                        system(cmd);
                        break;
                    case 'r': printf("Run What?;" );
                            scanf("%s",cmd);
                        system(cmd);
                        break;
                    case 'c': printf("Change To?:");
                        scanf("%s",cmd);
                        chdir(cmd);
                        break;
                }
            }

            return 0;

}

Recommended Answers

All 11 Replies

Lines 33 and 40. I see you Bitwise Anding with constants DT_DIR and DT_REG, which are 16 and 0 respectively.

Are you sure you want to Bitwise And instead of using the == sign? I'm thinking particularly of Bitwise Anding with DT_REG, which is 0, which means anything Bitwise Anded with it is 0, which means line 41 can never execute, correct?

The parentheses seem odd in those lines as well. If those lines are truly what you want, a comment explaining the if tests seems in order. I seem to be caught in a long loop executing lines 43 and 44 because c is always 0 since the line 40 test is never true, which means c never changes in line 41.

The code is working so far fine

Really? I keep being told to enter 'N', but I have no idea why. Your program should tell me what it's doing or what I'm supposed to do.

I'm not sure whether I get your question right, so I wonder whether below is the answer you are looking for.

            switch (c) {
                case 'q': exit(0);  /*quit */
                case 'e': printf("Edit what?:" );
                    scanf("%s", s);
                    strcpy(cmd, "pico");  // ¿ No need to use a space between the command and the file or dir?
                                    // Perhaps you want to test whether the input is actually a file.
                                    // Or would you like the option to open a directory in pico as well?
                    strcat(cmd,s);
                    system(cmd);
                    break;
                case 'g': printf("Edit what in the Gimp?:" );
                    scanf("%s", s);
                    strcpy(cmd, "gimp ");  // ¿ Need to use a space between the command and the file or dir? (I think so!)
                                    // Perhaps you want to test whether the input is actually a file....
                    strcat(cmd,s);
                    system(cmd);
                    break;
                case 'r': printf("Run What?;" );
                    scanf("%s",cmd);
                    system(cmd);
                    break;
                case 'c': printf("Change To?:");
                    scanf("%s",cmd);
                    chdir(cmd);   // Perhaps it's wise to check whether the input is actually a directory!
                    break;
            }
        }

Perhaps it might be an option to change the order of action and item choice in the menu structure. Then you can show file actions when you have choosen a file and dir-options when you have choosen a directory. That would avoid the need to check whether the choosen item is a file or directory (and related alternative flow ...) and also avoid errors related to file actions on directories and dir-actions on files.

At line 27 you mention whether the limit for directories should be 200 or if it could be larger. In your menu structure the action is 'activated' by a single key but the choice for file or directory can be made only by entering the whole name. That might lead to a lot of typing and typos .... That's why I like bash's option to use tab-completion and repeat-last-argument (meta-dot or Alt-.) so much. Instead of implementing this in your menu/application you could include a counter in front of every item in the listing. Then you could use the number(=counter) to choose the file or directory you want to act on. (It's up to you whether your counter starts at 1 or 0 !)

In case you foresee that you would more often change directories than act on files reprocessing the list in order to implement these functionalities might be an option. Otherwise I think the most efficient way is to fill two arrays of when you are processing the directory contents to make the list. One array with 'booleans' which will give either is_file[ nr_choosen - 1 ] or is_dir [ nr_choosen - 1 ] which would make it easy to implement the action menu dependent on the choosen item. The other array would save you a lot of typing (and cost 'a bit of kb workmemory') to translate te number to the file/dir name: item_name[ nr_choosen - 1 ]. (If you prefer the counter to start at zero you should use : item_name[ nr_choosen ]!)

This is a more clear description for the question: develop a small "text-based shell utility" for Unix or similar system. A common complaint of the most used Unix(text) shells (Bourne, Korn, C) is that it is difficult to remember (long) file names, and types "long " command names. A "Menu" type shell allows a user to "pick" an item (file) or command from a "menu".

It will look like this
Output:
Current working Dir; /home/os.progs/Me
it is now: 3 June 2017, 6:32 PM

Files: 0. ts.c

  1. a.out
  2. ts.txt
  3. assinment1
  4. program2.c

Directories: 0. . .

  1. my_dir

Operations: E Edit
R Run
C Change Directory
M Make a new Subdirectory
R Remove File
Q Quit

You will read a single key "command" entered by the user, then a file or directory number or partial file name with "completion" and ts will output some system information on the terminal, or run a program (by means of systems calls).
The commands you will implement:
Edit. opens a text editor with a file
Run - runs an executable program. You should handle parameters.
Change – changes working directory.
You will need to implement:
Prev, Next, and Operations need to be merged (menu fits on one screen).
Store names in an array,
Only one pass through the directory. If the "ts" program has an argument, use it as the directory starting point (working dir),
like "./ts /bin".
It should provide a reasonable user interface.

How many files and directories (max 1024)
How long is a file name (max 2048 characters, really: limits.h, NAME_MAX)

Joris Claassen, Yes you are correct for all of those Thak you! I am not able to find the exact topic that helps me give the missing information about which syntax should be properly used. When I search only examples i find are about shell scripting. As much as i understood I don't have to do shell scripting. But the syntax or commands used in shall scripting could be used ?

If i want to use counter the list shown in File should be in my code. Since I created the subfiles in the same folder (as where the .c file is ) and when I run the program. It's shows the output as the given output, but then how to set a seperate couter for each option and just give the number of it.?
Is my logic wrong for this part?

First of all I wonder how I can help you without 'ruin the assignment' .... At least I get the impression that this is an assignment. So I try not to give a complete ready to run example and also to give you some choices you have to make ...

« A common complaint of the most used Unix(text) shells (Bourne, Korn, C) is that it is difficult to remember (long) file names, and types "long " command names. » That's a nice introduction for the assignment but using bash would solve this for the biggest part as it supports smart things like command and file completion.

The listing below «it is now: 3 June 2017, 6:32 PM» in your post of Sep the 6th seems to confirm my suggestion to use a single key for 'item selection' instead of entering the whole name (to be processed with scanf("%s",cmd);)
But it suggests to use number 0 as starting number for both the file listing and the subdir listing. I wonder about that. I also wonder about the paging mechanism. After you choose to show the next 8 items I think it would be usefull to restart numbering (especially if you are working on a directory with 1024 items it doesn't make much sense to show the files 600 upto 607 and subdirs 600 upto 607 and 'allow' the user to act on file/dir 3. Most people won't remember item 3 so it's likely a typo ...

If a directory contains so many items and you know in advance that you are looking for a file, then I think it doesn't make sense to show all the subdirs as well (and the other way around: show all file while you are looking for a subdir). I also think it would become very annoying when you see the file you want to act on in the very first 8 items but you have to type N ....(over and over again) because the directory just happens to contain another 1000 subdirs/files. Actually your code (lines 43 and 44) don't require to press/hit N: just any key would do....

While I was testing some things (to make sure I wouldn't suggest nonsense ....) I soon had to start using functions to make it manageable/efficient. Actually I think that's one of the first advices you will find when you are looking for 'programming tips'.

You mention that the max amount of items in a directory to work with is 1024, and max 2048 characters for names. You could use char item_name[1024][2048];, but that would use a lot of memory/space which is likely not used.
One approach is to allocate at the moment it is used for the first time. Another approach is to initiate a sensible amount at first and use reallocate whenever needed.

getcwd(s,200); //why 200? what if bigger? check for errors?
Indeed why 200? I think it should match char s[256];
Besides that, why would you initiate char item_name[1024][2048];, if you only accept item names upto 200 characters?

First the altered main after moving some code into functions. Variables not declared in main are 'global' for efficiency (to share with functions).

int main(void){
      ////Variables////
    pid_t child;  // where is this used?
    DIR * d;
    struct dirent * de;
    int i,c, k;   // where is i used? (k isn't used anymore after 'breaking the code into functions'
    char s[NAME_MAX] ; //, cmd[356];
    time_t t;
    //////////////////////

   // max_lines = optional determine-screen-capacity ...  - lines for UI and presentation ...
#ifdef renumber_page 
    // more (memory) efficient and limits the item selection to one key!
    dir_lst  = (int*) malloc(max_lines * sizeof(int));
    file_lst = (int*) malloc(max_lines * sizeof(int));
#endif

    short is_dir[MAX_ITEMS];

    int   cnt[3];
    short filt = 0;
    memset(item_name, 0, MAX_ITEMS * sizeof(char));  // panic setting ...? Just to make sure.
    memset(is_dir, 0, MAX_ITEMS * sizeof(short));      // panic setting ...? Just to make sure.
    int res = -1;
    int shown = 0;
    if( max_lines < 11 ){  // « < 10 »: if you want to start numbering at 1!
        last = '0' + max_lines - 1;  // « = '1' +  »:  dito
    } else if( max_lines < 37 ){
        last = 'a' + max_lines - 11;
    } else {
        assert("To be determined!");
    }

    while(1){
        _clrscr();  // Or just once at program start ?!
        d= opendir(".");
        c = 0;

        cnt[0] = 0;
        cnt[1] = 0;
        cnt[2] = 0;
        while(de = readdir(d)){
            if( (strcmp(de->d_name, ".") != 0) && 
                ( (de->d_type) & (DT_DIR | DT_REG) ) 
                ){
                assign_item(cnt[0], de->d_name);
                is_dir[cnt[0]] = (de->d_type) & DT_DIR;
                if( is_dir[cnt[0]] ){
                    cnt[1]++;
                } else {
                    cnt[2]++;
                } 
                cnt[0]++;
            } 
            if( cnt[0] = MAX_ITEMS ){
                printf("Max amount of %d items is reached: ignoring remaining items! \n", MAX_ITEMS);
                break;
            }
        }
        closedir(d);

        // if( cnt > max_lines ){  // only limit type when not fitting at 'one page' or always?
            printf("Found %d entries in directory %s \n", cnt[0], s);
            printf("\t%d subdirectories and\n", cnt[1]);
            printf("\t%d files.\n", cnt[2]);
            printf("Limit listing to [f] files only, [d] directories only, or [n] no limit at all, or [q] to quit.\n");
            c = 0;
            while( c == 0 ){
                c = getchar();getchar();
                switch (c) {
                    case 'd': filt = 1; break;
                    case 'f': filt = 2; break;
                    case 'n': filt = 0; break;
                    case 'q': clear_items(); exit(0);
                    default:
                        printf("Invalid choice: %c.\r\n", c);
                        c = 0; // keep looping!
                }
            }
        // }

        printf("\n\n ____________________________\n");
        t= time (NULL);
        printf("Time:  %s \n", ctime(&t));
        getcwd(s, NAME_MAX); 
        printf("\n Current Directory: %s \n", s);

        c = 0;
        if( filt != 2 ){
            for(int idx = 0; idx < cnt[0]; idx++){
                if( is_dir[idx] ){
                    dir_lst[c] = idx;
                    // When you limit the amount of items to 10 and use renumbering(!), you can keep « " %d Directory … ", c++ »
                    printf(" [%c] Directory:  %s \n", key_choice(c++), item_name[idx]);
                  #if use_paging   
                    if( c >= max_lines ){
                        break;
                    }
                  #else
                    if( (c % max_lines )==0 ){  // <-- 8 
                        if( list_option(1 | 16) == eoRefreshList ){
                            goto rebuild_list; // horrible «goto», unfortunately continue can't be used here!
                        }
                      #ifdef renumber_page 
                         c = 0;
                      #endif 
                    }
                  #endif    
                }
            }
        }
        // when you want to present both subdirs and files (both with numbering starting at 0 or 1)  some processing is needed here: 'left as an excercise'

        if( filt != 1 ){
            for(int idx = 0; idx < cnt[0]; idx++){
                if( !is_dir[idx] ){
                    file_lst[c] = idx;
                    // When you limit the amount of items to 10 and use renumbering(!), you can keep « " %d File  …", c++ »
                    printf(" [%c] File:  %s \n", key_choice(c++), item_name[idx]);
                  #if use_paging   
                    if( c >= max_lines ){
                        break;
                    }
                  #else
                    if( (c % max_lines )==0 ){  // <-- 8 
                        if( list_option(filt | 16) == eoRefreshList ){
                            goto rebuild_list; // unfortunately continue can't be used here!
                        }
                      #ifdef renumber_page 
                         c = 0;
                      #endif 
                    }
                  #endif    
                }
            }
        }

        shown = 0;
        while( (res = ShowMenu(filt,shown++)) == eoShowMenu ){ ; }
        if( res == eoQuit ){
            // opportunity to clear things up!
            clear_items();
            exit(0);  /* quit */
        }
rebuild_list: ;
    }
    return 0;
}

Possible implementations of some (helper) functions:

void assign_item(int idx, char* src){
    if( item_name[idx] == NULL ){
        item_name[idx] = malloc(NAME_MAX * sizeof(char));
    }
    strcpy(item_name[idx], src);
}

void clear_items(void){
    for(int idx = 0; idx < MAX_ITEMS; idx++){
        if( item_name[idx] != NULL ){
            free(item_name[idx]);
        }
    }
}

char key_choice(int index){
    if( index < 10 ){
        return '0' + index;  // Or 1 + index to start at 1!
    } else {
        return 'a' - 10 + index;
    }
}

// «Operation» is just an enumeration  
Operation CommonMenu(int again){
    if( again == 0 ){
        printf("Operations: \tE Edit\n");
        printf("\t\tV View\n");
        printf("\t\tR Run\n");
        printf("\t\tC Change Directory\n");
        printf("\t\tM Make a new Subdirectory\n");
        printf("\t\tf Remove File\n");
        printf("\t\td Remove a Subdirectory\n");
        printf("\t\tQ Quit\n");
        printf("\t\tN Next page\n");
        printf("\t\tP Previous page\n");
        // printf("...\n");
    } else {
        printf("Another operation? ");
    }
    while( 1 ){
        char c = getchar();getchar();
        switch (c) {
            case 'Q':  // don't annoy the user with case sensitivity ....
            case 'q': 
                return eoQuit;
            case 'E':
            case 'e': 
                return eoEdit;
            // .... case ...
            //   return ....;
            case 'R': 
            case 'r': 
                return eoRun;
            case 'C': 
            case 'c': 
                return eoChDir;
            case 'M': 
            case 'm': 
                return eoMkDir;
            case 'F': 
            case 'f': 
                return eoRmFile;
            case 'D': 
            case 'd': 
                return eoRmDir;
            case 'P': 
            case 'p': 
                return eoPrevPage;
            case 'N': 
            case 'n': 
                return eoNextPage;
            default:
                printf("Unknown operation key '%c'. Try again...\r\n", c);
        }
    }
}

Functions «SdirMenu», «FileMenu» are like «CommonMenu» but limiting both menu/message and allowed input to what's sensible for file or directory items.

You might wonder why I use 'long variable names'. First of all: readability. But if you don't mind about that, think about when you need to rename a variable for whatever reason. To rename a single character named variable is perfect to create bugs ....
Using «goto» is another one risking bugs. That's why I always try not to use it at all. Unfortunately there's no extension of the continue statement to continue with a loop at a higher level. Like there's no extension of the break statement to skip 'more than just the most inner side loop/switch'. (Both could be possible quite easily I think: continue 2; and break 3; ... do I even need to explain what I have in mind with these two examples?)

As I have mentioned before: I don't want to do your 'home work' so below a not complete code. (I actually have removed some parts of the code which I haved tested .... While testing I could change directories (also upward!), remove directories and open/delete files. But in 'file' mode I couldn't go back to 'dir' mode. But it shouldn't be that hard to implement I guess.)
I think you should use it either as inspiration or try to extend it to complete code.

int get_operation(char* cmd, int* lst){
    char key = getchar();getchar();
    int idx = -1;
    if( (key >= '0') && (key <= '9') ){
        idx = key - '0';
    } else if( (key >= 'A') && (key <= 'Z') ){
        idx = key + 10 - 'A';
    } else if( (key >= 'a') && (key <= 'z') ){
      // When you want more than 36 items you could use «idx = key + 36 - 'a';» ...
      // But then you need to adjust the code in «char key_choice(int index)» as well ....
        idx = key + 10 - 'a';
    } 
    if( (idx > -1) && (idx < max_lines) ){
        strcat(cmd, item_name[lst[idx]] );
        return 1;
    }
    return 0;
}

 // «again» is used in «Operation …Menu(again)» to avoid printing the whole menu over and over .... for the same part of the listing 
 // instead of  «if( again == 0 )»  you could use «if( (again % 10) == 0 )» to repeat the menu after 10 times....
int ShowMenu(int filt, int again){
    Operation oc;
    printf(" ____________________________\n");
    if( filt == 0 ){
        oc = CommonMenu(again);
    } else if( filt & 1 ){
        oc = SdirMenu(again);
    } else if ( filt & 2 ){
        oc = FileMenu(again);
    } 
    strcpy(cmd, ""); 
    switch( oc ){
        case eoQuit:
            return eoQuit;
        case eoEdit:   // even better would be to limit to the current last item in the list (left as an excercise ...)
            printf("Which file to edit [0-%c]?\n" , last);
            strcpy(cmd, "pico ");  // space required!
            if( get_operation(cmd, file_lst) ){
                system(cmd);
            }
            break;
        case eoRun:
            .... // 'left as an excercise' .... 
            break;
        case eoChDir:
            .... // 'left as an excercise' .... 
            return eoRefreshList;  // need to rebuild the list!
        case eoMkDir:
            .... // 'left as an excercise' .... 
        case eoRmFile:
            .... // 'left as an excercise' .... 
        case eoRmDir:
            printf("Which dir to remove [0-%c]?\n" , last);
            if( get_operation(cmd, dir_lst) ){
                rmdir(cmd);
            }
            break;
        case eoPrevPage:
            assert("Actions to do here: 'left as an excercise ....'");
            break;
        case eoNextPage:
            if ( filt & 16 ){  // flag 16 is used to distinguish between 
                   // • 'the need' to show the next set of lines  which you implemented with «if((c % 8 )==0)» or to 
                   // • use 'a realy paging mechanism'
                return eoNextPage;
            } else {
                assert("Actions to do here: 'left as an excercise ....'");
            } 
            break;

        case eoShowMenu:
        case eoRefreshList:
            return oc;
        default:
            assert(oc);
    }
    return eoShowMenu;
}

Operation list_option(int filt){
    int res = -1;
    int cnt = 0;
    printf("Hit N for Next \n");  // extend message ....
    char key = getchar();getchar();
    switch( key ){
        case 'Q':
        case 'q':
            res = eoQuit;
            break;
        case 'N':
        case 'n':
            return eoNextPage;
      //  case  ...?   //  possible extensions ?
      //     return eo...;
        default:  // keep 'showing'/'processing' the menu for this set of item until choosen otherwise
            while( (res = ShowMenu(filt, cnt++)) == eoShowMenu ){ ; }
    }

    if( res == eoQuit ){
        // opportunity to clear things up!
        clear_items();
        exit(0);  /* quit */
    } else {
        return res;
    }
}

Also note that I have #define renumber_page in my 'global declaration part'. With or without this you also need to declare both dir_lst and file_lst (in a slightly different way!) I think you should be able to find out how...

In short: it works but without tweaking/fine tuning I would never start to use it ...

Oops, silly typo in the part that I added after testing (to avoid issues in directories containing too many items): if( cnt[0] = MAX_ITEMS ). This should of course be if( cnt[0] == MAX_ITEMS ).

When trying to remove the if -blocks inside the two for-loops [ if( !is_dir[idx] ) and if( is_dir[idx] ) ] I also noticed a mistake that hadn't caused any problems during previous tests. But perhaps it won't ever cause problems because it's not needed (is it guaranteed that the OS gives new memory cleaned up properly? I have seen many sources claiming this ...):
  memset(item_name, 0, MAX_ITEMS * sizeof(char));
should be
  memset(item_name, 0, MAX_ITEMS * sizeof(char*));.

I could remove these if-blocks by filling the array item_name in a different way: not in the order which readdir is giving the results, but ... using indices 0 upto f-1 for files and MAX_ITEMS - 1 - d upto MAX_ITEMS - 1 for subdirs. In which f is the amount of files and d is the amount of subdirs, and f + d is less than or equal to MAX_ITEMS: which is actually guaranteed by the added part mentioned above, the if-block: if( cnt[0] == MAX_ITEMS ).

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.