Easy way to remove extension from a filename? - c++

I am trying to grab the raw filename without the extension from the filename passed in arguments:
int main ( int argc, char *argv[] )
{
// Check to make sure there is a single argument
if ( argc != 2 )
{
cout<<"usage: "<< argv[0] <<" <filename>\n";
return 1;
}
// Remove the extension if it was supplied from argv[1] -- pseudocode
char* filename = removeExtension(argv[1]);
cout << filename;
}
The filename should for example be "test" when I passed in "test.dat".

size_t lastindex = fullname.find_last_of(".");
string rawname = fullname.substr(0, lastindex);
Beware of the case when there is no "." and it returns npos

This works:
std::string remove_extension(const std::string& filename) {
size_t lastdot = filename.find_last_of(".");
if (lastdot == std::string::npos) return filename;
return filename.substr(0, lastdot);
}

Since C++17 you can use std::filesystem::path::replace_extension with a parameter to replace the extension or without to remove it:
#include <iostream>
#include <filesystem>
int main()
{
std::filesystem::path p = "/foo/bar.txt";
std::cout << "Was: " << p << std::endl;
std::cout << "Now: " << p.replace_extension() << std::endl;
}
Compile it with:
g++ -std=c++17 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
Running the resulting binary leaves you with:
Was: "/foo/bar.txt"
Now: "/foo/bar"
However this does only remove the last file extension:
Was: "/foo/bar.tar.gz"
Now: "/foo/bar.tar"

In my opinion it is easiest, and the most readable solution:
#include <boost/filesystem/convenience.hpp>
std::string removeFileExtension(const std::string& fileName)
{
return boost::filesystem::change_extension(fileName, "").string();
}

