Batch one line to call an executable using arguments from file - c++

For convenience, I have renamed all the files to simple names for my example.
I'm trying to run an executable (test.exe), with a C++ entrypoint int main(int argc, char* argv[]) from a batch file (test.bat), and pass arguments from a text file (test.txt). The end goal is to run unit tests on an SDK using the testing software (test.exe).
My issue is that I do not want to have to use a variable when I call the executable since it makes the code harder to read :
rem This works
set /p test_input=<test.txt& call test.exe %test_input%
After some research, I figured I should use input redirection like so :
rem This does not work
call test.exe < test.txt
This does not work, and I don't understand why.
This is what I initially tried, and it has been suggested before on SO (here).
I have access to the test.exe code, so I can print argc and argv :
int main(int argc, char* argv[])
{
if(new_argc >= 2)
{
if(strcmp("-help", argv[1]) ==0)
{
show_help();
return 0;
}
for(int i=1; i < argc; i++)
{
if(strcmp("-framerate", argv[i]) ==0)
{
i++;
if(i < argc)
{
FrameRate = (float)atof(argv[i]);
}
else
{
std::cerr << "Parameters error" << std::endl;
return 0;
}
} else if ...
{
...
}
}
}
}
If I enter the arguments and parameters manually, it works as expected.
test.txt
-arg1 param1 -arg2 param2 ...
test.bat
call test.exe < test.txt
Output : test.exe runs as if there are no arguments or parameters.
Edit :
Added a few details about the entrypoint and renamed the batch variable.

Thanks to the comments under my question, I was pushed in the right direction.
The problem was my understanding of <. It literally means "Read file to STDIN" (as mentionned here). Many other documentation sites give vague definitions like (as mentionned here)
command < filename : Type a text file and pass the text to command
I need to parse the input correctly, since stdin isn't available in argc or argv, but through std::cin.
My batch code and text file remain unchanged, and I want to maintain the same form of parsing to avoid rewriting multiple projects, so I split the input string using the Solution 1.3 from here (slightly modified) and created a new_argv.
std::vector<char*> split(const std::string& s, char delimiter)
{
std::vector<char*> tokens;
std::string token;
std::istringstream tokenStream(s);
while (std::getline(tokenStream, token, delimiter))
{
tokens.push_back(_strdup(token.c_str()));
}
return tokens;
}
int main(int argc, char* argv[])
{
std::string extra_input; // Variable to store the contents of test.txt
std::getline(std::cin, extra_input); // Recuperate the contents of test.txt
std::vector<char*> new_argv = split(extra_input, ' '); // Split the args
for(int i = argc - 1; i >= 0; i--)
new_argv.insert(new_argv.begin(), argv[i]); // Add the original args to the beginning
const size_t new_argc = new_argv.size(); // Create the new argc based on the final argument list (vector)
if(new_argc >= 2)
{
if(strcmp("-help", new_argv[1]) ==0)
{
show_help();
return 0;
}
for(int i=1; i < new_argc; i++)
{
if(strcmp("-framerate", new_argv[i]) ==0)
{
i++;
if(i < new_argc)
{
FrameRate = (float)atof(new_argv[i]);
}
else
{
std::cerr << "Parameters error" << std::endl;
return 0;
}
} else if ...
{
...
}
}
}
// Important, don't forget to free the memory used by the _strdup
for(int i=1; i < new_argc; i++)
{
if(i >= argc)
free(new_argv[i]);
}
}
test.bat
call test.exe < test.txt
test.txt
-arg1 param1 -arg2 param2 ...
Of course, I need to add some checks to make it properly handle whitespace, but that's the gist of it. Thank you for your help and external point of view.
Edit : Fixed a mistake in the code.

Related

C++ : Escaping quotes for std::string behaves differently on Linux vs Windows

