0

My current shell is bash, but

echo '1 2 3 4'| awk '{print $2}' 

and

bash -c " echo '1 2 3 4'| awk '{print $2}' "

gives different result. Os is Linux. What's wrong on second statement?

chepner
  • 497,756
  • 71
  • 530
  • 681
Rovshan Musayev
  • 144
  • 3
  • 15
  • @hek2mgl I feel like the answers to the question you linked as duplicate aren't enough to answer OP's problem. – Aaron Feb 06 '17 at 13:27
  • What makes you think that? – hek2mgl Feb 06 '17 at 13:35
  • @hek2mgl The accepted answer of the other question would prompt to use single-quotes to preserve literals, but that's not easily adaptable to a `bash -c` command because of the double expansion and the difficulty to write single quotes in a text that is enclosed in single quotes. – Aaron Feb 06 '17 at 14:06
  • If I had the same problem and was only told to use single-quotes, I feel like I would come back to ask how I am supposed to do so. Maybe I would first try to escape them with `\'` as most characters are escaped. – Aaron Feb 06 '17 at 14:09
  • Instead of coming back and ask, I would try "how to escape single quote inside single quoted string" on Google. Please make no big science out of this. – hek2mgl Feb 06 '17 at 14:55
  • 1
    @hek2mgl I'd sure love people to be more autonomous, but that's not the point. I just feel like the other answer doesn't answers OP's specific problem, and I think I've stated why. Now if you don't agree that's OK, if it really bothered me I'd have voted to reopen the question. – Aaron Feb 06 '17 at 16:25

2 Answers2

3

How about using a HERE-document?

bash <<'EOH'
echo '1 2 3 4'| awk '{print $2}'
EOH

Note that I have dropped the -c option to bash, and that the single quotes around EOH are necessary to avoid evaluating $2 on the shell level.

user1934428
  • 19,864
  • 7
  • 42
  • 87
  • Yes, this also works, thanks a lot ! – Rovshan Musayev Feb 06 '17 at 10:59
  • Nicer than my solution. I'm curious what you mean with `EOH` though (any string can be used, and the norm is to use EOF for `End Of File`). Edit : nvm, `End Of Heredoc` I suppose. – Aaron Feb 06 '17 at 10:59
  • This solution is different from the other solution. This solution passes the commands to bash over `stdin`. Other solution (and question) passes them as command line arguments to bash. – anishsane Feb 06 '17 at 11:15
  • 1
    Not a single word about double quotes? – hek2mgl Feb 06 '17 at 13:20
  • @Aaron : EOH = "end of here-document". You can alternatively write "MGB" (= My grandmother's birthday). – user1934428 Feb 06 '17 at 15:55
  • @anishsane : This is correct, but in this example, the outcome is the same. Do you see a case where this distinction is important? – user1934428 Feb 06 '17 at 15:57
  • @hek2mgl : Could you elaborate further? In what respect should we discuss double quotes? – user1934428 Feb 06 '17 at 15:58
  • 1
    @user1934428 about anishsane's comment : it would be important if `bash -c` was used to provide positional parameters (it considers following parameters as positional parameters for the command). In this specific case I don't think it matters – Aaron Feb 06 '17 at 16:03
  • 1
    I am saying that for this case - bash being the binary to be run - these 2 mechanisms will give the same output. Your solution is more readable because it does not need escaping of quotes. However, for some other binary, that may not be the case. So, while your solution works for this case, OP should understand that the other solution is different and for some other binary, that solution will be required; not this one. – anishsane Feb 07 '17 at 03:29
  • What @hek2mgl said is that you have given a solution without mentioning why OP's code does not work. – anishsane Feb 07 '17 at 03:30
  • @anishsane: I see now what you mean. You're right. Fortunately, Aaron's response already explains it. – user1934428 Feb 07 '17 at 05:58
2

In your second command, $2 is evaluated before being handed to awk, which gives the following command :

echo "1 2 3 4" | awk "{print }"

To avoid this, you can use this awful syntax :

bash -c 'echo "1 2 3 4"| awk '"'"'{print $2}'"'"''

Or this syntax suggested by chepner :

bash -c $'echo "1 2 3 4" | awk \'{print $2}\''

The problem is that variable expansion is done twice here : a first time when the bash -c command is evaluated, and a second time when the spawned bash process evaluates its command line.

My initial answer was to change the command to bash -c 'echo "1 2 3 4"| awk "{ print $2 }'", which indeed avoided expansion in your current shell. However, in the spawned bash process, expansion was executed on the following command :

echo "1 2 3 4" | awk "{print $2 }"

And $2 was expanded to the empty string.

So we need this command to be executed by the spawned bash :

echo "1 2 3 4" | awk '{print $2 }'

And we need to surround it with single quotes in the current shell :

bash -c 'echo "1 2 3 4" | awk '{print $2 }''

Except here the awk quotes close the bash -c parameter's quotes, which leads to the above command where we use '"'"' to write a single quote inside a singe-quoted text.

Community
  • 1
  • 1
Aaron
  • 24,009
  • 2
  • 33
  • 57