For those who like boost:
Use boost::filesystem::path::stem. It returns the filename without the last extension. So ./myFiles/foo.bar.foobar becomes foo.bar. So when you know you are dealing with only one extension you could do the follwing:
boost::filesystem::path path("./myFiles/fileWithOneExt.myExt");
std::string fileNameWithoutExtension = path.stem().string();
When you have to deal with multiple extensions you might do the following:
boost::filesystem::path path("./myFiles/fileWithMultiExt.myExt.my2ndExt.my3rdExt");
while(!path.extension().empty())
{
path = path.stem();
}
std::string fileNameWithoutExtensions = path.stem().string();
(taken from here: http://www.boost.org/doc/libs/1_53_0/libs/filesystem/doc/reference.html#path-decomposition found in the stem section)
BTW works with rooted paths, too.

The following works for a std::string:
string s = filename;
s.erase(s.find_last_of("."), string::npos);

More complex, but with respect to special cases (for example: "foo.bar/baz", "c:foo.bar", works for Windows too)
std::string remove_extension(const std::string& path) {
if (path == "." || path == "..")
return path;
size_t pos = path.find_last_of("\\/.");
if (pos != std::string::npos && path[pos] == '.')
return path.substr(0, pos);
return path;
}

You can do this easily :
string fileName = argv[1];
string fileNameWithoutExtension = fileName.substr(0, fileName.rfind("."));
Note that this only work if there is a dot. You should test before if there is a dot, but you get the idea.

In case someone just wants a simple solution for windows:
Use PathCchRemoveExtension ->MSDN
... or PathRemoveExtension (deprecated!) ->MSDN

Try the following trick to extract the file name from path with no extension in c++ with no external libraries in c++ :
#include <iostream>
#include <string>
using std::string;
string getFileName(const string& s) {
char sep = '/';
#ifdef _WIN32
sep = '\\';
#endif
size_t i = s.rfind(sep, s.length());
if (i != string::npos)
{
string filename = s.substr(i+1, s.length() - i);
size_t lastindex = filename.find_last_of(".");
string rawname = filename.substr(0, lastindex);
return(rawname);
}
return("");
}
int main(int argc, char** argv) {
string path = "/home/aymen/hello_world.cpp";
string ss = getFileName(path);
std::cout << "The file name is \"" << ss << "\"\n";
}

Just loop through the list and replace the first (or last) occurrence of a '.' with a NULL terminator. That will end the string at that point.
Or make a copy of the string up until the '.', but only if you want to return a new copy. Which could get messy since a dynamically allocated string could be a source of memory leak.
for(len=strlen(extension);len>= 0 && extension[len] != '.';len--)
;
char * str = malloc(len+1);
for(i=0;i<len;i++)
str[i] = extension[i];
str[i] = '\0'l

Related

How can i concatenate a const char with char array?

The array itself is held at
char filestring[9];
and initialized via
snprintf_P(filestring,
sizeof(filestring),
PSTR("%04u%02u%02u"),
dt.Year(),
dt.Month(),
dt.Day());
How can i concatenate all above as in the example? (Add the slash and the .txt extension to the filestring variable)
File file = SD.open("/" + filestring + ".txt", FILE_APPEND);
I get the following misleading error for the example above.
expression must have integral or unscoped enum type
Maybe something like this:
char filename[MAX_PATH] = {0};
int n = snprintf(filename, sizeof(filename), "/%s.txt", filestring);
// check whether snprintf succeeded
if (n > 0 && n < sizeof(filename)) {
File file = SD.open(filename, FILE_APPEND);
}
Update: As requested by a user I am adding a clarification on MAX_PATH:
The line
char filename[MAX_PATH] = {0};
Defines a character array of size MAX_PATH. That could have used any integer value that you thought right for your program but, using MAX_PATH ensures the buffers can hold any filename.
On Linux, you must #include <limits.h> (or you can #include <stdio.h> and use FILENAME_MAX). I am not a Windows user but it looks like you have to #include <stdlib.h> to import MAX_PATH (doc).
Of course you could also also initialized filestring with the desired format in one go:
char filestring[MAX_PATH];
snprintf_P(filestring,
sizeof(filestring),
PSTR("/%04u%02u%02u.txt"),
dt.Year(),
dt.Month(),
dt.Day());
In C:
const int size = MAX_PATH;
char path[size];
int rc = snprintf(path, size, "/%s.txt", filestring);
if (rc < 0) {
fprintf(stderr, "Concatenation error.\n");
} else if (rc > size) {
fprintf(stderr, "Buffer is too small.\n");
} else {
printf("path: %s\n", path);
// Use it...
}
In C++ (since you tagged your question C++):
std::string path = "/" + std::string(filestring) + ".txt";
File file = SD.open(path.c_str(), FILE_APPEND);
Here's an alternative using a std::ostringstream to build the filename and a std::string to pass the result around to other functions:
#include <iomanip>
#include <sstream>
#include <string>
void some_function() {
std::ostringstream os;
// build the string using the std::ostringstream
os << std::setfill('0')
<< '/'
<< std::setw(4) << dt.Year()
<< std::setw(2) << dt.Month()
<< std::setw(2) << dt.Day()
<< ".txt";
// extract the result into a std::string
std::string filestring(os.str());
// Then depending on the SD.open() interface:
// 1. The preferred:
File file = SD.open(filestring, FILE_APPEND);
// 2. Backup version:
File file = SD.open(filestring.c_str(), FILE_APPEND);
}

Difficulties with string declaration/reference parameters (c++)

Last week I got an homework to write a function: the function gets a string and a char value and should divide the string in two parts, before and after the first occurrence of the existing char.
The code worked but my teacher told me to do it again, because it is not well written code. But I don't understand how to make it better. I understand so far that defining two strings with white spaces is not good, but i get out of bounds exceptions otherwise. Since the string input changes, the string size changes everytime.
#include <iostream>
#include <string>
using namespace std;
void divide(char search, string text, string& first_part, string& sec_part)
{
bool firstc = true;
int counter = 0;
for (int i = 0; i < text.size(); i++) {
if (text.at(i) != search && firstc) {
first_part.at(i) = text.at(i);
}
else if (text.at(i) == search&& firstc == true) {
firstc = false;
sec_part.at(counter) = text.at(i);
}
else {
sec_part.at(counter) = text.at(i);
counter++;
}
}
}
int main() {
string text;
string part1=" ";
string part2=" ";
char search_char;
cout << "Please enter text? ";
getline(cin, text);
cout << "Please enter a char: ? ";
cin >> search_char;
divide(search_char,text,aprt1,part2);
cout << "First string: " << part1 <<endl;
cout << "Second string: " << part2 << endl;
system("PAUSE");
return 0;
}
I would suggest you, learn to use c++ standard functions. there are plenty utility function that can help you in programming.
void divide(const std::string& text, char search, std::string& first_part, std::string& sec_part)
{
std::string::const_iterator pos = std::find(text.begin(), text.end(), search);
first_part.append(text, 0, pos - text.begin());
sec_part.append(text, pos - text.begin());
}
int main()
{
std::string text = "thisisfirst";
char search = 'f';
std::string first;
std::string second;
divide(text, search, first, second);
}
Here I used std::find that you can read about it from here and also Iterators.
You have some other mistakes. you are passing your text by value that will do a copy every time you call your function. pass it by reference but qualify it with const that will indicate it is an input parameter not an output.
Why is your teacher right ?
The fact that you need to initialize your destination strings with empty space is terrible:
If the input string is longer, you'll get out of bound errors.
If it's shorter, you got wrong answer, because in IT and programming, "It works " is not the same as "It works".
In addition, your code does not fit the specifications. It should work all the time, independently of the current value which is stored in your output strings.
Alternative 1: your code but working
Just clear the destination strings at the beginning. Then iterate as you did, but use += or push_back() to add chars at the end of the string.
void divide(char search, string text, string& first_part, string& sec_part)
{
bool firstc = true;
first_part.clear(); // make destinations strings empty
sec_part.clear();
for (int i = 0; i < text.size(); i++) {
char c = text.at(i);
if (firstc && c != search) {
first_part += c;
}
else if (firstc && c == search) {
firstc = false;
sec_part += c;
}
else {
sec_part += c;
}
}
}
I used a temporary c instead of text.at(i) or text\[i\], in order to avoid multiple indexing But this is not really required: nowadays, optimizing compilers should produce equivalent code, whatever variant you use here.
Alternative 2: use string member functions
This alternative uses the find() function, and then constructs a string from the start until that position, and another from that position. There is a special case when the character was not found.
void divide(char search, string text, string& first_part, string& sec_part)
{
auto pos = text.find(search);
first_part = string(text, 0, pos);
if (pos== string::npos)
sec_part.clear();
else sec_part = string(text, pos, string::npos);
}
As you understand yourself these declarations
string part1=" ";
string part2=" ";
do not make sense because the entered string in the object text can essentially exceed the both initialized strings. In this case using the string method at can result in throwing an exception or the strings will have trailing spaces.
From the description of the assignment it is not clear whether the searched character should be included in one of the strings. You suppose that the character should be included in the second string.
Take into account that the parameter text should be declared as a constant reference.
Also instead of using loops it is better to use methods of the class std::string such as for example find.
The function can look the following way
#include <iostream>
#include <string>
void divide(const std::string &text, char search, std::string &first_part, std::string &sec_part)
{
std::string::size_type pos = text.find(search);
first_part = text.substr(0, pos);
if (pos == std::string::npos)
{
sec_part.clear();
}
else
{
sec_part = text.substr(pos);
}
}
int main()
{
std::string text("Hello World");
std::string first_part;
std::string sec_part;
divide(text, ' ', first_part, sec_part);
std::cout << "\"" << text << "\"\n";
std::cout << "\"" << first_part << "\"\n";
std::cout << "\"" << sec_part << "\"\n";
}
The program output is
"Hello World"
"Hello"
" World"
As you can see the separating character is included in the second string though I think that maybe it would be better to exclude it from the both strings.
An alternative and in my opinion more clear approach can look the following way
#include <iostream>
#include <string>
#include <utility>
std::pair<std::string, std::string> divide(const std::string &s, char c)
{
std::string::size_type pos = s.find(c);
return { s.substr(0, pos), pos == std::string::npos ? "" : s.substr(pos) };
}
int main()
{
std::string text("Hello World");
auto p = divide(text, ' ');
std::cout << "\"" << text << "\"\n";
std::cout << "\"" << p.first << "\"\n";
std::cout << "\"" << p.second << "\"\n";
}
Your code will only work as long the character is found within part1.length(). You need something similar to this:
void string_split_once(const char s, const string & text, string & first, string & second) {
first.clear();
second.clear();
std::size_t pos = str.find(s);
if (pos != string::npos) {
first = text.substr(0, pos);
second = text.substr(pos);
}
}
The biggest problem I see is that you are using at where you should be using push_back. See std::basic_string::push_back. at is designed to access an existing character to read or modify it. push_back appends a new character to the string.
divide could look like this :
void divide(char search, string text, string& first_part,
string& sec_part)
{
bool firstc = true;
for (int i = 0; i < text.size(); i++) {
if (text.at(i) != search && firstc) {
first_part.push_back(text.at(i));
}
else if (text.at(i) == search&& firstc == true) {
firstc = false;
sec_part.push_back(text.at(i));
}
else {
sec_part.push_back(text.at(i));
}
}
}
Since you aren't handling exceptions, consider using text[i] rather than text.at(i).

C++ Finding an incomplete string from a text file

I have a program which reads text files and parses information from them, and I am trying to accomplish a task like this:
A text file which has about 500 characters of data in it, in this data lies user name like so:
this_just_some_random_data_in_the_file_hdfhehhr2342t543t3y3y
_please_don't_mind_about_me(username: "sara123452")reldgfhfh
2134242gt3gfd2342353ggf43t436tygrghrhtyj7i6789679jhkjhkuklll
The thing is that we only need to find and write sara123452 to a string from that text file. The user name is unknown of course, and does not have fixed length.
Here is what I have managed to do so far:
std::string Profile = "http://something.com/all_users/data.txt";
std::string FileName = "profileInfo.txt";
std::string Buffer, ProfileName;
std::ifstream FileReader;
DeleteUrlCacheEntryA(Profile .c_str());
URLDownloadToFileA(0, Profile .c_str(), FileName.c_str(), 0, 0);
FileReader.open(FileName);
if (FileReader.is_open())
{
std::ostringstream FileBuffer;
FileBuffer << FileReader.rdbuf();
Buffer= FileBuffer.str();
if (Buffer.find("(username: ") != std::string::npos) {
cout << "dont know how to continue" << endl;
}
FileReader.close();
DeleteFileA(FileName.c_str());
}
else {
}
cin.get();
So how can I get the user name string and assign/copy it to ProfileName string?
I believe what you're looking for is something like the code below -- possibly with minor tweaks to account for the username being quoted. The key here is to remember that your Buffer variable is a std::string and you can use substring once you have a definite start and end.
std::size_t userNameStartIndex, userNameEndIndex
...
userNameStartIndex = Buffer.find("(username: ")) + 11;
if (userNameStartIndex != std::string::npos) {
userNameEndIndex = Buffer.find(")", userNameStartIndex);
if (userNameEndIndex != std::string::npos)
{
ProfileName = Buffer.substr(userNameStartIndex, userNameEndIndex - userNameStartIndex)
}
}
There are many other ways to do it, but this one would be less painful I guess.
#include <regex>
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
struct Profile
{ // ...
string name;
};
int main(int argc, const char * argv[])
{
std::cout.sync_with_stdio(false); // optional
// read from file
string filename {"data1.txt"};
ifstream in {filename};
vector<Profile> profiles;
// tweaks this pattern in case you're not happy with it
regex pat {R"(\(username:\s*\"(.*?)\"\))"};
for (string line; getline(in,line); ) {
Profile p;
sregex_iterator first(cbegin(line), cend(line), pat);
const sregex_iterator last;
while (first != last) {
// By dereferencing a first, you get a smatch object.
// [1] gives you the matched sub-string:
p.name = (*first)[1]; // (*first)[1] = sara123452
profiles.push_back(p);
++first;
}
}
// Test
for (const auto& p : profiles)
cout << p.name << '\n';
}