So , my problem seemed an easy one to begin with.
I'm interested in saving the exact cmd line arguments i'm receiving in the main() of my program because of some inherited code that i'm supposed to use that needs access to it but gets called further down inside the code. I won't get into more detail here.
The main problem is the fact that if i pass a path that contains spaces, that path needs to be placed between double quotes. Those double quotes get stripped away by argv[] so i need to add them back.
So i did something like the following:
string buff;
buff.assign("");
for(int i = 0; i < m_argc; i++)
{
buff.append("\"");
buff.append(m_argv[i]);
buff.append("\" ");
}
Which on WINDOWS returns what i expect, something like this :
"C:\...\myProg.exe" "-s" "-i" "c:\...\file with spaces.dat".
Which is what i expect.
On LINUX, however i get something like this:
\"/.../myProg.exe\" \"-s\" \"-i\" \"/.../file with spaces.dat\"
Which is completely unexpected to me and further messes up any processing thereafter
Any help is appreciated, thanks :)
EDIT, as requested , a working example:
#include <iostream>
#include <cstdio>
#include <string.h>
#include <stdlib.h>
int main(int argc, char* argv[], char* envp[])
{
int nRetCode = 0;
std::string buff;
buff.assign("");
for(int i = 0; i < argc; i++)
{
if (i != argc-1)
{
if (i > 0 && (strcmp(argv[i-1],"-i") == 0 || strcmp(argv[i-1],"-o") == 0))
{
buff.append("\"");
buff.append(argv[i]);
buff.append("\" ");
}
else
{
buff.append(argv[i]);
buff.append(" ");
}
}
else
{
if (i > 0 && (strcmp(argv[i-1],"-i") == 0 || strcmp(argv[i-1],"-o") == 0))
{
buff.append("\"");
buff.append(argv[i]);
buff.append("\"");
}
else
buff.append(argv[i]);
}
}
std::cout << "contents of buff: \n" << buff.c_str() << std::endl;
return nRetCode;
}
I get a correct output from this one:
/.../myProg -s -i "/.../file with spaces"
As PaulMcKenzie and Igor Tandetnik suggested, that's just how the debugger reports the contents of the string.
Thank you for the help, everyone
The linux OS uses / for directory paths; Windows uses \. Nothing you can do about that.
The OS simply delivers all paths to you the way it knows them.

Python parsing stdin much faster than C++

