The optimizer should eliminate most of the local variables. The tmp pointer is there to make sure strcpy doesn't have to walk the string to find the null. tmp points to the end of result after each call. (See Shlemiel the painter's algorithm for why strcpy can be annoying.)
// You must free the result if result is non-NULL.
char *str_replace(char *orig, char *rep, char *with) {
char *result; // the return string
char *ins; // the next insert point
char *tmp; // varies
int len_rep; // length of rep (the string to remove)
int len_with; // length of with (the string to replace rep with)
int len_front; // distance between rep and end of last rep
int count; // number of replacements
// sanity checks and initialization
if (!orig || !rep)
return NULL;
len_rep = strlen(rep);
if (len_rep == 0)
return NULL; // empty rep causes infinite loop during count
if (!with)
with = "";
len_with = strlen(with);
// count the number of replacements needed
ins = orig;
for (count = 0; (tmp = strstr(ins, rep)); ++count) {
ins = tmp + len_rep;
}
tmp = result = malloc(strlen(orig) + (len_with - len_rep) * count + 1);
if (!result)
return NULL;
// first time through the loop, all the variable are set correctly
// from here on,
// tmp points to the end of the result string
// ins points to the next occurrence of rep in orig
// orig points to the remainder of orig after "end of rep"
while (count--) {
ins = strstr(orig, rep);
len_front = ins - orig;
tmp = strncpy(tmp, orig, len_front) + len_front;
tmp = strcpy(tmp, with) + len_with;
orig += len_front + len_rep; // move to next "end of rep"
}
strcpy(tmp, orig);
return result;
}
Answer from jmucchiello on Stack OverflowThe optimizer should eliminate most of the local variables. The tmp pointer is there to make sure strcpy doesn't have to walk the string to find the null. tmp points to the end of result after each call. (See Shlemiel the painter's algorithm for why strcpy can be annoying.)
// You must free the result if result is non-NULL.
char *str_replace(char *orig, char *rep, char *with) {
char *result; // the return string
char *ins; // the next insert point
char *tmp; // varies
int len_rep; // length of rep (the string to remove)
int len_with; // length of with (the string to replace rep with)
int len_front; // distance between rep and end of last rep
int count; // number of replacements
// sanity checks and initialization
if (!orig || !rep)
return NULL;
len_rep = strlen(rep);
if (len_rep == 0)
return NULL; // empty rep causes infinite loop during count
if (!with)
with = "";
len_with = strlen(with);
// count the number of replacements needed
ins = orig;
for (count = 0; (tmp = strstr(ins, rep)); ++count) {
ins = tmp + len_rep;
}
tmp = result = malloc(strlen(orig) + (len_with - len_rep) * count + 1);
if (!result)
return NULL;
// first time through the loop, all the variable are set correctly
// from here on,
// tmp points to the end of the result string
// ins points to the next occurrence of rep in orig
// orig points to the remainder of orig after "end of rep"
while (count--) {
ins = strstr(orig, rep);
len_front = ins - orig;
tmp = strncpy(tmp, orig, len_front) + len_front;
tmp = strcpy(tmp, with) + len_with;
orig += len_front + len_rep; // move to next "end of rep"
}
strcpy(tmp, orig);
return result;
}
This is not provided in the standard C library because, given only a char* you can't increase the memory allocated to the string if the replacement string is longer than the string being replaced.
You can do this using std::string more easily, but even there, no single function will do it for you.
How to substitute strings in c? Like how sed command works
Replace all occurrences of a substring in a string in C - Stack Overflow
performance - Replace string with another string in C - Code Review Stack Exchange
how to replace a substring in a string using C? - Post.Byes
I want to make a youtube searcher in c, where i will get a string as input and curl it. Curl gives its output as one massive string. I will need to afterwards clean that curl output up to get video titles, uploaders and links. And im very much drawn to use a bunch of bash commands like sed, awk, grep and what not to accomplish this. But i want to know if its possible in c ... So far the only string manipulation ive done is searching trough a string and adding /0 to end the string there. But i would like to know what is the best way to for example replace all chars from the start of a string up untill a specific char is found, and replace with a diffeent set of chars ... Like you would with sed. I also welcome any resources on this topic or any advice
It doesn't seem clear to me what algorithm you are trying to follow, it all looks fishy to me. What's probably the simplest approach is:
- search for first occurrence of the "needle" (searched-for substring)
- copy the part before the first occurrence to the result buffer
- append the replacement string to the result buffer
- increment the
ppointer so it points just after the needle - GOTO 10
void str_replace(char *target, const char *needle, const char *replacement)
{
char buffer[1024] = { 0 };
char *insert_point = &buffer[0];
const char *tmp = target;
size_t needle_len = strlen(needle);
size_t repl_len = strlen(replacement);
while (1) {
const char *p = strstr(tmp, needle);
// walked past last occurrence of needle; copy remaining part
if (p == NULL) {
strcpy(insert_point, tmp);
break;
}
// copy part before needle
memcpy(insert_point, tmp, p - tmp);
insert_point += p - tmp;
// copy replacement string
memcpy(insert_point, replacement, repl_len);
insert_point += repl_len;
// adjust pointers, move on
tmp = p + needle_len;
}
// write altered string back to target
strcpy(target, buffer);
}
Warning: You also have to be careful about how you call your function. If the replacement string is larger than the needle, your modified string will be longer than the original one, so you have to make sure your original buffer is long enough to contain the modified string. E.g.:
char s[1024] = "marie has apples has";
str_replace(s, "has", "blabla");
For starters:
This line
strncpy(buffer, string, p-string);
not necessarily appends a 0-terminator to what had been copied to buffer.
The following line
strcat(buffer, replace);
however relies on buffer being 0-terminated.
As buffer had not been initialised and though the 0-terminator most likely misses the latter line may very well read beyond buffer's memory and with this invoke the infamous Undefined Behaviour.
The fast_strncat() is a good idea. The actual copying isn't efficient (though a good optimiser might recognise the pattern), but letting the caller know how much was copied helps avoid a program being Schlemiel the painter.
I don't like the name; it sounds too much like strncat(), which has very different behaviour. Perhaps len_strcpy() or something better conveys the functionality?
And instead of passing a pointer to result, it's generally better to return the result (either as a number of characters as here - though size_t is probably a better choice of type - or perhaps as a pointer to the new string end).
It's possible we don't need it though - see our use of memcpy in the next section.
The way we're using strstr() is very inefficient. We're traversing input a character at a time, repeating the same search until our position reaches the search result. Imagine this in human terms:
- "Run down the road to the first red house, and tell me where it is."
- It's not this one, so walk ahead to the next house.
- "Run down the road to the first red house, and tell me where it is."
Doesn't this sound a lot like Shlemiel's algorithm, that we worked so hard to avoid earlier?
We could consider using strncmp() instead in the same loop, but we can do better. Once we have the result from strstr(), we know there are no matches up to that result. So we can measure the length change much more simply:
size_t count_old = 0;
for (const char *p = *str; (p = strstr(p, old)); ++p) {
++count_old;
}
And we can copy each chunk in one go:
while (*temp) {
const char *next_match = strstr(temp, old);
if (next_match) {
size_t match_len = next_match - temp;
memcpy(buff+i, temp, match_len);
i += match_len;
temp += match_len;
memcpy(buff+i, new, len_n);
i += len_n;
temp += len_o;
} else {
/* no match - copy the remainder */
strcpy(buff+i, temp);
}
The code is quite limited because it will only work with seekable files, it has to read the entire file into memory, and it needs space for the entire output file in memory before it starts writing.
We can make it more efficient (and avoid the need to seek) by reading a buffer of characters, replacing matches within the buffer, and then writing out the buffer, before repeating. There's a small complication that we need to deal with matches that might straddle two reads - we need to write out all but len_o - 1 characters, and then move those last few to the front of the buffer, before reading the next characters after them. If we know we're working with streams, then there's no need to write to a new string array - just fwrite() each chunk as we get there.
On the other hand, if we want a general function to work on in-memory strings, we can avoid needing separate input and output arrays. It's possible to perform replacement in situ, provided our buffer is big enough to hold the bigger of input and output. The code needs to have two branches - working from the end if the replacement string is bigger than the match string, or from the beginning otherwise. I'd definitely recommend using unit tests to guide implementation if you choose to attempt such in-place substitution.
We have no error checking for the fwrite() and fclose() in main(). These functions can and do fail (e.g. disk quota reached), and we mustn't mislead the user that we succeeded if we haven't written all the output.
Minor things:
- We don't need to include a newline at the end of the message we give to
perror(). That function will append:and error description, then a newline of its own. tempisn't a great name for a variable. It only tells us that it's short-lived, but doesn't indicate what it's for.- There are many redundant casts - I don't think any of the casts here are necessary (Any
Type*converts automatically toType const*- a cast just costs the reader's time, and makes it harder to spot the true problems). - The
if (ptr)test leaves us holding state in our heads for half a screenful - it's easier to read if we testif (!ptr), which has a much shorter block (and also exits the function). sizeof (char)is always 1 (becausesizeofworks in units ofchar), so those expressions can be simplified.
fast_strncat() fails to make a string.
Used within strreplace(), fast_strncat() may work as that code uses zero'd out buffers. But fast_strncat() is not a static function and subject to other's use.
Either make fast_strncat() static or append the null character.
Lack of error checking
Functions like ftell(), fread(), etc. deserve to have the return value checked for errors.
On memory allocation failure, don't die.
strreplace() should return a success/failure error indication. Just like malloc(), let the caller decide.
Minor: Unneeded cast
// strlen((const char *)data)
strlen(data)
Candidate alterative: For long strings, I expect the efficiencies of strlen(), memcpy() will win out.
void fast_strncat_alt(char *dest, const char *src, size_t *dest_len) {
if (dest && src) {
size_t src_len = strlen(src);
memcpy(dest + *dest_len, src, src_len + 1u);
*dest_len += src_len;
}
}
For a single pass, could use the below. This is reasonable when the compiler is smart enough to analyze sprintf(des,t "%s", src) and emit efficient code.
void fast_strncat_alt2(char *dest, const char *src, size_t *dest_len) {
if (dest && src) {
int len = sprintf(dest + *dest_len, "%s", src);
*dest_len += len;
}
}