Comparing 2 strings to exclude based on extention

Ive had a look at this post: Find if string ends with another string in C++
I am trying to achieve a similar goal.
Basically i want to take a file list from a directory and filter out any files which do not end with a specified allowed extention for processing in my program.
In java this would be performed by creating a method and passing the extention accross as a string then using .endswith in the following statement. C++ does not appear to support this so how would i go about it?
for (int fileList = 0; fileList < files.length; fileList++)
{
//output only jpg files, file list is still full
if(files[fileList].toString().endsWith(extension))
{
images.add(files[fileList]);
}//end if
}//end for
Thanks in advance
bool endsWith(std::string const & s, std::string const & e) {
if (s.size() < e.size())
return false;
return s.substr(s.size() - e.size()) == e;
}
If using boost::filesystem is ok for you then you could try
#include <boost/filesystem.hpp>
//...
boost::filesystem::path dir_path ("c:\\dir\\subdir\\data");
std::string extension(".jpg");
for (boost::filesystem::directory_iterator it_file(dir_path);
it_file != boost::filesystem::directory_iterator();
++it_file)
{
if ( boost::filesystem::is_regular_file(*it_file) &&
boost::filesystem::extension(*it_file) == extension)
{
// do your stuff
}
}
This will parse the given directory path and you then just have to filter desired extension.t
Next example checks if the filename ends with the jpg extension :
#include <iostream>
#include <string>
using namespace std;
bool EndsWithExtension (const string& str,const string &extension)
{
size_t found = str.find_last_of(".");
if ( string::npos != found )
{
return (extension == str.substr(found+1) );
}
return false;
}
int main ()
{
string filename1 ("c:\\windows\\winhelp.exe");
string filename2 ("c:\\windows\\other.jpg");
string filename3 ("c:\\windows\\winhelp.");
cout << boolalpha << EndsWithExtension(filename1,"jpg") << endl;
cout << boolalpha << EndsWithExtension(filename2,"jpg") << endl;
cout << boolalpha << EndsWithExtension(filename3,"jpg") << endl;
}

