33

The echo command isn't including the complete text that I give it. For example, if I do:

   $ echo ' echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '{print $2}' `    '

It outputs:

echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk {print } `

The single quotes (') I had in my echo command aren't included. If I switch to double quotes:

$ echo " echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '{print $2}' `  "

echo doesn't output anything at all! Why is it leaving out the single quotes in the first example and outputting nothing in the second? How do I get it to output exactly what I typed?

Michael Mrozek
  • 93,103
  • 40
  • 240
  • 233
  • 3
    Not quite sure what you're trying to achieve here. Why are you nesting one echo statement in another? Or are you literally trying to print out a command (for an example or such)? – Andy Smith Nov 15 '11 at 16:29
  • I need to insert the echo output to another file –  Nov 15 '11 at 16:36
  • You need to explain what you're trying to do. Such as "I want to append the following text: '…' to a file." – MikeyB Nov 15 '11 at 16:55
  • 2
    I've been trying to edit this for 5 minutes but I seriously don't know what the desired output is. Edit: Ok, I think I have a guess now, so I tried editing. If it's not what you intended let me know – Michael Mrozek Nov 15 '11 at 18:08
  • 1
    @Michael - Wow - If I could give you some rep for your edit, I would - nice job making this a really great question! – Randall Nov 10 '16 at 19:09

5 Answers5

43

Your shell is interpreting the quotes, both ' and ", before they even get to echo. I generally just put double quotes around my argument to echo even if they're unnecessary; for example:

$ echo "Hello world"
Hello world

So in your first example, if you want to include literal quote marks in your output, they either need to be escaped:

$ echo \'Hello world\'
'Hello world'

Or they need to be used within a quoted argument already (but it can't be the same kind of quote, or you'll need to escape it anyway):

$ echo "'Hello world'"
'Hello world'

$ echo '"Hello world"'
"Hello world"

In your second example, you're running a command substitution in the middle of the string:

grep  $ARG  /var/tmp/setfile  | awk {print $2}

Things that start with $ are also handled specially by the shell -- it treats them as variables and replaces them with their values. Since most likely neither of those variables are set in your shell, it actually just runs

grep /var/tmp/setfile | awk {print}

Since grep only sees one argument, it assumes that argument is the pattern you're searching for, and that the place it should read data from is stdin, so it blocks waiting for input. That's why your second command appears to just hang.

This won't happen if you single-quote the argument (which is why your first example nearly worked), so this is one way to get the output you want:

echo \'' echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '{print $2}' `    '\'

You can also double-quote it, but then you'll need to escape the $s so the shell doesn't resolve them as variables, and the backticks so the shell doesn't run the command substitution right away:

echo "' echo PARAM=\`  grep  \$ARG  /var/tmp/setfile  | awk '{print \$2}' \`    '"
Michael Mrozek
  • 93,103
  • 40
  • 240
  • 233
9

I'm not going to go into much detail on why your attempts behave the way they do, because Michael Mrozek's answer covers this well. In a nutshell, everything between single quotes ('…') is interpreted literally (and in particular the first ' marks the end of the literal string), whereas ` and $ retain their special meaning between "…".

There is no quoting within single quotes, so you can't put a single quote inside a single-quoted string. There is however an idiom that looks like it:

echo 'foo'\''bar'

This prints foo'bar, because the argument to echo is made of the single-quoted three-character string foo, concatenated with the single character ' (obtained by protecting the character ' from its special meaning through the preceding \), concatenated with the single-quoted three-character string bar. So although that's not quite what happens behind the scenes, you can think of '\'' as a way to include a single quote inside a single-quoted string.

If you want to print complex multiline strings, a better tool is the here document. A here document consists of the two characters << followed by a marker such as <<EOF, then some lines of text, then the end marker alone on its line. If the marker is quoted in any way ('EOF' or "EOF" or \EOF or 'E""OF' or …), then the text is interpreted literally (like inside single quotes, except that even ' is an ordinary character). If the marker is not quoted at all, then the text is interpreted like in a double-quoted string, with $\` retaining their special status (but " and newlines are interpreted literally).

cat <<'EOF'
echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '{print $2}' `
EOF
1

Okay, this will work:-

echo ' echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '\''{print $2}'\'' `    '

Testing it here:-

[asmith@yagi ~]$ echo ' echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '\''{print $2}'\'' `    '
 echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '{print $2}' ` 
Andy Smith
  • 198
  • 1
  • 3
0

Gilles's suggestion of using a here document is really nice, and even works within scripting languages like Perl. As a specific example based on his answer to the OP's issue,

use strict;
my $cmd = q#cat <<'EOF' # . "\n" .
          q#echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '{print $2}' `# . "\n" .
          q#EOF# ;
open (my $OUTPUT, "$cmd |");
print $_ while <$OUTPUT>;
close $OUTPUT;

Granted, this example is a little contrived, but I've found it to be a useful technique for, say, sending SQL statements to psql (instead of cat).

(Note that any non-alphanumeric non-space character can be used in place of the # in the generic quoting construct above, but the hash seemed to stand out well for this example, and is not treated as a comment.)

Randall
  • 445
-1

Let's take your command

$ echo ' echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '{print $2}' `    '

and first split it into words, using unquoted whitespace as delimiter:

Arg[1]=“' echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '{print”
Arg[2]=“$2}' `    '”

Next, we do parameter expansion: expand $… but not inside '…'. Since $2 is empty (unless you set it via e.g. set -- …), it will be replaced by an empty string.

Arg[1]=“' echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '{print”
Arg[2]=“}' `    '”

Next, do quote removal.

Arg[1]=“ echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk {print”
Arg[2]=“} `    ”

These arguments are then passed to echo, which will print them one after the other, separated by a single space.

“ echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk {print } `    ”

I hope that this step-by-step instruction makes the observed behavior clearer. To avoid the issue, escape single quotes like this:

$ echo ' echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '\''{print $2}'\'' `    '

This will end the single-quoted string, then add an (escaped) single quote to the word, then start a new single-quoted string, all without breaking the word apart.

MvG
  • 4,411