2

I'm trying to work through a simple hackerank problem and I'm stuck. When executing grep on the command line, I have no issues

grep -i \"the[^a-z0-9]\" $filename <<< "from fairest creatures we desire increase"

result: (returns nothing)

grep -i \"the[^a-z0-9]\" $filename <<< "the clock struck twice" result: the(highlighted) clock struck twice I read this article : Can I open bash from a popen() stream?

I have tried prepending the grep command with "exec bash -c" and "/bin/bash -c" to no avail.

Can anyone tell me why I'm getting this error and how to fix it?

sh: 1: Syntax error: redirection unexpected

f is null

My code:

#include <iostream>
#include <vector>
#include <algorithm>
#include <cstdio>
#include<cmath>
using namespace std;
int main(){

   /* Enter your code here. Read input from STDIN. Print output to STDOUT */
    int MAX_BUFFER =2048;
    string output;
    char fileInput [MAX_BUFFER];
   // string input(istreambuf_iterator<char>(cin), {});

   string input =" From fairest creatures we desire increase ,\
That thereby beauty's rose might never die,\
But as the riper should by time decease,\
His tender heir might bear his memory:\
But thou contracted to thine own bright eyes,\
Feed'st thy light's flame with self-substantial fuel,\
Making a famine where abundance lies,\
Thy self thy foe, to thy sweet self too cruel:\
Thou that art now the world's fresh ornament,\
And only herald to the gaudy spring,\
Within thine own bud buriest thy content,\
And tender churl mak'st waste in niggarding:\
Pity the world, or else this glutton be,\
To eat the world's due, by the grave and thee.\
When forty winters shall besiege thy brow,\
And dig deep trenches in thy beauty's field,\
Thy youth's proud livery so gazed on now,\
Will be a tattered weed of small worth held:\
Then being asked, where all thy beauty lies,\
Where all the treasure of thy lusty days;\
To say within thine own deep sunken eyes,\
Were an all-eating shame, and thriftless praise.\
How much more praise deserved thy beauty's use,\
If thou couldst answer 'This fair child of mine\
Shall sum my count, and make my old excuse";
string cmd_string = "/bin/bash -c grep -i \"the[^a-z0-9]\" $filename <<< "+input;
    //cout<<cmd_string.c_str();
    FILE *f=popen(cmd_string.c_str(),"r");
   while (!feof(f))
       {
            if (fgets(fileInput, MAX_BUFFER, f) != NULL)
            {
              cout<<" line 1"<<fileInput[0];
              output+=fileInput;
            }
           else
               {

               cout<<"f is null"<<endl;
           }
       }
       pclose(f);

    cout<< output;
return 0;

}
Community
  • 1
  • 1
artifex_somnia
  • 438
  • 9
  • 20
  • What is the point of `$filename` in that command line? I assume that `$filename` is undefined (or the empty string) since otherwise you wouldn't get the indicated output from the command line. But surely it is simpler to leave it out of the command? – rici Sep 02 '15 at 03:25
  • since grep does not parse strings (have to be files) `$filename` allows me to store the string into a variable which grep will process as though its a file. leaving it out would cause an error – artifex_somnia Sep 02 '15 at 03:29
  • That's what the here-string does (i.e. `<<< word`): it sends `word` to the command's stdin. Leaving out the `$filename` will have no effect. – rici Sep 02 '15 at 03:32
  • Ok. I tried it on the command line and that seems correct. However i'm getting an error `Filename too long` – artifex_somnia Sep 02 '15 at 03:37
  • Yeah, I'm getting to that. Answer being composed. – rici Sep 02 '15 at 03:37

2 Answers2

1

The normal (and almost universal) convention for handling command-line flags which take arguments is that the single argument following the command-line flag itself is the associated argument. Subsequent arguments are either other flags or positional arguments.

That's certainly true of bash. If you want to use the -c command-line flag to execute a bash command, the entire command needs to be the argument to the -c flag, and it needs to be a single argument. Subsequent arguments are assigned to $0, $1, etc. before the command is executed.

