I have the following C++ code and I can't seem to get it working. What I am trying to do is read numerous entries from the command line, separated by ('|') pipe characters, and then splitting the resulting strings by spaces.
eg.
mkdir C:/unixcode/shells|cd D:/margins/code | pwd| finger kobojunkie | last -l kobojunkie
but so far, I get errors, something about declaring the size of the pointer:
Initializer fails to determine the size of argv2
cannot convert char** to char* for argument 1 to char strtok(char*, const char*)
int main(int argc, char *argv[])
{
char * pch;
pch = strtok (argv,"|");
//parse the contents of the generated arrays
while (pch != NULL)
{
printf ("%s\n",pch);
char * argv2[] = pch;
char * subpch = strtok(argv2," ");
while (subpch !=NULL)
{
printf ("%s\n",subpch);
subpch = strtok (NULL, " ");
}
pch = strtok (NULL, " ");
}
return 0;
}
the type of argv is char**, not char* hence you cannot pass it to strtok. Use argv[ 1 ] instead, but check that argc >= 2 first.
Or, since this is tagged c++, use stl to split the string, eg
void split( const std::string& s, char delim, std::vector<std::string>& elems )
{
std::stringstream ss( s );
std::string item;
while( std::getline( ss, item, delim ) )
if( !item.empty() )
elems.push_back( item );
}
int main( int argc, char *argv[] )
{
if( argc == 2 )
{
std::vector< std::string > elements;
split( argv[ 1 ], '|', elements );
//elements now contains all items..
}
}
The command-line is managed by a program: the shell (probably cmd.exe in Windows or bash in Linux). That shell gets all the stuff written in the command line and parses it and executes the commands specified.
Unless you are writing a shell, you cannot ever see the "|" of your example command-line inside the programs you write. They are effectively processed from the shell and removed from the parameters sent to the programs.
In
mkdir C:/unixcode/shells|cd D:/margins/code | pwd| finger kobojunkie | last -l kobojunkie
the shell calls the 5 following commands, each with the parameters specified
mkdir C:/unixcode/shells
cd D:/margins/code
pwd
finger kobojunkie
last -l kobojunkie
Note none of the programs receive a "|".
If you are indeed writing a shell, the command-line is not available in the argv array. It depends on the way you manage input inside your shell.
argv is an array of arrays pointers. You cannot pass it as is to strtok: you need to pass its elements in a loop
for (k = 1; k < argc; k++) {
pch = strtok(argv[k], "|");
/* ... */
}
Also: are you sure you want to delimit with "|"? That character has a special meaning to shells and, usually, does not make it to your program.
Unless you call your program with them escaped, eg
bash$ ./a.out 'one|two|three' 'four|five|six'
argv is not a string. argv is an array of strings. strtok takes a string, so you cannot pass it an array of strings and expect it to do something meaningful.
Each string element of the argv array is a separate command line parameter, except for the first which is the name of the executable. So what you should be doing is looking through each string entry for "|", and acting accordingly.
Related
I want to call an external program from my Qt application. This requires me to prepare some arguments for the external command. This will be just like calling another process from terminal. E.g.:
app -w=5 -h=6
To test this, I have a simple function like:
void doStuff(int argc, char** argv){ /* parse arguments */};
I try to prepare a set of arguments like this:
QString command;
command.append(QString("-w=%1 -h=%2 -s=%3 ")
.arg("6").arg("16").arg(0.25));
command.append("-o=test.yml -op -oe");
std::string s = command.toUtf8().toStdString();
char cstr[s.size() + 1];
char* ptr = &cstr[0];
std::copy(s.begin(), s.end(), cstr);
cstr[s.size()] = '\0';
Then I call that function:
doStuff(7, &cstr);
But I get the wrong argumetns (corrupted) in the debuggre and my parser (opencv CommandLineParser crashes!
Can you please tell me what am I doing wrong?
doStuff is expecting an array of strings not a single string.
Something like this should work:
std::vector<std::string> command;
command.push_back(QString("-w=%1").arg("6").toUtf8().toStdString());
command.push_back(QString("-h=%2").arg("16").toUtf8().toStdString());
command.push_back(QString("-s=%3").arg("0.25").toUtf8().toStdString());
command.push_back("-o=test.yml");
command.push_back("-op");
command.push_back("-oe");
std::vector<char*> cstr;
std::transform(command.begin(), command.end(), std::back_inserter(cstr),[](std::string& s){return s.data();});
doStuff(cstr.size(), cstr.data());
Because you need to split your space separated string into individual strings for arguments.
argv points to an array of strings - what you need to end up with is something like
argv[0] -> "-w=6"
argv[1] -> "-h=16"
...
argv[7] -> 0
which writing the string over your char array isn't going to achieve, as thats just treating cstr as an array of characters.
I am creating a command prompt and I take input from the user as a string and convert it to a char** using a parse function I have created. I can not see any issues with it but after running a few commands in my prompt I begin to run into the error of "corrupted size vs prev_size 0x00c06738. Here is my parse function. Any help would be appreciated.
char** parse(string s){
vector<string> commandvector;
istringstream iss(s);
for(string s; iss>>s;)
commandvector.push_back(s);
char** argv = new char*[commandvector.size() + 1];
for(int i = 0; i<commandvector.size();i++){
argv[i] = new char[commandvector[i].size()];
strcpy(argv[i],commandvector[i].c_str());
}
argv[commandvector.size() + 1] = NULL;
return argv;
}
If you want to use C-style strings, you have to take into account the null-terminator char.
While std::basic_string::c_str returns a pointer to a null-terminated (since C++11) array of char, std::basic_string::size doesn't count the null terminator, so in order to allocate enough memory, you have to add 1 to returned value.
Besides, as UnholySheep already noticed, you are accessing the argv array outside its bounds, beeing argv[commandvector.size()] its last element.
I am writing a program in C++ that takes in an argument for a filename, the argument is a char*
ex: myFile.lan
I need to remove the last 3 digits of this char* ("lan") and change them to "asm" (ex: myFile.asm)
It seems really easy to add chars to a char pointer through strcpy, but does anyone know how I can remove chars from a char pointer?
If you're using C++, you should convert your argument to an std::string. This will protect you from going out of bounds and is more clear.
#include <string>
#include <iostream>
int main ()
{
// ... get the arg
const char* arg = "myFile.lan";
std::string filenameAsm(arg);
filenameAsm = filenameAsm.substr(0, filenameAsm.find_last_of("."));
filenameAsm += ".asm";
std::cout << filenameAsm; // prints myFile.asm
return 0;
}
What this code does is take only the part of the filename preceding the "." file extension delimiter (if it doesn't exist, it will take the whole filename) and append the desired ".asm" extension.
Working with a char * is basic 'C'. You can write anything into the memory space, but be careful about not going past the end of allocated space.
char * strings are all terminated with the null byte \0. So, to truncate, you could put a \0 at the appropriate location.
On the other hand, to overwrite characters, just use array syntax; e.g. if the string is length 10 and you want to change the last character, c_string[9] = 'X'; would change that character to an X.
You have to know how a string is ended in C. The length of your string is determined by the first occurrence of the \0 character. Thus, by moving this character backwards, your string becomes shorter. So you probably want to search your string for the first position of the dot, and then replace this dot with \0 (this depends on how exactly the string input looks like though. i'm assuming it's always filenames with a dot somewhere, but you know better).
Using the tools in string.h can simplify the task. strrchr will find the last '.' in the filename allowing you to manipulate the extension. Don't forget to test the lengths of new/old extension to prevent overwriting the \0 - null-terminating character (if the lengths differ, you can always concatenate or realloc the string size, but that's beyond the scope of this example). Look over the solution and let me know if you have any questions:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main (int argc, char **argv) {
if (argc < 3) {
fprintf (stderr, "\n error: insufficient input. Usage: %s <filename> <new_ext>\n\n", argv[0]);
return 1;
}
char *filename = strdup (argv[1]); /* copy argv[1] to prevent clobbering it */
char *p = strrchr (filename, '.'); /* pointer to last '.' in filename */
char *ext = strdup (p+1); /* make a copy of existing extension */
size_t esz = strlen (ext); /* length of existing extension in filename */
size_t nesz = strlen (argv[2]); /* length of new extension */
if (esz < nesz) {
fprintf (stderr, "\n error: invalid extension size. (%s > %s)\n\n", argv[2], ext);
return 1;
}
printf ("\n The original filename: %s\n", filename);
strncpy (p+1, argv[2], nesz); /* copy new extension to filename */
if (nesz < esz) /* if new extension is shorter than old */
*(p+1+nesz) = 0; /* null terminate after new extesion size */
printf (" The amended filename : %s\n\n", filename);
if (filename) free (filename); /* free memory allocated by strdup */
if (ext) free (ext);
return 0;
}
output:
$ ./bin/swapext myfile.lan asm
The original filename: myfile.lan
The amended filename : myfile.asm
$ ./bin/swapext myfile.lan c
The original filename: myfile.lan
The amended filename : myfile.c
I have a,
int main (int argc, char *argv[])
and one of the arguements im passing in is a char. It gives the error message in the title when i go to compile
How would i go about fixing this?
Regards
Paul
When you pass command line parameters, they are all passed as strings, regardless of what types they may represent. If you pass "10" on the command line, you are actually passing the character array
{ '1', '0', '\0' }
not the integer 10.
If the parameter you want consists of a single character, you can always just take the first character:
char timer_unit = argv[2][0];
If you only ever want the first character from the parameter the folowing will extract it from string:
char timer_unit = argv[2][0];
The issue is that argv[2] is a char* (C-string) not char.
You are probably not passing in what you think (though this should come from the command line). Please show the complete error message and code, but it looks like you need to deal with the second argument as char *argv[], instead of char argv[] -- that is, as a list of character arrays, as opposed to a single character array.
Everything stays strings when you pass them in to your program as arguments, even if they are single characters. For example, if your program was called "myprog" and you had this at the command line:
myprog arg1 53 c a "hey there!"
Then what you get in the program is the following:
printf("%d\n", argc);
for(int i = 0; i < argc; i++)
{
printf("%s\n", argv[0]);
}
The output of that would be:
6
myprog
arg1
53
c
a
hey there!
The point being that everything on the command line turns into null-terminated strings, even single characters. If you wanted to get the char 'c' from the command line, you'd need to do this:
char value = argv[3][0];
not
char value = argv[3]; // Error!
Even the value of "53" doesn't turn into an int. you can't do:
int number = argv[2]; // Error!
argv[2] is { '5', '2', '\0' }. You have to do this:
int number = atoi(argv[2]); // Converts from a string to an int
I hope this is clear.
Edit: btw, everything above is just as valid for C (hence the printf statements). It works EXACTLY the same in C++.
I'm getting the text from editbox and I'd want to get each name separated by enter key like the character string below with NULL characters.
char *names = "Name1\0Name2\0Name3\0Name4\0Name5";
while(*names)
{
names += strlen(names)+1;
}
how would you do the same for enter key (i.e separated by /r/n) ? can you do that without using the std::string class?
Use strstr:
while (*names)
{
char *next = strstr(names, "\r\n");
if (next != NULL)
{
// If you want to use the key, the length is
size_t len = next - names;
// do something with a string here. The string is not 0 terminated
// so you need to use only 'len' bytes. How you do this depends on
// your need.
// Have names point to the first character after the \r\n
names = next + 2;
}
else
{
// do something with name here. This version is 0 terminated
// so it's easy to use
// Have names point to the terminating \0
names += strlen(names);
}
}
One thing to note is that this code also fixes an error in your code. Your string is terminated by a single \0, so the last iteration will have names point to the first byte after your string. To fix your existing code, you need to change the value of names to:
// The algorithm needs two \0's at the end (one so the final
// strlen will work and the second so that the while loop will
// terminate). Add one explicitly and allow the compiler to
// add a second one.
char *names = "Name1\0Name2\0Name3\0Name4\0Name5\0";
If you want to start and finish with a C string, it's not really C++.
This is a job for strsep.
#include <stdlib.h>
void split_string( char *multiline ) {
do strsep( &multiline, "\r\n" );
while ( multiline );
}
Each call to strsep zeroes out either a \r or a \n. Since only the string \r\n appears, every other call will return an argument. If you wanted, you could build an array of char*s by recording multiline as it advances or the return value of strsep.
void split_string( char *multiline ) {
vector< char* > args;
do {
char *arg = strsep( &multiline, "\r\n" );
if ( * arg != 0 ) {
args.push_back( arg );
}
} while ( multiline );
}
This second example is at least not specific to Windows.
Here's a pure pointer solution
char * names = "name1\r\nName2\r\nName3";
char * plast = names;
while (*names)
{
if (names[0] == '\r' && names[1] == '\n')
{
if (plast < names)
{
size_t cch = names - plast;
// plast points to a name of length cch, not null terminated.
// to extract the name use
// strncpy(pout, plast, cch);
// pout[cch] = '\0';
}
plast = names+2;
}
++names;
}
// plast now points to the start of the last name, it is null terminated.
// extract it with
// strcpy(pout, plast);
Since this has the C++ tag, the easiest would probably using the C++ standard library, especially strings and string streams. Why do you want to avoid std::string when you're doing C++?
std::istringstream iss(names);
std::string line;
while( std::getline(iss,line) )
process(line); // do process(line.c_str()) instead if you need to