Related
There are similar questions but my problem is a little more specific. I have a C code that takes file.txt and returns file.txt.rle when I am encoding by using RLE algorithm. Same way I decode that and want to write from file.txt.rle and return file.txt. The following code is what I am using as I go from file.txt to file.txt.rle:
char name[NAME_SIZE];
if(sprintf(name, "%s.rle", argv[1]) >= sizeof(name)){
fprintf(stderr, "Destination file name is too long\n");
}
while((o_fp = fopen(name, "wb")) == NULL){
fprintf(stderr, "Can't create the file to be written\n");
exit(1);
}
How can I accomplish to change the extension from file.txt.rle to file.txt when I decode? A full code won't help because I will use this in a code that decodes an encoded file.
Note: The given will always be in .txt.rle format and the returning file should always convert it to .txt.
You can simply do this with:
strrchr to find where is the last period in string,
strlen / malloc to allocate memory to store the new name,
sprintf to create the new name.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/* this function will create a new name, replacing the existing extension
by the given one.
returned value should be `free()` after usage
/!\ warning:
* validity of parameters is not tested
* return of strdup and malloc are not tested.
*/
char *replace_ext(const char *org, const char *new_ext)
{
char *ext;
/* copy the original file */
char *tmp = strdup(org);
/* find last period in name */
ext = strrchr(tmp , '.');
/* if found, replace period with '\0', thus, we have a shorter string */
if (ext) { *ext = '\0'; }
/* compute the new name size: size of name w/o ext + size of ext + 1
for the final '\0' */
size_t new_size = strlen(tmp) + strlen(new_ext) + 1;
/* allocate memory for new name*/
char *new_name = malloc(new_size);
/* concatenate the two string */
sprintf(new_name, "%s%s", tmp, new_ext);
/* free tmp memory */
free(tmp);
/* return the new name */
return new_name;
}
int main(void)
{
int i;
char *tests[] = { "test.ext", "test.two.ext", "test_no_ext", NULL};
for (i = 0; tests[i]; ++i)
{
char *new_name = replace_ext(tests[i], ".foo");
printf("%s --> %s\n", tests[i], new_name);
free(new_name);
}
return 0;
}
Here is an implementation.
The magic here is carried out by the change_file_name(org, dest, size, ext), that checks whether the name org ends with ext, and in that case copies the name up to that point.
Hope this helps.
/* Changes the name of the sys input file. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
bool change_file_name(const char * org, char * dest, size_t max_length, const char * file_ext)
{
bool toret = false;
const size_t name_length = strlen( org );
const size_t ext_length = strlen( file_ext );
const size_t new_name_size = name_length - ext_length;
if ( name_length > ext_length
&& name_length < max_length
&& strcmp( org + new_name_size, file_ext ) == 0 )
{
strncpy( dest, org, name_length - ext_length );
*( dest + new_name_size ) = 0;
toret = true;
}
return toret;
}
void convert_file(const char * org, const char * dest)
{
printf( "Processing file '%s' into '%s'\n", org, dest );
}
int main(int argc, char *argv[])
{
const int NAME_SIZE = 1024;
const char * rle_ext = ".rle";
char new_name[NAME_SIZE];
int toret = EXIT_SUCCESS;
if ( argc == 2 ) {
if ( change_file_name( argv[ 1 ], new_name, NAME_SIZE, rle_ext ) ) {
printf( "The new name is: '%s'\n", new_name );
convert_file( argv[ 1 ], new_name );
} else {
toret = EXIT_FAILURE;
fprintf( stderr,
"Name results empty, is not ending in '%s' or is too large: '%s'\n",
rle_ext,
argv[ 1 ] );
}
} else {
toret = EXIT_FAILURE;
fprintf( stderr, "Usage: %s <file name>.txt.rle\n", argv[ 0 ] );
}
return toret;
}
You can use strsep (successor to strtok) to tokenize your input filename and copy the parts which you are interested in and discarding the rest.
If your input filename is always of the form file.txt.rle, you can use the below code.
char *name = malloc(sizeof(char) * NAME_SIZE);
if(sprintf(name, "%s.rle", argv[1]) >= sizeof(name)){
fprintf(stderr, "Destination file name is too long\n");
}
char *token = NULL;
char *newfilename = malloc(sizeof(char) * (NAME_SIZE-4)); //strlen(".rle") = 4
uint8_t offset = 0;
memset(newfilename, 0, (NAME_SIZE-4));
while ((token = strsep(&name, ".")) != NULL) {
if(strcmp(token, "rle") == 0) {
break;
}
strncpy(newfilename+offset, token, strlen(token));
offset += strlen(token);
newfilename[offset] = '.';
offset += 1;
}
newfilename[strlen(newfilename)-1] = '\0';
The point of the script is to take three parameters. Find, replace, prefix. Find being the text to replace, replace being what to replace the text with, and prefix is a special case. If prefix is in the text, you replace the prefix (some text) with prefix+replace. I would like to know why the below code throws a error right after saying opened file. It only seems to throw an error if the text being replaced is repeated like "aaa", "bbb" where "a" is what is being replaced.
Opened file.txt
*** Error in `./a.out': malloc(): memory corruption: 0x00005652fbc55980 ***
There's also the occasionally seg fault after printing "Trying to replace for file ...". I'm not fluent in C and GDB on my system resulted in just missing library errors which has nothing to do with this.
Here is the code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
char concat(const char *s1, const char *s2)
{
char *result = calloc(strlen(s1)+strlen(s2)+1, 1);
strcpy(result, s1);
strcat(result, s2);
printf("Prefix will be replaced with %s.\n", result);
return result;
}
static int replaceString(char *buf, const char *find, const char *replace, const char *prefix)
{
int olen, rlen;
char *s, *d;
char *tmpbuf;
if (!buf || !*buf || !find || !*find || !replace)
return 0;
tmpbuf = calloc(strlen(buf) + 1, 1);
if (tmpbuf == NULL)
return 0;
olen = strlen(find);
rlen = strlen(replace);
s = buf;
d = tmpbuf;
while (*s) {
if (strncmp(s, find, olen) == 0) {
strcpy(d, replace);
s += olen;
d += rlen;
}
else
{
*d++ = *s++;
}
}
*d = '\0';
if(strcmp(buf, tmpbuf) == 0)
{
free(tmpbuf);
return 0;
}
else
{
strcpy(buf, tmpbuf);
free(tmpbuf);
printf("%s", buf);
printf("Replaced!\n");
return 1;
}
}
void getAndReplace(char* filename, char* find, char* replace, char* prefix)
{
long length;
FILE* f = fopen (filename, "r");
char* buffer = 0;
if (f)
{
fseek (f, 0, SEEK_END);
length = ftell (f);
fseek (f, 0, SEEK_SET);
buffer = calloc(length+1, 1); //If i use malloc here, any file other than the first has garbage added to it. Why?
if (buffer)
{
fread(buffer, 1, length, f);
}
fclose(f);
}
if(buffer)// && strlen(buffer) > 1)
{
int result = replaceString(buffer, find, replace, prefix);
if(result == 0)
{
printf("Trying to replace prefix.\n");
replace = concat(prefix, replace);
result = replaceString(buffer, prefix, replace, "");
}
else
{
printf("Successfully replaced %s with %s\n", find, replace);
}
if(result == 1)
{
FILE* fp = fopen(filename, "w+");
if(fp)
{
printf("Opened %s\n", filename);
fprintf(fp, buffer);
fclose(fp);
printf("File %s overwritten with changes.\n", filename);
}
}
else
{
printf("Nothing to replace for %s\n", filename);
}
}
else
{
printf("Empty file.");
}
if(buffer)
{
free(buffer);
}
}
int main(int argc, char **argv)
{
if(argc < 4)
{
printf("Not enough arguments given: ./hw3 <find> <replace> <prefix>\n");
return 1;
}
struct dirent *de;
DIR *dr = opendir(".");
if (dr == NULL)
{
printf("Could not open current directory\n");
return 0;
}
while ((de = readdir(dr)) != NULL)
{
if(strlen(de->d_name) > 4 && !strcmp(de->d_name + strlen(de->d_name) - 4, ".txt"))
{
printf("Trying to replace for file %s\n", de->d_name);
getAndReplace(de->d_name, argv[1], argv[2], argv[3]);
}
}
closedir(dr);
return 0;
}
I hope that you concat function
char concat(const char *s1, const char *s2);
is just a typo and you meant
char *concat(const char *s1, const char *s2);
otherwise the function would be returning a pointer as if it were a char.
Using valgrind would give more details where exactly you are reading/writing where you are not allowed to and
where you are leaking memory. Without that it's hard to pinpoint the exact
place. One thing I noticed is that depending on the length of find and replace,
you might not have enough memory for tmpbuf which would lead to a buffer
overflow.
I think that the best way to write the replaceString is by making it
allocate the memory it needs itself, rather than providing it a buffer to write into.
Because you are getting both find and replace from the user, you don't know
how large the resulting buffer will need to be. You could calculate it
beforehand, but you don't do that. If you want to pass a pre-allocated buffer to
replaceString, I'd pass it as a double pointer, so that replaceString can do
realloc on it when needed. Or allocate the memory in the function and return a
pointer to the allocated memory.
This would be my version:
char *replaceString(const char *haystack, const char *needle, const char *replace)
{
if(haystack == NULL || needle == NULL || replace == NULL)
return NULL;
char *dest = NULL, *tmp;
size_t needle_len = strlen(needle);
size_t replace_len = strlen(replace);
size_t curr_len = 0;
while(*haystack)
{
char *found = strstr(haystack, needle);
size_t copy_len1 = 0;
size_t new_size = 0;
size_t pre_found_len = 0;
if(found == NULL)
{
copy_len1 = strlen(haystack) + 1;
new_size = curr_len + copy_len1;
} else {
pre_found_len = found - haystack;
copy_len1 = pre_found_len;
new_size = curr_len + pre_found_len + replace_len + 1;
}
tmp = realloc(dest, new_size);
if(tmp == NULL)
{
free(dest);
return NULL;
}
dest = tmp;
strncpy(dest + curr_len, haystack, copy_len1);
if(found == NULL)
return dest; // last replacement, copied to the end
strncpy(dest + curr_len + pre_found_len, replace, replace_len + 1);
curr_len += pre_found_len + replace_len;
haystack += pre_found_len + needle_len;
}
return dest;
}
The idea in this version is similar to yours, but mine reallocates the memory as
it goes. I changed the name of the arguments to have the same name as the
strstr function does based on my documentation:
man strstr
char *strstr(const char *haystack, const char *needle);
Because I'm going to update haystack to point past the characters copied, I
use this loop:
while(*haystack)
{
...
}
which means it is going to stop when the '\0'-terminating byte is reached.
The first thing is to use strstr to locate a substring that matches needle.
Base on whether a substring is found, I calculate how much bytes I would need to
copy until the substring, and the new size of the buffer. After that I
reallocate the memory for the buffer and copy everything until the substring,
then append the replacement, update the curr_len variable and update the
haystack pointer to point past the substring.
If the substring is not found, no more replacements are needed. So we have to
copy the string pointed to by haystack and return the constructed string. The
new size of the destination is curr_len + strlen(haystack) + 1 (the +1
because I want the strncpy function to also copy the '\0'-terminating byte).
And it has to copy strlen(haystack) + 1 bytes. After the first strncpy, the
function returns dest.
If the substring is found, then we have to copy everything until the substring,
append the replacement and update the current length and the haystack pointer.
First I calculate the string until the found substring and save it in
pre_found_len. The new size of the destination will be
curr_len + pre_found_len + replace_len + 1 (the current length + length of
string until substring + the length of the replacement + 1 for the
'\0'-terminating byte). Now the first strncpy copies only pre_found_len
bytes. Then it copies the replacement.
Now you can call it like this:
int main(void)
{
const char *orig = "Is this the real life? Is this just fantasy?";
char *text = replaceString(orig, "a", "_A_");
if(text)
{
puts(orig);
puts(text);
}
free(text);
}
which will output:
Is this the real life? Is this just fantasy?
Is this the re_A_l life? Is this just f_A_nt_A_sy?
Now you can use this function in getAndReplace to replace the prefix:
char *getAndReplace(char* filename, char* find, char* replace, char* prefix)
{
...
char *rep1 = replaceString(buffer, find, replace);
if(rep1 == NULL)
{
// error
free(buffer);
return NULL;
}
char *prefix_rep = malloc(strlen(replace) + strlen(prefix) + 1);
if(prefix_rep == NULL)
{
// error
free(buffer);
free(rep1);
return NULL;
}
sprintf(prefix_rep, "%s%s", replace, prefix);
char *rep2 = replaceString(rep1, prefix, prefix_rep);
if(rep2 == NULL)
{
// error
free(buffer);
free(rep1);
free(prefix_rep);
return NULL;
}
// rep2 has all the replacements
...
// before leaving
free(buffer);
free(rep1);
free(prefix_rep);
// returning all replacements
return rep2;
}
When using malloc & co, don't forget to check if they return NULL and don't
forget to free the memory when not needed.
I am trying to setup a local folder access from a c program by first initializing a string to the location the subsequently reading files from there. I'll also be required to write files in a similar manner eventually, but I am getting some strange bugs.
first the code:
resource_utils.h
static char* res_dir = NULL;
void clearnUpResourcePath(void);
char* getResource(char* filename);
char* readFile(char* file_path);
void writeFile(filename, File* file);
resource_utils.c
#include "resource_utils.h"
static char* getBasePath(void);
static void initResourcePath(void);
char* getResource(char* filename)
{
if(res_dir == NULL)
{
initResourcePath();
}
printf("res dir: %s || fn:%s\n",res_dir, filename);
char* str = (char*)malloc(sizeof(char) + strlen(res_dir) + strlen(filename));
memcpy(str, res_dir, strlen(res_dir)+1);
memcpy(str + strlen(res_dir), filename, strlen(filename));
str[(strlen(filename) + strlen(res_dir))] = '\0';
printf("resource filename:%s\n\n",str);
return str;
}
static void initResourcePath(void) {
char *base_path = getBasePath();
size_t len = strlen(base_path) + 22;
size_t i = 0;
size_t sz = 0;
char tmp[len];
while(base_path[i] != '\0')
{
if(base_path[i] == 'b' && base_path[i+1] == 'i' && base_path[i+2] == 'n')
{
sz = i;
}
tmp[i] = base_path[i];
i++;
}
char* b = strstr(tmp, "/bin");
memcpy(b, "/bin/resources/",15);
tmp[ sz + 14 ] = '\0';
res_dir = (char*)malloc(sizeof(char) * (sz + 4));
i = 0;
while(tmp[i] != '\0')
{
res_dir[i] = tmp[i];
i++;
}
res_dir[i] = '\0';
free(base_path);
}
void clearnUpResourcePath(void)
{
free(res_dir);
}
static char* getBasePath(void)
{
return "string to working directory"
}
char* readFile(char* file_path)
{
FILE* fp = fopen(file_path, "r");
if( fp == NULL )
{
perror("Error while opening the file.\n");
printf("failed to open file path:%s\n",file_path);
exit(EXIT_FAILURE);
}
size_t size = 1024;
char ch;
int index = 0;
char* line = (char*)malloc(sizeof(char) * size);
while( (ch = (char)fgetc(fp)) != EOF )
{
*(line+index) = ch;
++index;
if(index == size-1)
{
size = size * 2;
line = realloc(line, size);
printf("\nreallocing %zu\n",size);
}
line = realloc(line, (sizeof(char) * index) + 1);
*(line+index) = '\0';
}
//printf("sanity check\n\n%d\n\n",strlen(line));
//printf("final size: %lu for loading: %s\n",strlen(line), file_path);
fclose(fp);
return line;
}
This is basically suppose to setup this resource path once, keep it around for the lifetime of the program and free it before the program exits, but I get some strange results sometimes.
Take a look at this output
char* vshad = getResource("vert.shad");
char* fshad = getResource("frag.shad");
char* name = getResource("pal.ppm");
char* name1 = getResource("1234pal.ppm");
char* name2 = getResource("pal.ppm1234");
char* name3 = getResource("pal1.ppm");
char* name4 = getResource("pal.pp");
char* name5 = getResource("pal.ppdddddm");
char* name6 = getResource("pa");
res dir: /Users/username/DIRECTORY/project/build/bin/resources/ || fn:vert.shad
res dir len:48, filename len:9
resource filename:/Users/username/DIRECTORY/project/build/bin/resources/vert.shad
res dir: /Users/username/DIRECTORY/project/build/bin/resources/ || fn:frag.shad
res dir len:48, filename len:9
resource filename:/Users/username/DIRECTORY/project/build/bin/resources/frag.shad
res dir: /Users/username/DIRECTORY/project/build/bin/resources/FACETKEYS || fn:pal.ppm
res dir len:57, filename len:7
resource filename:/Users/username/DIRECTORY/project/build/bin/resources/FACETKEYSpal.ppm
res dir: /Users/username/DIRECTORY/project/build/bin/resources/FACETKEYS || fn:1234pal.ppm
res dir len:57, filename len:11
resource filename:/Users/username/DIRECTORY/project/build/bin/resources/FACETKEYS1234pal.ppm
res dir: /Users/username/DIRECTORY/project/build/bin/resources/FACETKEYS || fn:pal.ppm1234
res dir len:57, filename len:11
resource filename:/Users/username/DIRECTORY/project/build/bin/resources/FACETKEYSpal.ppm1234
res dir: /Users/username/DIRECTORY/project/build/bin/resources/FACETKEYS || fn:pal1.ppm
res dir len:57, filename len:8
resource filename:/Users/username/DIRECTORY/project/build/bin/resources/FACETKEYSpal1.ppm
res dir: /Users/username/DIRECTORY/project/build/bin/resources/FACETKEYS || fn:pal.pp
res dir len:57, filename len:6
resource filename:/Users/username/DIRECTORY/project/build/bin/resources/FACETKEYSpal.pp
res dir: /Users/username/DIRECTORY/project/build/bin/resources/FACETKEYS || fn:pal.ppdddddm
res dir len:57, filename len:12
resource filename:/Users/username/DIRECTORY/project/build/bin/resources/FACETKEYSpal.ppdddddm
res dir: /Users/username/DIRECTORY/project/build/bin/resources/FACETKEYS || fn:pa
res dir len:57, filename len:2
resource filename:/Users/username/DIRECTORY/project/build/bin/resources/FACETKEYSpa
loaded name:/Users/username/DIRECTORY/project/build/bin/resources/FACETKEYSpal.ppm?
the first two calls load properly but subsequent calls, especially if i change the length of the filename causes the code to break. I am not really sure.
I also noticed that the length of my res_dir changes which I really don't understand.
along with the accepted answer, here is my new code w/o magic numbers
static void initResourcePath(void)
{
char *base_path = getBasePath();
size_t len = strlen(base_path) *2; //making enough space
size_t i, sz = 0;
char tmp[len];
while(base_path[i] != '\0')
{
if(base_path[i] == 'b' && base_path[i+1] == 'i' && base_path[i+2] == 'n')
{
sz = i;
}
tmp[i] = base_path[i];
i++;
}
char* b = strstr(tmp, "/bin");
memcpy(b, "/bin/resources/",15);
tmp[ sz + 14 ] = '\0';
res_dir = (char*)malloc(sizeof(char) * (strlen(tmp) +1));
strcpy(res_dir, tmp);
free(base_path);
}
The problem you're having is here:
res_dir = (char*)malloc(sizeof(char) * (sz + 4));
You're not allocating enough space. You probably meant to use sz + 14 instead of sz + 4. That's one of the issues with using magic numbers as WhozCraig mentioned.
Rather than doing something convoluted like that, you know that you're copying tmp into res_dir, so do this instead:
res_dir = malloc(strlen(tmp)+1);
Note that the return value of malloc is not being casted. Doing so in C can hide subtle bugs if you fail to #include <stdlib.h>.
I have an assignment for a class where I have a text file, bikes.txt. The contents are attributes of bikes like so:
bike_id=16415
bike_station_id=455
bike_status=free
bike_id=6541
bike_station_id=1
bike_status=reserved
bike_id=5
bike_station_id=6451
bike_status=reserved
Right now I'm trying to read all bike_id's, and I have a question:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char* getAttribute(const char* attr, const char* line);
int main(int argc, char** argv)
{
FILE* file = fopen("bikes.txt", "r");
if (file != NULL)
{
char line[256];
while (fgets(line, sizeof(line), file))
{
if (strstr(line, "bike_id=") != NULL)
{
char* bikeIdText = getAttribute("bike_id", line);
printf("\"%s\"", bikeIdText);
//free(bikeIdText);
//bikeIdText = NULL;
}
}
}
}
char* getAttribute(const char* attr, const char* line)
{
int lineLength = strlen(line);
int attrLength = strlen(attr);
// +1 because of "="
char* attrText = malloc(lineLength - attrLength + 1);
// +2 because of "=" and NEWLINE
memcpy(attrText, line + attrLength + 1, lineLength - (attrLength + 2));
return attrText;
}
The above code works. The output is:
"16415""6541""5"
The problem is that - if I'm right - the getAttribute() function will allocate more and more memory which won't get freed.
However, if I uncomment the free(bikeIdText); and bikeIdText = NULL; lines in main(), the output shows that the same memory location is used, because the longer values won't get overwritten by shorter ones.
The output in this case:
"16415""65415""55415"
How could I solve the problem?
This
char* attrText = malloc(lineLength - attrLength + 1);
shall be
char * attrText = malloc(lineLength - (attrLength + 1));
attrText[lineLength - (attrLength + 1) - 1] = '\0' ;
or the equivalent
char * attrText = malloc(lineLength - attrLength - 1);
attrText[lineLength - attrLength - 2] = '\0' ;
This assumes line to end with one addtional character.
Last character of the string is not set to '\0' therefore, the "%s" prints more than it should (%s stops output at \0 byte).
Try to malloc one byte more char* attrText = malloc(lineLength - attrLength + 2) and set, attrText[lineLength - attrLength + 1] = '\0' before return.
So we have a path string /home/user/music/thomas.mp3.
Where is the easy way to extract file name(without extension, "thomas") and it's extension ("mp3") from this string? A function for filename, and for extension. And only GNU libc in our hands.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_FILENAME_SIZE 256
char *filename(char *str) {
char *result;
char *last;
if ((last = strrchr(str, '.')) != NULL ) {
if ((*last == '.') && (last == str))
return str;
else {
result = (char*) malloc(MAX_FILENAME_SIZE);
snprintf(result, sizeof result, "%.*s", (int)(last - str), str);
return result;
}
} else {
return str;
}
}
char *extname(char *str) {
char *result;
char *last;
if ((last = strrchr(str, '.')) != NULL) {
if ((*last == '.') && (last == str))
return "";
else {
result = (char*) malloc(MAX_FILENAME_SIZE);
snprintf(result, sizeof result, "%s", last + 1);
return result;
}
} else {
return ""; // Empty/NULL string
}
}
Use basename to get the filename and then you can use something like this to get the extension.
char *get_filename_ext(const char *filename) {
const char *dot = strrchr(filename, '.');
if(!dot || dot == filename) return "";
return dot + 1;
}
Edit:
Try something like.
#include <string.h>
#include <libgen.h>
static void printFileInfo(char *path) {
char *bname;
char *path2 = strdup(path);
bname = basename(path2);
printf("%s.%s\n",bname, get_filename_ext(bname));
free(path2);
}
Regarding your actual code (all the other answers so far say to scrap that and do something else, which is good advice, however I am addressing your code as it contains blunders that it'd be good to learn about in advance of next time you try to write something).
Firstly:
strncpy(str, result, (size_t) (last-str) + 1);
is not good. You have dest and src around the wrong way; and further this function does not null-terminate the output (unless the input is short enough, which it isn't). Generally speaking strncpy is almost never a good solution to a problem; either strcpy if you know the length, or snprintf.
Simpler and less error-prone would be:
snprintf(result, sizeof result, "%.*s", (int)(last - str), str);
Similary in the other function,
snprintf(result, sizeof result, "%s", last + 1);
The snprintf function never overflows buffer and always produces a null-terminated string, so long as you get the buffer length right!
Now, even if you fixed those then you have another fundamental problem in that you are returning a pointer to a buffer that is destroyed when the function returns. You could fix ext by just returning last + 1, since that is null-terminated anyway. But for filename you have the usual set of options:
return a pointer and a length, and treat it as a length-counted string, not a null-terminated one
return pointer to mallocated memory
return pointer to static buffer
expect the caller to pass in a buffer and a buffer length, which you just write into
Finally, returning NULL on failure is probably a bad idea; if there is no . then return the whole string for filename, and an empty string for ext. Then the calling code does not have to contort itself with checks for NULL.
Here is a routine I use for that problem:
Separates original string into separate strings of path, file_name and extension.
Will work for Windows and Linux, relative or absolute style paths. Will handle directory names with embedded ".". Will handle file names without extensions.
/////////////////////////////////////////////////////////
//
// Example:
// Given path == "C:\\dir1\\dir2\\dir3\\file.exe"
// will return path_ as "C:\\dir1\\dir2\\dir3"
// Will return base_ as "file"
// Will return ext_ as "exe"
//
/////////////////////////////////////////////////////////
void GetFileParts(char *path, char *path_, char *base_, char *ext_)
{
char *base;
char *ext;
char nameKeep[MAX_PATHNAME_LEN];
char pathKeep[MAX_PATHNAME_LEN];
char pathKeep2[MAX_PATHNAME_LEN]; //preserve original input string
char File_Ext[40];
char baseK[40];
int lenFullPath, lenExt_, lenBase_;
char *sDelim={0};
int iDelim=0;
int rel=0, i;
if(path)
{ //determine type of path string (C:\\, \\, /, ./, .\\)
if( (strlen(path) > 1) &&
(
((path[1] == ':' ) &&
(path[2] == '\\'))||
(path[0] == '\\') ||
(path[0] == '/' ) ||
((path[0] == '.' ) &&
(path[1] == '/' ))||
((path[0] == '.' ) &&
(path[1] == '\\'))
)
)
{
sDelim = calloc(5, sizeof(char));
/* // */if(path[0] == '\\') iDelim = '\\', strcpy(sDelim, "\\");
/* c:\\ */if(path[1] == ':' ) iDelim = '\\', strcpy(sDelim, "\\"); // also satisfies path[2] == '\\'
/* / */if(path[0] == '/' ) iDelim = '/' , strcpy(sDelim, "/" );
/* ./ */if((path[0] == '.')&&(path[1] == '/')) iDelim = '/' , strcpy(sDelim, "/" );
/* .\\ */if((path[0] == '.')&&(path[1] == '\\')) iDelim = '\\' , strcpy(sDelim, "\\" );
/* \\\\ */if((path[0] == '\\')&&(path[1] == '\\')) iDelim = '\\', strcpy(sDelim, "\\");
if(path[0]=='.')
{
rel = 1;
path[0]='*';
}
if(!strstr(path, ".")) // if no filename, set path to have trailing delim,
{ //set others to "" and return
lenFullPath = strlen(path);
if(path[lenFullPath-1] != iDelim)
{
strcat(path, sDelim);
path_[0]=0;
base_[0]=0;
ext_[0]=0;
}
}
else
{
nameKeep[0]=0; //works with C:\\dir1\file.txt
pathKeep[0]=0;
pathKeep2[0]=0; //preserves *path
File_Ext[0]=0;
baseK[0]=0;
//Get lenth of full path
lenFullPath = strlen(path);
strcpy(nameKeep, path);
strcpy(pathKeep, path);
strcpy(pathKeep2, path);
strcpy(path_, path); //capture path
//Get length of extension:
for(i=lenFullPath-1;i>=0;i--)
{
if(pathKeep[i]=='.') break;
}
lenExt_ = (lenFullPath - i) -1;
base = strtok(path, sDelim);
while(base)
{
strcpy(File_Ext, base);
base = strtok(NULL, sDelim);
}
strcpy(baseK, File_Ext);
lenBase_ = strlen(baseK) - lenExt_;
baseK[lenBase_-1]=0;
strcpy(base_, baseK);
path_[lenFullPath -lenExt_ -lenBase_ -1] = 0;
ext = strtok(File_Ext, ".");
ext = strtok(NULL, ".");
if(ext) strcpy(ext_, ext);
else strcpy(ext_, "");
}
memset(path, 0, lenFullPath);
strcpy(path, pathKeep2);
if(rel)path_[0]='.';//replace first "." for relative path
free(sDelim);
}
}
}
Here is an old-school algorithm that will do the trick.
char path[100] = "/home/user/music/thomas.mp3";
int offset_extension, offset_name;
int len = strlen(path);
int i;
for (i = len; i >= 0; i--) {
if (path[i] == '.')
break;
if (path[i] == '/') {
i = len;
break;
}
}
if (i == -1) {
fprintf(stderr,"Invalid path");
exit(EXIT_FAILURE);
}
offset_extension = i;
for (; i >= 0; i--)
if (path[i] == '/')
break;
if (i == -1) {
fprintf(stderr,"Invalid path");
exit(EXIT_FAILURE);
}
offset_name = i;
char *extension, name[100];
extension = &path[offset_extension+1];
memcpy(name, &path[offset_name+1], offset_extension - offset_name - 1);
Then you have both information under the variables name and extension
printf("%s %s", name, extension);
This will print:
thomas mp3
I know this is old. But I tend to use strtok for things like this.
/* strtok example */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX_TOKENS 20 /* Some reasonable values */
#define MAX_STRING 128 /* Easy enough to make dynamic with mallocs */
int main ()
{
char str[] ="/home/user/music/thomas.mp3";
char sep[] = "./";
char collect[MAX_TOKENS][MAX_STRING];
/* Not really necessary, since \0 is added inplace. I do this out of habit. */
memset(collect, 0, MAX_TOKENS * MAX_STRING);
char * pch = strtok (str, sep);
int ccount = 0;
if(pch != NULL) {
/* collect all seperated text */
while(pch != NULL) {
strncpy( collect[ccount++], pch, strlen(pch));
pch = strtok (NULL, sep);
}
}
/* output tokens. */
for(int i=0; i<ccount; ++i)
printf ("Token: %s\n", collect[i]);
return 0;
}
This is a rough example, and it makes it easy to deal with the tokens afterwards. Ie the last token is the extension. Second last is the basename and so on.
I also find it useful for rebuilding paths for different platforms - replace / with \.