I have a python function which I was hoping to translate into C++ to try and gain some extra speed (as it will be used to parse >100GB files). I am very inexperienced with C++ and was horrified to find my C++ function running much slower after my basic translation. Any pointers as to why this is, or what I can do to improve my C++ code would be much appreciated.
Script overview: the function reads stdin from another program, checks each line for any substring matches, and prints each line to stdout
Python function:
def find_tagPy(conditions):
# conditions e.g. ['TTAT', 'TAT'] etc
for line in stdin:
# Check conditionss against this line
l = line.split("\t")
if l[0][0] == "#":
stdout.write(line)
continue
FLAG = int(l[1])
if 1 & FLAG: # Read has a pair
for bases in conditions:
if bases in l[9]:
ADD_MATE = 1
stdout.write(line)
break # stop looking
C++ function:
void find_tagCpp (vector<string> conditions) {
cin.sync_with_stdio(false);
cin.tie(NULL);
string line;
while (getline(cin, line)) {
vector<string> l;
boost::split(l, line, boost::is_any_of("\t"), boost::token_compress_on);
if (l[0][0] == '#') {
cout << line << "\n";
continue;
}
int FLAG = stoi(l[1]);
int pair_FLAG = 1;
if (pair_FLAG & FLAG) { // Read has a pair
for (int i=0; i < conditions.size(); i++) { // If bases in SEQ
if (l[9].find(conditions[i]) != string::npos) {
cout << line << "\n";
break; // Stop looking
}
}
}
}
}
An example of a stdin line is:
FCC2CCMACXX:4:1105:10758:14389# 81 chrM 1 32 10S90M = 16151 16062 CATCACGATGGATCACAGGTCTATCACCCTATTAACCACTCACGGGAGCTTTCCATGCATTTGGTATTTTCGTCTGGGGGGTGTGCACGCGATAGCATTG bbb^Wcbbbbccbbbcbccbba]WQG^bbcdcb_^_c_^`ccdddeeeeeffggggiiiiihiiiiihiiihihiiiihghhiihgfgfgeeeeebbb NM:i:1 AS:i:85 XS:i:65 RG:Z:1_DB31
On my machine the python function takes 1.97 s and the C++ function takes 11.05 s (file size around 25 mb, but this includes processing with upstream and downstream tools)
EDIT:
I've found a bottleneck in boost::split which is a bit suprising:
Python:
for i in range(100000):
l = line.split("\t")
C++:
for (int i=0; i < 100000; i++) {
vector<string> l;
boost::split(l, line, boost::is_any_of("\t"), boost::token_compress_on);
}
Python = 0.0325 s
C++ = 1.245 s
However my file is only 156,980 lines, so this cant be the whole problem.
The split copies the pieces into new strings. This is slow and you do not need them. Instead search the line for the start of the piece you want ( 10th ) and then call find starting from there.
I realised my original code is not amenable to testing, so I thought I would refactor it here, and discuss what I have found.
I turned on compiler optimisations using the -Ofast (fastest, aggressive optimisations, Apple LLVM 6.1) as suggested, and for comparison Python is 2.7.10.
Python function
import time
def fun(line):
l = line.split(" ", 10)
if 'TTAGGG' in l[9]:
pass
line = "FCC2CCMACXX:4:1105:10758:14389# 81 chrM 1 32 10S90M = 16151 16062 CATCACGATGGATCACAGGTCTATCACCCTATTAACCACTCACGGGAGCTTTCCATGCATTTGGTATTTTCGTCTGGGGGGTGTGCACGCTTAGGGGATAGCATTG bbb^Wcbbbbccbbbcbccbba]WQG^bbcdcb_^_c_^`ccdddeeeeeffggggiiiiihiiiiihiiihihiiiihghhiihgfgfgeeeeebbb NM:i:1 AS:i:85 XS:i:65 RG:Z:1_DB31"
time0 = time.time()
for i in range(100000):
fun(line)
print time.time() - time0
C++ function
void fun(string* line, string* substring) {
vector<string> l;
boost::split(l, *line, boost::is_any_of(" "));
if (l[9].find(*substring) != string::npos) {
// Do nothing
}
}
int main(int argc, const char * argv[]) {
string line = "FCC2CCMACXX:4:1105:10758:14389# 81 chrM 1 32 10S90M = 16151 16062 CATCACGATGGATCACAGGTCTATCACCCTATTAACCACTCACGGGAGCTTTCCATGCATTTGGTATTTTCGTCTGGGGGGTGTGCACGCTTAGGGGATAGCATTG bbb^Wcbbbbccbbbcbccbba]WQG^bbcdcb_^_c_^`ccdddeeeeeffggggiiiiihiiiiihiiihihiiiihghhiihgfgfgeeeeebbb NM:i:1 AS:i:85 XS:i:65 RG:Z:1_DB31";
string substring = "TTAGGG";
boost::timer t;
for (int i=0; i<100000; i++) {
fun(&line, &substring);
}
cout << t.elapsed() << endl;
return 0;
}
On my machine I now time the c++ function at 205 ms and the python function at 66 ms. Interestingly, now almost the entire runtime is taken up by the boost::split function.
If I get rid of this function and use string.find to search the whole line (not quite what I wanted though):
if ((*line).find(*substring) != string::npos) {
// Do nothing
}
The c++ runtime is reduced to about <1 ms! So it appears boost::split was the problem all alone. Thanks for the suggestions.
Try this code with some optimization
C++ function:
void find_tagCpp (vector<string> conditions) {
cin.sync_with_stdio(false);
cin.tie(NULL);
string line;
vector<string> l;
while (getline(cin, line)) {
l.clear();
boost::split(l, line, boost::is_any_of("\t"), boost::token_compress_on);
if (l[0][0] == '#') {
cout << line << "\n";
continue;
}
int FLAG = stoi(l[1]);
int pair_FLAG = 1;
if (pair_FLAG & FLAG) { // Read has a pair
for (int i=0; i < conditions.size(); i++) { // If bases in SEQ
if (l[9].find(conditions[i]) != string::npos) {
printf("%s\n", line.c_str());
break; // Stop looking
}
}
}
}
}

C++ How to pass command line argument to read txt file

