DaemonForums  

Go Back   DaemonForums > Miscellaneous > Programming

Programming C, bash, Python, Perl, PHP, Java, you name it.

Reply
 
Thread Tools Display Modes
  #1   (View Single Post)  
Old 11th November 2011
Daffy Daffy is offline
Fdisk Soldier
 
Join Date: Jun 2010
Posts: 73
Thanked 0 Times in 0 Posts
Default First program in C

Hello. For the last 5 days, I begun to learn C (with K&R alongside with "C primer plus" for that little bit of extra explanation).

I know it's a bit early to ask questions, but I try to push myself.

So I decided to write a program in C that takes all of my .wav files in my dir and make them mp3 with tags. So far, I have this:

Code:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

void remove_enter( char *s )	// function to remove enter from stdin
{
	s[strcspn( s, "\n" )] = '\0';
}

int main()
{
	char artist[40];
	char album[40];
	char genre[20];
	char year[4];

	printf("Enter artist name:\n");	// input name,  move to array, remove \n
	fgets(artist, 256, stdin);
	remove_enter(artist);

	printf("Enter album name:\n");
	fgets(album, 256, stdin);
	remove_enter(album);

	printf("Enter year:\n");
	fgets(year, 256, stdin);
	remove_enter(year);

	printf("Enter genre:\n");
	fgets(genre, 256, stdin);
	remove_enter(genre);

	char argv[512]; 	// create array to pass lame switches

	snprintf(argv, 512, "/usr/local/bin/lame --ta %s --tl %s --ty %s --tg %s *.wav", artist, album, year, genre);	// lame to array

	system(argv);		// pipe exec to system

	return 0;
}
The "problem" is that it excecutes only if there is only 1 wav file inside the directory. It is more than obvious that I must read my @ss off and I'm excited about that. Can I ask for some pointers for what do I have to look for to be able to excecute this program for a number of wav files (>1)? (not a solution, just for a more general direction)


