0

I have found that the command expanded from variable won't run the latter parts in a pipe. For example, as following test.sh:

#!/bin/bash

y='echo hello man | awk "{print \$1}"'
$y
eval $y

y='echo hello'
$y

The output of the script is:

hello man | awk "{print \$1}"
hello
hello

The first $y only execute echo hello man but not execute awk "{print $1}" of the pipe. That's why?
My bash version is 4.3.48.

1 Answer 1

3

That's because the variable is expanded after the pipes and redirection is already done. So in that case | is just another argument to echo, not a pipe that's interpreted by the shell.

Recommended reading: http://mywiki.wooledge.org/BashFAQ/050

When executing the command

echo hello man | awk '{print $1}'

the shell will see the | and setup a pipeline, on one side it will run the command echo hello man and on the other awk '{print $1}'. The commands then get subjected to word splitting so you run the command echo with 2 arguments: hello and man. On the other side you run the command awk with a single argument (because of the quoting) "{print \$1}"

When that command is stored as a string though the shell first looks at the command $y and sees no redirection to do. It then expands $y and then does word splitting on it. It gets expanded to the same looking string, but now it's too late for redirection. So it gets split into the words echo, hello, man, |, awk, "{print, \$1}" (note that the argument to awk now gets split because the quotes inside the string are part of the string, not syntactical)

The first word in that list is echo so that's the command, and all the rest of the words are passed as arguments to it, which is why you see the output

hello man | awk "{print \$1}"

When you do the eval line, it takes that same string and tells bash to parse it as though it had been typed so the pipe once again becomes syntatical and causes the pipeline.

Because echo puts its arguments on the same line it's a little tougher sometimes to see what's happening, if we replace it with printf '%s\n' each argument will become its own line though:

$ y='printf %s\n hello man | awk "{print \$1}"'
$ $y
hello
man
|
awk
"{print
\$1}"
Sign up to request clarification or add additional context in comments.

7 Comments

Variable is expanded after the pipes and redirection. But When is echo executed? Is echo not a part of the pipe?
@zhenguoli no, the variable gets expanded to the string you assigned to it, then that gets subject to word splitting. The first word is treated as the command and all the rest are arguments to that command. So echo is the first word and is the command, no redirection is processed, so everything else, including the pipe, is an argument to echo
You mean the Bash treats it as a echo command, not a pipe. In other words, the pipe is not done.
Sort of, if it were a pipe you'd have both an echo command and an awk command, with the stdout of one as the stdin of the other. In this case though, the pipe is not seen by the shell as having that special meaning--it doesn't appear until it's too late for the shell to do that. The | is just another part of the string that's passed to the echo command, and because of the the string "awk" is also an argument passed to echo and so forth. There's no pipeline and there is only one command executed instead of 2
Thanks very much, I probably get it.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.