Report errors to error output stream
Error messages should be written to stderr, not stdout. We should also return a non-zero value when we fail - ideally EXIT_FAILURE, defined in <stdlib.h>:
if (argc < 2) {
fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
return EXIT_FAILURE;
}
Note also that I've interpolated argv[0], so as to print the actual name we were invoked as, rather than having to match the code and the build system.
Accept many arguments
Users would like to be able to operate on multiple files at once:
NoSpace *
We can support this (and I've hinted at this above, if you spotted the test argc < 2) by looping over the arguments:
for (int i = 1; i < argc; ++i) {
const char *const src = argv[i];
unspace(src);
}
No-action-required is not an error
Possibly an opinion point, but I would suggest that a file that doesn't need any replacement shouldn't be considered a failure.
Avoid overwriting existing files
We don't want to accidentally destroy data if the target filename already exists. For Linux, we can avoid this without risk of a race between checking and acting by using the renameat2() system call:
renameat2(AT_FDCWD, file_name_old, AT_FDCWD, file_name_new, RENAME_NOREPLACE);
Buffer size (BUG)
We allocate strlen(src) characters as buffer, but we need one more, to account for the terminating NUL character. Note that strlen returns a size_t, not an int (although it's unlikely to make a practical difference here).
Replace during copy
We can perform the substitution as we copy:
char *const src = argv[i];
size_t filename_size = strlen(argv[i]);
char dest[filename_size+1];
for (char *p = src, *q = dest; *p; ++p, ++q)
if (*p == ' ')
*q = '_';
else
*q = *p;
dest[filename_size] = '\0';
More compactly:
for (char *p = src, *q = dest; *p; ++p, ++q)
*q = *p == ' ' ? '_' : *p;
Note that we use character constants ' ' and '_', so our code works in non-ASCII environments and so that its intention is clear without needing comments.
Check the result of the rename() call
The library call may fail - perhaps the destination name exists (and we're using the RENAME_NOREPLACE flag), or the directory is not writable.
if (renameat2(AT_FDCWD, src, AT_FDCWD, dest, RENAME_NOREPLACE)) {
perror(src);
}
We can make this more portable, by testing whether it's supported:
#if _POSIX_C_SOURCE >= 200809L
#define rename(src, dest) renameat2(AT_FDCWD, src, AT_FDCWD, dest, RENAME_NOREPLACE);
#endif
Complete program
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if _POSIX_C_SOURCE >= 200809L
#define rename(src, dest) renameat2(AT_FDCWD, src, AT_FDCWD, dest, RENAME_NOREPLACE);
#endif
int main(int argc, char *argv[])
{
if (argc < 2) {
fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
return EXIT_FAILURE;
}
int ret_val = EXIT_SUCCESS;
for (int i = 1; i < argc; ++i) {
char *const src = argv[i];
size_t filename_size = strlen(argv[i]);
char dest[filename_size+1];
for (char *p = src, *q = dest; *p; ++p, ++q)
*q = *p == ' ' ? '_' : *p;
dest[filename_size] = '\0';
if (rename(src, dest)) {
perror(src);
ret_val = EXIT_FAILURE;
}
}
return ret_val;
}
Enhancement suggestions
You might want to look into accepting some option flags to adjust behaviour. I suggest the following (modelled on the mv command):
-vfor verbose output: print a line for every file successfully moved-fto overwrite existing files without checking-ito ask before overwriting an existing file
Report errors to error output stream
Error messages should be written to stderr, not stdout. We should also return a non-zero value when we fail - ideally EXIT_FAILURE, defined in <stdlib.h>:
if (argc < 2) {
fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
return EXIT_FAILURE;
}
Note also that I've interpolated argv[0], so as to print the actual name we were invoked as, rather than having to match the code and the build system.
Accept many arguments
Users would like to be able to operate on multiple files at once:
NoSpace *
We can support this (and I've hinted at this above, if you spotted the test argc < 2) by looping over the arguments:
for (int i = 1; i < argc; ++i) {
const char *const src = argv[i];
unspace(src);
}
No-action-required is not an error
Possibly an opinion point, but I would suggest that a file that doesn't need any replacement shouldn't be considered a failure.
Avoid overwriting existing files
We don't want to accidentally destroy data if the target filename already exists. For Linux, we can avoid this without risk of a race between checking and acting by using the renameat2() system call:
renameat2(AT_FDCWD, file_name_old, AT_FDCWD, file_name_new, RENAME_NOREPLACE);
Buffer size (BUG)
We allocate strlen(src) characters as buffer, but we need one more, to account for the terminating NUL character. Note that strlen returns a size_t, not an int (although it's unlikely to make a practical difference here).
Replace during copy
We can perform the substitution as we copy:
char *const src = argv[i];
size_t filename_size = strlen(argv[i]);
char dest[filename_size+1];
for (char *p = src, *q = dest; *p; ++p, ++q)
if (*p == ' ')
*q = '_';
else
*q = *p;
dest[filename_size] = '\0';
More compactly:
for (char *p = src, *q = dest; *p; ++p, ++q)
*q = *p == ' ' ? '_' : *p;
Note that we use character constants ' ' and '_', so our code works in non-ASCII environments and so that its intention is clear without needing comments.
Check the result of the rename() call
The library call may fail - perhaps the destination name exists (and we're using the RENAME_NOREPLACE flag), or the directory is not writable.
if (renameat2(AT_FDCWD, src, AT_FDCWD, dest, RENAME_NOREPLACE)) {
perror(src);
}
We can make this more portable, by testing whether it's supported:
#if _POSIX_C_SOURCE >= 200809L
#define rename(src, dest) renameat2(AT_FDCWD, src, AT_FDCWD, dest, RENAME_NOREPLACE);
#endif
Complete program
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if _POSIX_C_SOURCE >= 200809L
#define rename(src, dest) renameat2(AT_FDCWD, src, AT_FDCWD, dest, RENAME_NOREPLACE);
#endif
int main(int argc, char *argv[])
{
if (argc < 2) {
fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
return EXIT_FAILURE;
}
int ret_val = EXIT_SUCCESS;
for (int i = 1; i < argc; ++i) {
char *const src = argv[i];
size_t filename_size = strlen(argv[i]);
char dest[filename_size+1];
for (char *p = src, *q = dest; *p; ++p, ++q)
*q = *p == ' ' ? '_' : *p;
dest[filename_size] = '\0';
if (rename(src, dest)) {
perror(src);
ret_val = EXIT_FAILURE;
}
}
return ret_val;
}
Enhancement suggestions
You might want to look into accepting some option flags to adjust behaviour. I suggest the following (modelled on the mv command):
-vfor verbose output: print a line for every file successfully moved-fto overwrite existing files without checking-ito ask before overwriting an existing file
Although this indeed does something similar to your goal, here are some criticisms / improvements:
- There's absolutely no need to take a copy of the original filename.
- If you meet an error condition, return 1 (or better
EXIT_FAILUREfromstdlib.h), so scripts calling your tool get a chance to detect the error. - Use functions; this helps your code to keep structure
- Your tool will not work correctly for filenames with paths, so better search from the end and stop replacing when you hit a path separator character.
- When iterating through a string, it's often easier to just use pointers instead of array indexing.
I'd suggest something like this:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
#define isPathSep(x) ((x) == '\\' || (x) == '/')
#else
#define isPathSep(x) ((x) == '/')
#endif
char *sanitizeFileName(const char *filename)
{
size_t namelen = strlen(filename);
char *sanitized = malloc(namelen + 1);
if (!sanitized)
{
perror("malloc");
exit(EXIT_FAILURE);
}
strcpy(sanitized, filename);
char *p = sanitized + namelen - 1;
while (p >= sanitized)
{
if (isPathSep(*p)) break;
if (*p == ' ') *p = '_';
--p;
}
return sanitized;
}
int main(int argc, char **argv)
{
if (argc != 2)
{
fprintf(stderr, "Usage: %s [filename]\n", argv[0]);
return EXIT_FAILURE;
}
char *safename = sanitizeFileName(argv[1]);
// instead of rename(), for demonstration:
printf("%s -> %s\n", argv[1], safename);
free(safename);
return EXIT_SUCCESS;
}
Whitespace replace in C language - Stack Overflow
c++ - Replace space with an underscore - Stack Overflow
How to replace space(s) in a string by another charater like and underscore (_)? - Statalist
How can I replace a space in a string with an underscore in C#? - Stack Overflow
Check if the character is a space if yes, then replace it with _.
For example:
#include <stdio.h>
#include <ctype.h>
int main ()
{
int i=0;
unsigned char str[]="a ";
while (str[i])
{
if (isspace(str[i]))
str[i]='_';
i++;
}
printf("%s\n",str);
return 0;
}
A simple manner for character substitution is simply to create a pointer to the string and then check each character in the string for value x and replace it with character y as you go. An example would be:
#include <stdio.h>
int main (void)
{
char passcode[] = "a ";
char *ptr = passcode;
while (*ptr)
{
if (*ptr == ' ')
*ptr = '_';
ptr++;
}
printf ("\n passcode: %s\n\n", passcode);
return 0;
}
output:
$ ./bin/chrep
passcode: a_
You've got your getline issue fixed but I just wanted to say the Standard Library contains a lot of useful functions. Instead of a hand-rolled loop you could do:
std::string space2underscore(std::string text)
{
std::replace(text.begin(), text.end(), ' ', '_');
return text;
}
This works, it's fast, and it actually expresses what you are doing.
The problem is that cin >> word is only going to read in the first word. If you want to operate on a whole like at a time, you should use std::getline.
For example:
std::string s;
std::getline(std::cin, s);
s = space2underscore(s);
std::cout << s << std::endl;
Also, you may want to check that you actually were able to read a line. You can do that like this:
std::string s;
if(std::getline(std::cin, s)) {
s = space2underscore(s);
std::cout << s << std::endl;
}
Finally, as a side note, you could probably write your function in a cleaner way. Personally I would write it like this:
std::string space2underscore(std::string text) {
for(std::string::iterator it = text.begin(); it != text.end(); ++it) {
if(*it == ' ') {
*it = '_';
}
}
return text;
}
Or for bonus points, use std::transform!
EDIT:
If you happen to be lucky enough to be able to use c++0x features (and I know that's a big if) you could use lambdas and std::transform, which results in some very simple code:
std::string s = "hello stackoverflow";
std::transform(s.begin(), s.end(), s.begin(), {
return ch == ' ' ? '_' : ch;
});
std::cout << s << std::endl;
If you want to do it in place:
abc = abc.Replace(" ", "_");
Although do realize a new string instance will be created; it's not actually done in the same memory location - String is an immutable type.
Using String.Replace(char,char) instead of String.Replace(string, string) should be much faster. i.e.
abc = abc.Replace(' ', '_');
Try this, it finds the first pair of slashes and removes all spaces between!
awk -F'/' '{for(i=2;i<=NF;i++)if(i==2)gsub(" ","_",$i);}1' OFS="/"
Example
file='href="./Dynamic Directory name - Junk_files/irrelevant stuff after match">'
echo $file | awk -F'/' '{for(i=2;i<=NF;i++)if(i==2)gsub(" ","_",$i);}1' OFS="/"
# Output:
href="./Dynamic_Directory_name_-_Junk_files/irrelevant stuff after match">
Through python,
$ echo 'href="./Dynamic Directory name - Junk_files/irrelevant stuff after match"' |
> python -c "import re;
> import sys;
> print re.sub(r'(?<=\./).*?(?=/)', lambda m: m.group().replace(' ', '_'), sys.stdin.read())
> "
href="./Dynamic_Directory_name_-_Junk_files/irrelevant stuff after match"
Through perl,
$ echo 'href="./Dynamic Directory name - Junk_files/irrelevant stuff' | perl -pe '
> s/\s(?=(?:(?!\.\/).)*?\/)/_/g
> '
href="./Dynamic_Directory_name_-_Junk_files/irrelevant stuff
There is no reason to dynamically allocate storage for your name and surname. Looking at your input, neither will exceed 9-characters, so simply using an array for each of 64-chars provides 6X the storage required (if you are unsure, double that to 128-chars and have 1200% additional space). That avoids the comparatively expensive calls to malloc.
To check whether keyword exists in nameList[i], you don't need to separate the values first and then compare. Simply use strstr (nameList[i], keyword) to determine if keyword is contained in nameList[i]. If you then want to match only the name or surname you can compare again after they are separated. (up to you)
To parse the names from the nameList[i] string, all you need is a single pointer to locate the '_' character. A simple call to strchr() will do and it does not modify nameList[i] so there is no need to duplicate.
After using strchr() to locate the '_' character, simply memcpy() from the start of nameList[i] to your pointer to your name array, increment the pointer and then strcpy() from p to surname. Now you have separated name and surname, simply call toupper() on the first character of each and then output the names separate by a space, e.g.
...
#include <ctype.h>
#define NLEN 64
void searchKeyword (const char *nameList[], int n, const char keyword[])
{
for (int i = 0; i < n; i++) { /* loop over each name in list */
if (strstr (nameList[i], keyword)) { /* does name contain keyword? */
char name[NLEN], surname[NLEN]; /* storage for name, surname */
const char *p = nameList[i]; /* pointer to parse nameList[i] */
if ((p = strchr(p, '_'))) { /* find '_' in nameList[i] */
/* copy first-name to name */
memcpy (name, nameList[i], p - nameList[i]);
name[p++ - nameList[i]] = 0; /* nul-terminate first name */
*name = toupper (*name); /* convert 1st char to uppwer */
/* copy last name to surname */
strcpy (surname, p);
*surname = toupper (*surname); /* convert 1st char to upper */
printf ("%s %s\n", name, surname); /* output "Name Surname" */
}
}
}
}
Example Use/Output
Used with the remainder of your code, searching for "james" locates those names containing "james" and provides what looks like the output you requested, e.g.
$ ./bin/keyword_surname
Enter a keyword: james
James Bale
James Willis
Michael James
zoe_bale
sam_rodriguez
jack_alonso
david_studi
denzel_feldman
james_bale
james_willis
michael_james
dustin_bale
(note: to match only the name or surname add an additional strcmp before the call to printf to determine which you want to output)
Notes On Your Existing Code
Additional notes continuing from the comments on your existing code,
char *str = (char *) malloc((strlen(nameList[0])+1)*sizeof(char));
should simply be
str = malloc (strlen (nameList[i]) + 1);
You have previously declared char *str; so the declaration before your call to malloc() shadows your previous declaration. If you are using gcc/clang, you can add -Wshadow to your compile string to ensure you are warned of shadowed variables. (they can have dire consequences in other circumstances)
Next, sizeof (char) is always 1 and should be omitted from your size calculation. There is no need to cast the return of malloc() in C. See: Do I cast the result of malloc?
Your comparison if (nameList[i] == '_') is a comparison between a pointer and integer and will not work. Your compiler should be issuing a diagnostic telling you that is incorrect (do not ignore compiler warnings -- do not accept code until it compiles without warning)
Look things over and let me know if you have further questions.
that worked for me and has no memory leaks.
void searchKeyword(const char * nameList[], int n, const char keyword[])
{
int found = 0;
const char delim = '_';
for (int i = 0; i < n; i++) {
const char *fst = nameList[i];
for (const char *tmp = fst; *tmp != '\0'; tmp++) {
if (*tmp == delim) {
const char *snd = tmp + 1;
int fst_length = (snd - fst) / sizeof(char) - 1;
int snd_length = strlen(fst) - fst_length - 1;
if (strncmp(fst, keyword, fst_length) == 0 ||
strncmp(snd, keyword, snd_length) == 0) {
found = 1;
printf("%c%.*s %c%s\n",
fst[0]-32, fst_length-1, fst+1,
snd[0]-32, snd+1);
}
break;
}
}
}
if (!found)
puts("No such keyword found");
}
hopefully it's fine for you too, although I use string.h-functions very rarely.
I am working on a class using R and some bike share data from 3 different cities. I used the code below to select the top 5 start stations for each city.
You can see the line that says:
names(popStart) = c(paste('Most_common_', City, '_start_station, sep=' '), 'Count')
This works just fine for me to then be able to plot like this:
ggplot(aes(x=Most_common_Chicago_start_station, y=Count), data=chiStart) +
geom_bar(stat="identity") +
theme(axis.text.x = element_text(angle = 90)) +
coord_flip() +
ggtitle('Chicago most common start staion')
That works fine for Chicago and Washington, but since the City name for new york is New York City with the spaces it doesn't work when I do:
ggplot(aes(x=Most_common_New York City_start_station, y=Count), data=chiStart) +
It doesn't work because of the spaces in the City name. I could obviously update the City name in the data frame to have underscores, but that seems overboard just for this one piece. Is there any way I can make this work inline by modifying the code I have provided? I am very new to R, so I may be missing something simple