I also would love some criticism about how correct is the code. (again, I know it's not much ).

Thank you.
Reply With Quote
  #2   (View Single Post)  
Old 11th November 2011
ocicat ocicat is offline
Administrator
 
Join Date: Apr 2008
Posts: 2,935
Thanked 190 Times in 160 Posts
Default

Quote:
Originally Posted by Daffy View Post
The "problem" is that it excecutes only if there is only 1 wav file inside the directory.
Okay, you have two avenues to choose from:
  • Pass in all relevant filenames as command-line arguments.
  • Read the directory contents programmatically from within the code.
As for an example of the first, consider the following:
Code:
$ cat test.c
#include <stdio.h>
int main(int argc, char *argv[])
{
  int i;
  for (i = 0; i < argc; i++)
    printf("%d:\t%s\n", i, argv[i]);
  return 0;
}
$ gcc test.c
$ a.out
0:      a.out
$ a.out 1 2 three
0:      a.out
1:      1
2:      2
3:      three
$
For the second approach, study readdir(2). I would recommend working with processing command-line arguments first. It's more flexible.

Writing lots of simple programs (less than 20 lines...) like what I have indicated above is very important as you teach yourself how to answer your own questions. This is the sign of a good programmer -- one who thinks through the problem, identifies what they know, what they don't know, & does the research needed to answer unknowns themselves.
Quote:
char argv[512]; // create array to pass lame switches
While this definition is perfectly legal, it does not follow convention. The names of the two variables passed to main() are typically argc & argv. Limiting use of these names to this special case makes maintenance easier for the next guy who inherits the code. Although you may not pass this code on to anyone else, thinking about the ramifications of maintenance will serve you well.

Once you have mastered The C Programming Language, I would recommend C Traps and Pitfalls next. Andrew Koenig is also one of the original AT&T elite who penned seminal books on the subject. C Traps and Pitfalls points out where C inconsistencies frequently bite newbies & experienced professionals alike.
Reply With Quote
  #3   (View Single Post)  
Old 11th November 2011
ocicat ocicat is offline
Administrator
 
Join Date: Apr 2008
Posts: 2,935
Thanked 190 Times in 160 Posts
Default

For bonus points, you might also want to consider how your program could be written as a shell script. After all, the logic of your program is simply manipulating some strings followed by spawning another shell to execute a constructed command.

The problem with many professional C programmers (especially if they only have proficiency in the one language...) is that they think that everything can & should be written in C. While it is true that C is flexible enough to handle general usage, it may not be the most advantageous choice. Scripting has its place, & many scripting languages have special facilities for handling string manipulations far more flexibly than C. Yes, shell scripts are limited to what can be spliced together via sed(1), grep(1), awk(1), etc., the newer scripting languages such as Perl, Python, & Ruby excel at string manipulations. Learning regular expressions can be a very useful tool in your ever-expanding bag of tricks.
Reply With Quote
  #4   (View Single Post)  
Old 11th November 2011
Daffy Daffy is offline
Fdisk Soldier
 
Join Date: Jun 2010
Posts: 73
Thanked 0 Times in 0 Posts
Default

Thank you VERY much ocicat for the advice and the help.

As for the second post, I've already done that in perl. I want to achieve it in C (as well as some other simple scripts) just for the excercise and the excitement. I see that many simple scripts can be a hell to achieve with C, but they are a great learning experience. No pain, no gain after all.
Reply With Quote
  #5   (View Single Post)  
Old 11th November 2011
ocicat ocicat is offline
Administrator
 
Join Date: Apr 2008
Posts: 2,935
Thanked 190 Times in 160 Posts
Default

To further elucidate, your initial code posted was interactive -- requiring user input during execution. Specifying everything (all titles, artists, tracks...) on the command-line would be cumbersome & scale very poorly.

If you have lots of files to process, you might want to consider making the application more batch-oriented as opposed to interactive. To do this, you might want to look at figuring out a file format which would then be parsed by your code. All information could be manually written to a text file which is then read & processed by program logic. The file would include all information needed to tag tracks. Processing different tracks would then mean that only this one text file would need to be modified.

Since I suspect this application is simply educational in nature, you might consider mixing command-line input with interactive prompts for specific values -- track names, artist names, etc. In this fashion, filenames could be specified at the command-line explicitly and/or via wildcards, & then the program logic would then ask for more information for each specified track.

Just a thought. There are lots of ways of doing the processing desired.

Last edited by ocicat; 11th November 2011 at 11:46 PM.
Reply With Quote
  #6   (View Single Post)  
Old 12th November 2011
Oko's Avatar
Oko Oko is offline
Fsck Surgeon
 
Join Date: May 2008
Location: Kosovo, Serbia
Posts: 850
Thanked 36 Times in 32 Posts
Default

Quote:
Originally Posted by Daffy View Post

So I decided to write a program in C that takes all of my .wav files in my dir and make them mp3 with tags.
Code:
#!/bin/sh
for i in *.wav
    do
    lame $i `basename $i .wav`.mp3;
    done
For the record I love C and work daily with it but I would never use it for what you wanted to use
Reply With Quote
  #7   (View Single Post)  
Old 12th November 2011
drhowarddrfine drhowarddrfine is offline
VPN Cryptographer
 
Join Date: May 2008
Posts: 358
Thanked 9 Times in 8 Posts
Default

But he's trying to learn C so it doesn't matter that using a shell might be better.
Reply With Quote
  #8   (View Single Post)  
Old 23rd November 2011
Daffy Daffy is offline
Fdisk Soldier
 
Join Date: Jun 2010
Posts: 73
Thanked 0 Times in 0 Posts
Default

After one week and not so much time on my hands, I achieved it. I have some questions about the way I did it though.

With the first try, I achieve it like this:
Code:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stddef.h>

void remove_enter( char *s )	/* function to remove enter from stdin */
{
	s[strcspn( s, "\n" )] = '\0';
}

int main(int argc, char *argv[])
{
	char artist[150];
	char album[150];
	char genre[40];
	char year[8];
	char track[50];

	/* check for command line arguments and print msg */
	if (argc < 2)
	{
		fprintf(stderr, "Usage: %s filename\n", argv[0]);
		exit(1);
	}


	printf("Enter artist name:\n");		/* ask for input, read to array, remove enter */
	fgets(artist, sizeof(artist), stdin);
	remove_enter(artist);

	printf("Enter album name:\n");
	fgets(album, sizeof(album), stdin);
	remove_enter(album);

	printf("Enter year:\n");
	fgets(year, sizeof(year), stdin);
	remove_enter(year);

	printf("Enter genre:\n");
	fgets(genre, sizeof(genre), stdin);
	remove_enter(genre);

	int pid, status;
	int i;

	/* count files *.wav and fork() with counter i<=tracks_number + ask for track name */

	for (i = 1; i < argc; i++)
	{
		printf("Enter track name:\n");
		fgets(track, sizeof(track), stdin);
		remove_enter(track);

		pid=fork();
		if (pid==0)
		{
			char *command[] = {"/usr/local/bin/lame","-V 2","--tt",track,"--ta",artist, "--tl",album, "--ty",year, "--tg",genre,argv[i],NULL};
			execvp("lame", command);
			sleep(1);
		}
		else
		{
			wait(&status);
		}
	}

	return 0;
}
While I find it somehow "dirty", it works. After that, I decided to achieve it with the se of malloc() and a struct specifier. I'm having a problem to understand -how- to use malloc() in an array. I have this modified code from the first try:

Code:
/* same as above to save space */
#define LEN 1000
struct tag_t {
	char artist[LEN];
	char album[LEN];
	char year[LEN];
	char genre[LEN];
	char track[LEN];
} taglist;

void remove_enter( char *s )	/* function to remove enter from stdin */
{
	s[strcspn( s, "\n" )] = '\0';
}

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

	/* check for command line arguments and print msg */
	if (argc < 2)
	{
		fprintf(stderr, "Usage: %s filename\n", argv[0]);
		exit(1);
	}

	printf("Enter artist name:\n");		/* ask for input, read to array, remove enter */
	char *artist = (char*) malloc(50 *sizeof(char));
	fgets(taglist.artist, LEN, stdin);
	remove_enter(taglist.artist);

        /* same for album, year and genre to save space in post */

	int pid, status;
	int i;

	/* count files *.wav and fork() with counter i<=tracks_number + ask for track name */

	for (i = 1; i < argc; i++)
	{
		printf("Enter track name:\n");
		fgets(taglist.track, LEN, stdin);
		remove_enter(taglist.track);

		pid=fork();
		if (pid==0)
		{
			char *command[] = {"/usr/local/bin/lame","-V 2","--tt",taglist.track,"--ta",taglist.artist, "--tl",taglist.album, "--ty",taglist.year, "--tg",taglist.genre,argv[i],NULL};
			execvp("lame", command);
			sleep(1);
		}
		else
		{
			wait(&status);
		}
	}
	free(artist);

	return 0;
}
By trying a long name (let's say n chars) as the first argument, it fails to pass it to 'char *command[]' and skips (n-30) characters. I -think- I need to use malloc() as well to this array but I cannot understand how.

Sorry for the very long post and a big thank you to anyone who reads it.

Comments about the structure of the code are also welcome.
Reply With Quote
  #9   (View Single Post)  
Old 24th November 2011
ocicat ocicat is offline
Administrator
 
Join Date: Apr 2008
Posts: 2,935
Thanked 190 Times in 160 Posts
Default

Quote:
Originally Posted by Daffy View Post
By trying a long name (let's say n chars) as the first argument, it fails to pass it to 'char *command[]' and skips (n-30) characters.
This is unclear. Please clarify.
Reply With Quote
Old 24th November 2011
ocicat ocicat is offline
Administrator
 
Join Date: Apr 2008
Posts: 2,935
Thanked 190 Times in 160 Posts
Default

Quote:
Originally Posted by Daffy View Post
Comments about the structure of the code are also welcome.
  1. Code:
    struct tag_t {
    	char artist[LEN];
    	char album[LEN];
    	char year[LEN];
    	char genre[LEN];
    	char track[LEN];
    } taglist;
    This creates a global instance of the structure accessible from anywhere. While there are times that global variables are useful & sometimes even necessary in C programming (given C's limited scoping implementation...), use it sparingly. taglist is the name of the global instance. Consider the following as an alternative:
    Code:
    struct tag_t {
    	char artist[LEN];
    	char album[LEN];
    	char year[LEN];
    	char genre[LEN];
    	char track[LEN];
    };
    int main()
    {
        struct tag_t taglist;
    }
    This limits the scope of taglist to exist only within function main().

  2. Code:
    /* ... */
    void remove_enter( char *s )	/* function to remove enter from stdin */
    {
    	s[strcspn( s, "\n" )] = '\0';
    }
    
    int main(int argc, char *argv[])
    {
    /* ... */
    While it is not required nor a convention that main() be the first function in a file, many programmers do so. However if main() calls other user-defined functions defined in the same file, the compiler may give a warning that it does not understand how to check the function's parameter list unless the function's body has already been seen.

    The solution (& the point I want to make here...) is to use function prototypes. This keeps the compiler happy by providing the parameter list to the function before it is first called or defined. As an example using your earlier code:
    Code:
    /* ... */
    void remove_enter(char *s);
    
    int main(int argc, char *argv[])
    {
        /* ... */
    }
    
    void remove_enter(char *s) 
    {
        /* ... */
    }
  3. Quote:
    ...I decided to achieve it with the se of malloc() and a struct specifier.
    The problem you may be beginning to surmise is that defining all sizes within a file before compilation has its limitations. The purpose of the malloc() family of functions is to allocate memory at runtime. In other words, computation can take place at runtime dependent upon other runtime conditions to allocate the amount of memory needed for a specific situation. No more "one size fits all".

    Having said that, all calls to malloc()or its relatives...) need to ultimately have a corresponding call to free() later in the execution path. Doing so separates newbies from the more experienced. It may not be apparent why this is important, so let me give you a real-world example.

    A company in Silicon Valley about ten years ago was writing a database for a sole customer. The code was being ported from an ancient language only used by this company to C, so the culture didn't have a lot of experience in identifying pitfalls of C programming. In the early phases of development, the database would run fine for a week & then crash. The problem was that memory was being allowed at runtime but never released. Such "memory leaks" consumed all remaining application space, & it was very difficult for that staff to chase down the execution flow in order to find out where memory could (& should...) be deallocated.
    Learning how to use free() now will only help you in the long run.
Reply With Quote
Old 24th November 2011
Daffy Daffy is offline
Fdisk Soldier
 
Join Date: Jun 2010
Posts: 73
Thanked 0 Times in 0 Posts
Default

Ocicat, I cannot thank you enough for your help. I understand fully what you said about the structures and also thanks for the clarification or the function.

Quote:
Originally Posted by ocicat View Post
This is unclear. Please clarify.
When I enter a name (for example in 'artist') and it has 40 characters, taglist.artist stores it properly (with malloc() ). The problem is when I pass the value in char *command[LEN], it stores only the 30 first characters. Do I need to use malloc() on every array object? And if yes, how can I approach this properly? I couldn't find any source about this...

And a last question for free(). When I use it on a char object inside a structure, I target it by name (for this example free(artist)) or by first referring to the structure name (free(taglist.artist))? Is it a good solution to free after use the entire structure by free(taglist)?

I hope I'm making some sense...
Reply With Quote
Old 24th November 2011
ocicat ocicat is offline
Administrator
 
Join Date: Apr 2008
Posts: 2,935
Thanked 190 Times in 160 Posts
Default

Quote:
Originally Posted by Daffy View Post
When I enter a name (for example in 'artist') and it has 40 characters, taglist.artist stores it properly (with malloc() ).
Not from the code posted. From above:
Code:
    printf("Enter artist name:\n");/* ask for input, read to array, remove enter */
    char *artist = (char*) malloc(50 *sizeof(char));
    fgets(taglist.artist, LEN, stdin);
    remove_enter(taglist.artist);
Here, artist gets 50 bytes of heapspace (assuming a character is represented in one byte...) through malloc(), & then nothing is done with the heapspace until it is freed at the end of main(). Any keyboard entry is read directly into the global structure instance which was fully defined at compile-time.

Recognize that the name "artist" is being used in two completely different contexts. One, naming a field within a global structure. The other, a variable defined within main(). To illustrate, study the following example:
Code:
$ cat addresses.c
#include <stdio.h>

struct struct_name {
    int foo;
} global_sn_instance;

int main()
{
    struct struct_name sn_instance; 
    int foo;

    printf("global struct:\t%u\n", (void*) &global_sn_instance.foo);
    printf("local struct:\t%u\n", (void*) &sn_instance.foo);
    printf("local variable:\t%u\n", (void*) &foo);

    return 0;
}
$ gcc addresses.c
$ a.out
global struct:  1006645888
local struct:   3485315520
local variable: 3485315516
$
The addresses of each "foo" is different.
Quote:
The problem is when I pass the value in char *command[LEN], it stores only the 30 first characters.
I suspect you are using code which has not been posted as the last example above, only the artist & track name are being initialized from keyboard input. My recommendation would be add printf() statements after each keyboard entry such that you can verify how input is getting copied about. I would also suggest dumping all elements in your constructed command variable before spawning the child process. These are debugging aides which can be taken out later if desired.
Quote:
Do I need to use malloc() on every array object?
There are two ends to this continuum. Either the entire structure could be allocated from heapspace (memory grabbed at runtime...), or individual fields. Consider the following example:
Code:
$ cat memory.c
#include <stdio.h>
#include <stdlib.h>

struct a {
    char *s;
};

struct b {
    char s[8];
};

int main()
{
    struct a a;
    struct b *p;

    a.s = (char*) malloc(8 * sizeof(char));
    scanf("%s", a.s);

    p = (struct b*) malloc(sizeof(struct b));
    scanf("%s", p->s);
    
    printf("a:\t%s\n", a.s);
    printf("b:\t%s\n", p->s);

    free(a.s);
    free(p);

    return 0;
}
$ gcc memory.c
$ a.out 
abc
xyz
a:      abc
b:      xyz
$
Note that I used the word "continuum". Stranger scenarios can exist, but the code gets further twisted. At this point, you should be making sure you understand the basics before getting too exotic.
Quote:
And a last question for free(). When I use it on a char object inside a structure, I target it by name (for this example free(artist)) or by first referring to the structure name (free(taglist.artist))? Is it a good solution to free after use the entire structure by free(taglist)?
free() should only be called on addresses which have been allocated through malloc() or any of its relatives. Calling free() on anything which has not been allocated through malloc() (or its relatives...) could very well send execution into the weeds. It all depends upon how heapspace management is written for the specific processor. On some platforms, it may work for any pointer. Others, it won't. The processor agnostic answer is to only call free() on objects previously allocated on the heap.

Last edited by ocicat; 24th November 2011 at 04:21 PM.
Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

Similar Threads
Thread Thread Starter Forum Replies Last Post
DragonFly BSD disklabel program changes. J65nko News 0 18th February 2010 01:59 AM
Can't install program guitarscn OpenBSD General 3 31st August 2009 08:40 PM
How to learn to program under BSD? Sunnz Programming 5 24th December 2008 11:45 PM
Learning how to program Solaris_Delta Programming 9 24th December 2008 07:58 PM
run linux's program johnzlly OpenBSD General 38 8th November 2008 12:10 PM


All times are GMT. The time now is 06:53 AM.


Powered by vBulletin® Version 3.8.4
Copyright ©2000 - 2014, Jelsoft Enterprises Ltd.
Content copyright © 2007-2010, the authors
Daemon image copyright ©1988, Marshall Kirk McKusick