What I've been trying to do is...
1) to read txt files by command line argument,
2) to use strings in the txt files as arguments for the main method (or whatever method you need to invoke).
For example, there are two txt files, one of which is named character.txt and the other match.txt.
The contents of the files would be like this.
character.txt
//This comprises of six rows. Each of the rows has two string values
Goku Saiyan
Gohan Half_Saiyan
Kuririn Human
Piccolo Namekian
Frieza villain
Cell villain
match.txt
//This comprises of three rows, each of them is one string value
Goku Piccolo
Gohan Cell
Kuririn Frieza
If I use those strings without using command line, I'd declare the strings in character.txt like this.
typedef string name; //e.g. Goku
typedef string type; //e.g. Saiyan, Human, etc
Now I'm looking for how to read and send string values from txt files like the ones above, and to use them for functions inside the main method, ideally like this way.
int main(int argc, char *argv)
{
for (int i = 1; i < argc; i++) {
String name = *argv[i]; //e.g. Goku
String type = *argv[i]; //e.g. Saiyan, Human, etc
String match = * argv[i]; //Goku Piccolo
//I don't think any of the statements above would be correct.
//I'm just searching for how to use string values of txt files in such a way
cout << i << " " << endl; //I'd like to show names, types or matchs inside the double quotation mark.
}
}
Ideally, I'd like to invoke this method in this way.
According to this web site., at least I understand it is possible to use command line arguments with C++, but I cannot find any more information. I'd appreciate if you'd give any advice on it.
PS. I'm using Windows and Code Blocks.
Asuming you just want to read contents of the files and process it, you can start with this code (Without any errors checks tho). It simply gets filenames from command line and reads file contents into 2 vectors. Then you can just process these vectors as u need.
#include <string>
#include <fstream>
#include <iostream>
#include <vector>
std::vector<std::string> readFileToVector(const std::string& filename)
{
std::ifstream source;
source.open(filename);
std::vector<std::string> lines;
std::string line;
while (std::getline(source, line))
{
lines.push_back(line);
}
return lines;
}
void displayVector(const std::vector<std::string&> v)
{
for (int i(0); i != v.size(); ++i)
std::cout << "\n" << v[i];
}
int main(int argc, char **argv)
{
std::string charactersFilename(argv[1]);
std::string matchesFilename(argv[2]);
std::vector<std::string> characters = readFileToVector(charactersFilename);
std::vector<std::string> matches = readFileToVector(matchesFilename);
displayVector(characters);
displayVector(matches);
}
to see how to use command line arguments look at this.
http://www.cplusplus.com/articles/DEN36Up4/
you cannot use the contents of the file which you have passed to your app through command line arguments. only the name of the file is passed to the app.
you should open the file using that name and read its contents. take a look at this:
http://www.cplusplus.com/doc/tutorial/files/
First the main function prototype should be
int main(int argc, char **argv)
OR
int main(int argc, char *argv[])
Second after retrieving files names in the main function you should open each file and retrieve its contents
Third Sample code
int main(int argc, char* argv[])
{
for(int i=1; i <= argc; i++) // i=1, assuming files arguments are right after the executable
{
string fn = argv[i]; //filename
cout << fn;
fstream f;
f.open(fn);
//your logic here
f.close();
}
return 0;
}
You define main prototype incorrectly. You also need std::ifstream to read files.
If you expect exactly two arguments, you may check argc and extract arguments directly:
int main(int argc, char* argv[]) {
if(argc != 3) {
std::cerr << "Usage: " << argv[0]
<< " name.txt match.txt" << std::endl;
return 1;
}
std::ifstream name_file(argv[1]);
std::ifstream match_file(argv[2]);
// ...
return 0;
}
If you expect unspecified number of files, than you need a loop and an array to save them, i.e. vector:
int main(int argc, char* argv[]) {
std::vector<std::ifstream> files;
for(int i = 1; i < argc; ++i)
files.emplace_back(argv[i]);
// ...
return 0;
}
And do not forget to check if files are openable.
#include<stdio.h>
#include<stdlib.h>
int main(int argc, char *argv[])
{
FILE *fp = fopen( argv[1], "r");
char line[50];
if (fp == NULL)
{
printf("File opening Unsuccessful\n");
exit(1);
}
while (fgets(line , 30 , fp) != NULL)
{
printf("%s",line);
}
fclose(fp) ;
return 0;
}

Checking command line inputs for errors in C++