So if you type:

bash -c echo foo

you will see a blank line. You could do this:

bash -c 'echo $0' foo

which will print foo "as expected" (if your expectations are well-honed), but it would be normal to do this:

bash -c 'echo foo' 

Similarly, if you execute

/bin/bash -c grep -i \"the[^a-z0-9]\" ...

you are asking bash to execute the command grep with no arguments; the -i and subsequent arguments are assigned to $0, etc., but since those are never referenced by the command, that is somewhat pointless.

What you meant was

/bin/bash -c 'grep -i "the[^a-z0-9]" ... '

(You cannot backslash escape anything inside single-quoted strings, so the backslashes are removed. I don't see how they could have worked in the original command-line either; there are no double quotes to match in the target string.)

Obviously, it is vital that the here-string (<<<"...poem...") be part of the -c argument to /bin/bash and not part of the command-line interpreted by popen. popen uses sh to execute the provided command-line, and it is highly likely that sh does not recognize the "here-string" syntax; it's a bash extension. Because <<< is not special in sh, << is interpreted normally as a here-doc, which must be followed by a delimiter string. It cannot be followed by a < redirection; hence the "unexpected redirection" error. When you make the word following <<< very long, you apparently trigger a "filename too long" error before the erroneous redirection has been parsed. That's just a guess; I didn't reproduce the problem.

Quoting is going to be annoying for you. As mentioned, there is no problem with " inside single-quoted strings, but you will have to backslash the double-quotes to comply with C string literal syntax. Unfortunately, the string literal contains single quotes, and the first of those will be parsed as closing the single-quote in bash -c '. And there is no way to backslash-escape anything inside bash single-quoted strings, not even a single quote.

The only thing that will work will be to represent each ' with the sequence '\'' (which will need to be '\\'' inside the C string). In that sequence, the first and last ' close and reopen the single-quoted string, and the \' in the middle is a backslash-escaped '.

Three final notes:

  1. $filename has no effect in your commands, since $filename has never been defined. If it were defined, it would create a variety of errors; since it is not defined and the expansion is not quoted, it simply disappears from the command-line, but it would be a lot easier not to put it there in the first place.

  2. C strings can be continued from line to line using a backslashed newline, as in your sample code, but that does not insert a newline into the string. So the input provided to grep will be a single line. I don't think that's what you meant.

  3. You do have to quote the here-string; the syntax of here-strings is <<< followed by a single "word"; if you don't put quotes around it, you'll end up with only the part up to the first space being treated as input.

rici
  • 234,347
  • 28
  • 237
  • 341
  • have you tried running this? When I do it does not compile. I get an error saying `test.cpp:40:49: error: expected ‘,’ or ‘;’ before ‘the’ string cmd_string = "/bin/bash -c 'grep -i "the[^a-z0-9]" <<< "+'"'+ input +'"';` If I remove the quotes from "the[^a-z0-9]" the code compiles but present he same "redirection unexpected" error. – artifex_somnia Sep 02 '15 at 03:47
  • @bbpy-newb: You need to distinguish between backslashes in C strings, and backslashes in bash commands. I tried to describe that in the last addition I made to my answer; if it is confusing, that's because properly escaping characters when you have three levels of quoted strings *is* confusing. Good luck. (And, no, I didn't try compiling anything.) – rici Sep 02 '15 at 03:58
  • I'll look through it. Thanks for the help – artifex_somnia Sep 02 '15 at 04:02
0

I think your input is not quoted.

string cmd_string = "/bin/bash -c grep -i \"the[^a-z0-9]\" $filename <<< "+ \" + input + \";
mksteve
  • 12,614
  • 3
  • 28
  • 50
  • string cmd_string = "/bin/bash -c grep -i \"the[^a-z0-9]\" $filename <<< "+ '\"' + input + '\"'; worked. However now there is apparently a size limit on the filename `File name too long' – artifex_somnia Sep 02 '15 at 03:04