How do I check if a C++ std::string starts with a certain string, and convert a substring to an int?

How do I implement the following (Python pseudocode) in C++?
if argv[1].startswith('--foo='):
foo_value = int(argv[1][len('--foo='):])
(For example, if argv[1] is --foo=98, then foo_value is 98.)
Update: I'm hesitant to look into Boost, since I'm just looking at making a very small change to a simple little command-line tool (I'd rather not have to learn how to link in and use Boost for a minor change).
Use rfind overload that takes the search position pos parameter, and pass zero for it:
std::string s = "tititoto";
if (s.rfind("titi", 0) == 0) { // pos=0 limits the search to the prefix
// s starts with prefix
}
Who needs anything else? Pure STL!
Many have misread this to mean "search backwards through the whole string looking for the prefix". That would give the wrong result (e.g. string("tititito").rfind("titi") returns 2 so when compared against == 0 would return false) and it would be inefficient (looking through the whole string instead of just the start). But it does not do that because it passes the pos parameter as 0, which limits the search to only match at that position or earlier. For example:
std::string test = "0123123";
size_t match1 = test.rfind("123"); // returns 4 (rightmost match)
size_t match2 = test.rfind("123", 2); // returns 1 (skipped over later match)
size_t match3 = test.rfind("123", 0); // returns std::string::npos (i.e. not found)
You would do it like this:
std::string prefix("--foo=");
if (!arg.compare(0, prefix.size(), prefix))
foo_value = std::stoi(arg.substr(prefix.size()));
Looking for a lib such as Boost.ProgramOptions that does this for you is also a good idea.
Just for completeness, I will mention the C way to do it:
If str is your original string, substr is the substring you want to
check, then
strncmp(str, substr, strlen(substr))
will return 0 if str
starts with substr. The functions strncmp and strlen are in the C
header file <string.h>
(originally posted by Yaseen Rauf here, markup added)
For a case-insensitive comparison, use strnicmp instead of strncmp.
This is the C way to do it, for C++ strings you can use the same function like this:
strncmp(str.c_str(), substr.c_str(), substr.size())
If you're already using Boost, you can do it with boost string algorithms + boost lexical cast:
#include <boost/algorithm/string/predicate.hpp>
#include <boost/lexical_cast.hpp>
try {
if (boost::starts_with(argv[1], "--foo="))
foo_value = boost::lexical_cast<int>(argv[1]+6);
} catch (boost::bad_lexical_cast) {
// bad parameter
}
This kind of approach, like many of the other answers provided here is ok for very simple tasks, but in the long run you are usually better off using a command line parsing library. Boost has one (Boost.Program_options), which may make sense if you happen to be using Boost already.
Otherwise a search for "c++ command line parser" will yield a number of options.
Code I use myself:
std::string prefix = "-param=";
std::string argument = argv[1];
if(argument.substr(0, prefix.size()) == prefix) {
std::string argumentValue = argument.substr(prefix.size());
}
Nobody used the STL algorithm/mismatch function yet. If this returns true, prefix is a prefix of 'toCheck':
std::mismatch(prefix.begin(), prefix.end(), toCheck.begin()).first == prefix.end()
Full example prog:
#include <algorithm>
#include <string>
#include <iostream>
int main(int argc, char** argv) {
if (argc != 3) {
std::cerr << "Usage: " << argv[0] << " prefix string" << std::endl
<< "Will print true if 'prefix' is a prefix of string" << std::endl;
return -1;
}
std::string prefix(argv[1]);
std::string toCheck(argv[2]);
if (prefix.length() > toCheck.length()) {
std::cerr << "Usage: " << argv[0] << " prefix string" << std::endl
<< "'prefix' is longer than 'string'" << std::endl;
return 2;
}
if (std::mismatch(prefix.begin(), prefix.end(), toCheck.begin()).first == prefix.end()) {
std::cout << '"' << prefix << '"' << " is a prefix of " << '"' << toCheck << '"' << std::endl;
return 0;
} else {
std::cout << '"' << prefix << '"' << " is NOT a prefix of " << '"' << toCheck << '"' << std::endl;
return 1;
}
}
Edit:
As #James T. Huggett suggests, std::equal is a better fit for the question: Is A a prefix of B? and is slight shorter code:
std::equal(prefix.begin(), prefix.end(), toCheck.begin())
Full example prog:
#include <algorithm>
#include <string>
#include <iostream>
int main(int argc, char **argv) {
if (argc != 3) {
std::cerr << "Usage: " << argv[0] << " prefix string" << std::endl
<< "Will print true if 'prefix' is a prefix of string"
<< std::endl;
return -1;
}
std::string prefix(argv[1]);
std::string toCheck(argv[2]);
if (prefix.length() > toCheck.length()) {
std::cerr << "Usage: " << argv[0] << " prefix string" << std::endl
<< "'prefix' is longer than 'string'" << std::endl;
return 2;
}
if (std::equal(prefix.begin(), prefix.end(), toCheck.begin())) {
std::cout << '"' << prefix << '"' << " is a prefix of " << '"' << toCheck
<< '"' << std::endl;
return 0;
} else {
std::cout << '"' << prefix << '"' << " is NOT a prefix of " << '"'
<< toCheck << '"' << std::endl;
return 1;
}
}
With C++17 you can use std::basic_string_view & with C++20 std::basic_string::starts_with or std::basic_string_view::starts_with.
The benefit of std::string_view in comparison to std::string - regarding memory management - is that it only holds a pointer to a "string" (contiguous sequence of char-like objects) and knows its size. Example without moving/copying the source strings just to get the integer value:
#include <exception>
#include <iostream>
#include <string>
#include <string_view>
int main()
{
constexpr auto argument = "--foo=42"; // Emulating command argument.
constexpr auto prefix = "--foo=";
auto inputValue = 0;
constexpr auto argumentView = std::string_view(argument);
if (argumentView.starts_with(prefix))
{
constexpr auto prefixSize = std::string_view(prefix).size();
try
{
// The underlying data of argumentView is nul-terminated, therefore we can use data().
inputValue = std::stoi(argumentView.substr(prefixSize).data());
}
catch (std::exception & e)
{
std::cerr << e.what();
}
}
std::cout << inputValue; // 42
}
Given that both strings — argv[1] and "--foo" — are C strings, #FelixDombek's answer is hands-down the best solution.
Seeing the other answers, however, I thought it worth noting that, if your text is already available as a std::string, then a simple, zero-copy, maximally efficient solution exists that hasn't been mentioned so far:
const char * foo = "--foo";
if (text.rfind(foo, 0) == 0)
foo_value = text.substr(strlen(foo));
And if foo is already a string:
std::string foo("--foo");
if (text.rfind(foo, 0) == 0)
foo_value = text.substr(foo.length());
Starting with C++20, you can use the starts_with method.
std::string s = "abcd";
if (s.starts_with("abc")) {
...
}
text.substr(0, start.length()) == start
Using STL this could look like:
std::string prefix = "--foo=";
std::string arg = argv[1];
if (prefix.size()<=arg.size() && std::equal(prefix.begin(), prefix.end(), arg.begin())) {
std::istringstream iss(arg.substr(prefix.size()));
iss >> foo_value;
}
At the risk of being flamed for using C constructs, I do think this sscanf example is more elegant than most Boost solutions. And you don't have to worry about linkage if you're running anywhere that has a Python interpreter!
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
for (int i = 1; i != argc; ++i) {
int number = 0;
int size = 0;
sscanf(argv[i], "--foo=%d%n", &number, &size);
if (size == strlen(argv[i])) {
printf("number: %d\n", number);
}
else {
printf("not-a-number\n");
}
}
return 0;
}
Here's some example output that demonstrates the solution handles leading/trailing garbage as correctly as the equivalent Python code, and more correctly than anything using atoi (which will erroneously ignore a non-numeric suffix).
$ ./scan --foo=2 --foo=2d --foo='2 ' ' --foo=2'
number: 2
not-a-number
not-a-number
not-a-number
I use std::string::compare wrapped in utility method like below:
static bool startsWith(const string& s, const string& prefix) {
return s.size() >= prefix.size() && s.compare(0, prefix.size(), prefix) == 0;
}
C++20 update :
Use std::string::starts_with
https://en.cppreference.com/w/cpp/string/basic_string/starts_with
std::string str_value = /* smthg */;
const auto starts_with_foo = str_value.starts_with(std::string_view{"foo"});
In C++20 now there is starts_with available as a member function of std::string defined as:
constexpr bool starts_with(string_view sv) const noexcept;
constexpr bool starts_with(CharT c) const noexcept;
constexpr bool starts_with(const CharT* s) const;
So your code could be something like this:
std::string s{argv[1]};
if (s.starts_with("--foo="))
In case you need C++11 compatibility and cannot use boost, here is a boost-compatible drop-in with an example of usage:
#include <iostream>
#include <string>
static bool starts_with(const std::string str, const std::string prefix)
{
return ((prefix.size() <= str.size()) && std::equal(prefix.begin(), prefix.end(), str.begin()));
}
int main(int argc, char* argv[])
{
bool usage = false;
unsigned int foos = 0; // default number of foos if no parameter was supplied
if (argc > 1)
{
const std::string fParamPrefix = "-f="; // shorthand for foo
const std::string fooParamPrefix = "--foo=";
for (unsigned int i = 1; i < argc; ++i)
{
const std::string arg = argv[i];
try
{
if ((arg == "-h") || (arg == "--help"))
{
usage = true;
} else if (starts_with(arg, fParamPrefix)) {
foos = std::stoul(arg.substr(fParamPrefix.size()));
} else if (starts_with(arg, fooParamPrefix)) {
foos = std::stoul(arg.substr(fooParamPrefix.size()));
}
} catch (std::exception& e) {
std::cerr << "Invalid parameter: " << argv[i] << std::endl << std::endl;
usage = true;
}
}
}
if (usage)
{
std::cerr << "Usage: " << argv[0] << " [OPTION]..." << std::endl;
std::cerr << "Example program for parameter parsing." << std::endl << std::endl;
std::cerr << " -f, --foo=N use N foos (optional)" << std::endl;
return 1;
}
std::cerr << "number of foos given: " << foos << std::endl;
}
Why not use gnu getopts? Here's a basic example (without safety checks):
#include <getopt.h>
#include <stdio.h>
int main(int argc, char** argv)
{
option long_options[] = {
{"foo", required_argument, 0, 0},
{0,0,0,0}
};
getopt_long(argc, argv, "f:", long_options, 0);
printf("%s\n", optarg);
}
For the following command:
$ ./a.out --foo=33
You will get
33
Ok why the complicated use of libraries and stuff? C++ String objects overload the [] operator, so you can just compare chars.. Like what I just did, because I want to list all files in a directory and ignore invisible files and the .. and . pseudofiles.
while ((ep = readdir(dp)))
{
string s(ep->d_name);
if (!(s[0] == '.')) // Omit invisible files and .. or .
files.push_back(s);
}
It's that simple..
You can also use strstr:
if (strstr(str, substr) == substr) {
// 'str' starts with 'substr'
}
but I think it's good only for short strings because it has to loop through the whole string when the string doesn't actually start with 'substr'.
With C++11 or higher you can use find() and find_first_of()
Example using find to find a single char:
#include <string>
std::string name = "Aaah";
size_t found_index = name.find('a');
if (found_index != std::string::npos) {
// Found string containing 'a'
}
Example using find to find a full string & starting from position 5:
std::string name = "Aaah";
size_t found_index = name.find('h', 3);
if (found_index != std::string::npos) {
// Found string containing 'h'
}
Example using the find_first_of() and only the first char, to search at the start only:
std::string name = ".hidden._di.r";
size_t found_index = name.find_first_of('.');
if (found_index == 0) {
// Found '.' at first position in string
}
More about find
More about find_first_of
Good luck!
std::string text = "--foo=98";
std::string start = "--foo=";
if (text.find(start) == 0)
{
int n = stoi(text.substr(start.length()));
std::cout << n << std::endl;
}
Since C++11 std::regex_search can also be used to provide even more complex expressions matching. The following example handles also floating numbers thorugh std::stof and a subsequent cast to int.
However the parseInt method shown below could throw a std::invalid_argument exception if the prefix is not matched; this can be easily adapted depending on the given application:
#include <iostream>
#include <regex>
int parseInt(const std::string &str, const std::string &prefix) {
std::smatch match;
std::regex_search(str, match, std::regex("^" + prefix + "([+-]?(?=\\.?\\d)\\d*(?:\\.\\d*)?(?:[Ee][+-]?\\d+)?)$"));
return std::stof(match[1]);
}
int main() {
std::cout << parseInt("foo=13.3", "foo=") << std::endl;
std::cout << parseInt("foo=-.9", "foo=") << std::endl;
std::cout << parseInt("foo=+13.3", "foo=") << std::endl;
std::cout << parseInt("foo=-0.133", "foo=") << std::endl;
std::cout << parseInt("foo=+00123456", "foo=") << std::endl;
std::cout << parseInt("foo=-06.12e+3", "foo=") << std::endl;
// throw std::invalid_argument
// std::cout << parseInt("foo=1", "bar=") << std::endl;
return 0;
}
The kind of magic of the regex pattern is well detailed in the following answer.
EDIT: the previous answer did not performed the conversion to integer.
if(boost::starts_with(string_to_search, string_to_look_for))
intval = boost::lexical_cast<int>(string_to_search.substr(string_to_look_for.length()));
This is completely untested. The principle is the same as the Python one. Requires Boost.StringAlgo and Boost.LexicalCast.
Check if the string starts with the other string, and then get the substring ('slice') of the first string and convert it using lexical cast.