I had this program working just a bit ago but changed something and now my error handling has gone bonkers. I'm practically hitting my head against the wall here trying to put it back the way it was, but no matter what I do now, it spits out an error.
The program is supposed to take command line arguments to define the rows and columns and create a dynamic 2D array based on that. The format is "-rows (number) -columns (number)". I tried to add a few more cases before I turned in the assignment, but I must have changed some logic elsewhere, because even after I removed the new part it still won't work no matter what I input. At this point I think I just need a pair of fresh eyes to point me in the right direction.
int main(int argc, char* argv[]) {
// Checks if the user input the correct number of arguments.
// If so, checks if they were input corecctly. If so, it assigns
// the user input values to rows/columns, and if not, prints
// an error message.
if(argc == 5) {
for(int i = 1; i < argc; i++) {
rows = getArg(argc, argv, i, compare1);
}
for(int i = 1; i < argc; i++) {
columns = getArg(argc, argv, i, compare2);
}
} else {
printError(argv);
}
That is the relevant part of main.
Below are the functions involved in checking for errors. The one I actually have been working on is getArg, so I'm assuming this is where the logic is failing, but I included the other necessary function for clarity.
// Description: Checks if user input was valid
// Parameters: Command line arguments, int i from the for
// loop used to run this check on all command line arguments
// in main, and an array of chars used to compare the user's
// inputs to "-rows" or "-columns"
// Return value: If user input was valid, returns an int
// If not, exits program.
int getArg(int argc, char* argv[], int i, char compare[]) {
int arg;
if (strcmp(argv[i], compare) == 0) {
if (isInt((i + 1), argv)) {
arg = atoi(argv[i + 1]);
} else {
printError(argv);
}
} else {
printError(argv);
}
return arg;
}
// Description: Checks user input for valid integers
// Parameters: Command line arguments
// Return value: Returns true if input is an int;
// false if not.
bool isInt(int argc, char* argv[]) {
bool isInt;
for (int j = 0; j < strlen(argv[argc]); j++) { //For loop runs through each char in the array at argc
if (isdigit(argv[argc][j])) { // Checks to see if char is an integer
isInt = true;
return isInt;
} else {
isInt = false; // If there is ever a non-integer character, exit loop and return false
return isInt;
}
}
}
The first for loop in main continues to scan the argument list after it has matched "-rows". The loop makes the following calls (I'm assuming compare1="-rows" here since you didn't mention it):
i=1: getArg(argc=5, argv={"a.out", "-rows", "2", "-columns", "3"}, i=1, compare="-rows")
returns 2
i=2: getArg(argc=5, argv={"a.out", "-rows", "2", "-columns", "3"}, i=2, compare="-rows")
calls printError(argv) because strcmp("2", "-rows") is nonzero
Also, as Abhishek mentioned, isInt only ever checks the first character of the string because you return in the isInt = true branch.
One possible error that I can spot is that you are not looping correctly in your isInt() function.
You would invariably return out of the function after the 1st iteration.
Its nice you got an answer, but this is one is pretty easy to use and on entire SO you'll find this suggestion wherever such questions are tagged for C++
Using boost::program_options
#include <iostream>
#include <boost/program_options.hpp>
namespace po = boost::program_options;
int main( int argc, char *argv[ ] )
{
try {
int rows,cols;
po::options_description desc("Allowed options");
desc.add_options()
( "help", "produce this help message" )
( "rows", po::value< int>(&rows)->required(), "No. of Rows" )
( "cols", po::value< int>(&cols)->required(), "No. of Cols" )
;
po::variables_map vm;
po::store( po::parse_command_line( argc, argv , desc ), vm );
po::notify( vm );
if ( vm.count( "help" ) )
{
std::cout << desc;
return 0;
}
std::cout<<"Rows :"<<rows<<" "<<"Cols :"<<cols<<std::endl;
}
catch( std::exception& e )
{
std::cout << e.what() << "\n";
return 1;
}
return 0;
}
Usage :
./test --cols 4 --rows 3
Outputs :
Rows :3 Cols :4
Good Tutorial Here

regarding file existence check within a for loop in C++

I am attempting to check if a file exists, and then if so proceed with a task and if not to just output that there is no such file. I have done this in other code but it doesn't seem to be working with my current code.
The basics of it read:
count=argc;
for(i=0; i < count-1; i++)
{
filename[i] = argv[i+1];
}
for( i=0; i < count-1; i++)
{
int tempi=i;
ifstream infile(filename[i].c_str());
if(infile)
{
//do things
}
else
{
cout<<"no file"<<endl;
}
You need to call infile.is_open(). Also, do you plan to do something with the file if it exists, or not?
The canonical way to access argv is:
int main( int argc, char * argv[] ) {
for ( int i = 1; i < argc; i++ ) {
// do something with argv[i]
}
}
infile, in the conditional, evaluates to false when the stream is in a "bad" state.
However, merely failing to open a file does not leave the stream in a bad state (welcome to C++!). Only after attempting to read from the stream would this mechanism work for you.
Fortunately, you can use infile.is_open() to explicitly test for whether the stream was opened or not.
Edit
The above is not true.
Testing the stream state is sufficient, and I can't see anything wrong with your code.